I simply want a best-fitting ellipse or circle for scatter data I have. I have been able to fit a circle to the data using numerous packages, but then the results are clearly nonsense. Maybe I need to do something weird to get results that work (a) for lat/lon data and (b) with Cartopy projections?
I have the following array of longitude/latitude values:
coords = np.array([-153.1906979 , 62.01707771],
[ 13.05660412, 63.15537447],
[-175.82610203, 67.11698477],
[ -10.31730643, 61.74562855],
[ 168.02402748, 79.60818152],
[ -34.46162907, 65.10894426],
[ -57.20962503, 59.49626998],
[ 113.70202771, 68.22239091],
[ -80.43411993, 55.6654176 ],
[ 93.77252509, 76.19392633],
[-104.10892084, 56.68264351],
[ 66.36158188, 67.59664968],
[-127.75176924, 57.31577071],
[-151.83057714, 61.64142205],
[ 17.44848859, 56.02194986],
[-176.30087703, 66.5955554 ],
[ -5.48747931, 61.95844561],
[ 160.22917767, 66.07650153],
[ -27.93440014, 67.82152994],
[ 137.09393573, 63.71148003],
[ -53.3290508 , 55.79699915],
[ 109.42329666, 75.43090294],
[ -76.59105583, 59.18143738],
[ 89.94733587, 63.50658353],
[-100.54585734, 55.16704225],
[ 66.15810397, 64.64851675],
[-123.65415058, 60.14507524],
[ 41.00262656, 70.67714209],
[-145.66917977, 68.55315102],
[ 18.34306395, 67.62222778])
I plot them on a map as following:
fig = plt.figure(figsize=(20,20))
ax = fig.add_subplot(121,projection=ccrs.NearsidePerspective(central_longitude=0, central_latitude=90,
satellite_height=30785831))
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'ocean', '50m', facecolor='#daf7f7', alpha=0.7, zorder=0))
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'land', '50m', facecolor='#ebc7a4', edgecolor='black', alpha=0.7,zorder=0))
ax.set_global()
grid = ax.gridlines(draw_labels=True)
grid.xlabel_style = {'size': 20, 'color': 'black'}
grid.ylabel_style = {'size': 20, 'color': 'black'}
ax.scatter(coords[0:,0], coords[0:,1], c='red', s=40, zorder=1, transform=PlateCarree())
I get this plot
All I now want to do is fit an ellipse or a circle to this scatter data. Using the solution here: https://stackoverflow.com/a/52877062/17583970, I cannot even try plot anything because the b axis of the ellipse is just a nan. Using skg.nsphere_fit() gave a radius of 433, which is obviously wrong or needs transforming in some way.
Any help would be greatly appreciated.
The coordinates you use really matter in this case. Any fitting will use some sort of distance metric that's being minimized, and the distance in lat/lon coordinates doesn't reflect something in meters or miles at all:
https://en.wikipedia.org/wiki/Geodesics_on_an_ellipsoid
If you're willing to assume that your map projection is a reasonable distance metric you can simply transform the coordinates, and perform the fit on those. I suspect that in this case the fit will be slightly biassed towards the pole (center of the map), making the ellipse a little smaller than it would be when fitted using the actual distance on the earths surface.
Using Cartopy to define the projections of your data and map:
data_proj = ccrs.PlateCarree()
map_proj = ccrs.NearsidePerspective(central_longitude=0, central_latitude=90, satellite_height=30785831)
Those can then be used to convert the coordinates to the map projection:
coords_map = map_proj.transform_points(data_proj, coords[:,0], coords[:,1])[:, :-1]
Fitting (and predicting) an ellipse using Scikit-Image:
from skimage.measure import EllipseModel
model = EllipseModel()
model.estimate(coords_map)
ellipse_coords = model.predict_xy(np.linspace(0, 2*np.pi, 100))
That gives the vertices of the ellipse in the map projection. You could consider converting them back to lat/lon, which would allow you to use ccrs.Geodetic and have Cartopy plot the segments as great circles. But sampling the predicted ellipse in map coordinates might already be fine.
This results in:
fig, ax = plt.subplots(figsize=(6,6), subplot_kw=dict(projection=map_proj), dpi=86, facecolor="w")
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'ocean', '110m', facecolor='#C6EEFF', alpha=1, zorder=0))
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'land', '110m', facecolor='#A2BAA4', edgecolor='none', alpha=1,zorder=0))
ax.set_global()
grid = ax.gridlines(draw_labels=True, lw=0.5, color="k", alpha=0.2)
ax.plot(coords_map[0:,0], coords_map[0:,1], "o", mfc="none", mec='#A70000', mew=1, ms=5, zorder=1, transform=map_proj)
ax.plot(ellipse_coords[:, 0], ellipse_coords[:,1], "-", color="#A70000", transform=map_proj)
I am using leaflet and openstreet maps. I created a png using EPSG3857 however the image is not laying correctly on the map.
if you look at the Baja Region and Florida you will see the data on land. The data should be over the water not the land.
var map = L.map('map', { editable: true },{crs: L.CRS.EPSG3857}).setView(initialCoordinates , initialZoom),
tilelayer =
L.tileLayer(url_tile,
{
noWrap: true,
maxZoom: 12, minZoom: 2, attribution: 'Data \u00a9 OpenStreetMap Contributors Tiles \u00a9 HOT'
}).addTo(map);
var overlay_image = 'images/webmercator-google.png';
imageBounds = [[-90, -180], [90, 180]];
L.imageOverlay(overlay_image, imageBounds, { opacity: 0.8 }).addTo(map);
When using EPSG:3857, Leaflet clamps all latitude data to +/-85.05° (or, to be precise, +/-20037508.34 on the EPSG:3857 Y coordinate). This is done to prevent data appearing outside of the coverage area of default EPSG:3857 tiles.
To illustrate this, consider the following bit of code:
for (var i=83; i<90; i+=0.1) {
L.marker([i, i]).addTo(map);
}
That should (naïvely) display a lot of markers in a diagonal-ish line. But when actually doing that, the result looks like:
See how the markers don't go north of the 85.01° parallel, and how that fits the limit of tiles (blue sea versus grey out-of-map background).
Remember, EPSG:3857 and any other (non-traverse, non-oblique) cylindrical projections cannot display the north/south poles because they get projected to an infinite Y coordinate.
OK, so what does this have to do with your problem? You're using:
imageBounds = [[-90, -180], [90, 180]];
But, since Leaflet will clamp latitudes, that's actually the same as doing:
imageBounds = [[-85.01, -180], [85.01, 180]];
Keep this in mind when using L.ImageOverlays that cover areas near the poles. You probably want to recreate your image using a narrower band of latitudes.
I'm trying to animate the rotation of my map markers when their values change using MapboxGL's data driven styling and the interpolate expression. Here is the relevant part of the layer config:
{
layout: {
'icon-rotate': ['interpolate', ['linear'], ['number', ['get', 'winddir'], 0], -180,-180, -90,-90, 0,0, 90, 90, 180, 180]
}
}
The winddir property will be a value between -180 & 180.
The markers appear on the map rotated correctly. However, when they change, they "snap" to the next position. I'm thinking I'm not using the "stops" correctly. Here are the interpolate docs.
As the documentation says:
Layout property. Optional number. Units in degrees. Defaults to 0. Requires icon-image. Supports interpolate expressions.
It does not include the magic term "transitionable". So, animation transitions are not applied to icon-rotate.
Can someone provide an explanation on how to use the Leaflet Marker Icon XY Coordinates listed here:
http://leafletjs.com/examples/custom-icons/
Is lng/lat directly mapped to x/y? For example, sometimes in game engines, the Y pixel increases in value, but goes down the page.
Here is it the same? I can't quite wrap my head around it.
Not exactly sure what you mean by "Is lng/lat directly mapped to x/y?", but here are some explanations that should talk enough:
(tile courtesy MapQuest)
As in most image manipulation software:
X increases from left to right
Y increases from top to bottom
When specifying iconAnchor and shadowAnchor for Leaflet custom icons, these directions still apply. Furthermore, like in most image software as well, the origin is the top left corner of your image.
var myIcon = L.icon({
iconUrl: 'path/to/image.png', // relative to your script location, or absolute
iconSize: [25, 41], // [x, y] in pixels
iconAnchor: [12, 41]
});
As explained in the doc, if you specify iconSize but not iconAnchor, Leaflet will assume your icon tip is at the center of your image and position it accordingly (same for shadow).
But if you do specify neither iconSize nor iconAnchor, Leaflet will position your icon image "as is", i.e. as if its tip was its top left corner. Then you can apply a className option and define it in CSS with negative left and top margins to re-position your image.
var myIcon = L.icon({
iconUrl: 'path/to/image.png',
// iconSize: [25, 41],
// iconAnchor: [12, 41], // [x, y]
className: 'custom-icon'
});
.custom-icon {
margin-left: -12px; /* -x */
margin-top: -41px; /* -y */
}
This usage might be more interesting when using a DivIcon, for which you may not know the size in advance, and use CSS transforms to position it.
As for the popupAnchor, it uses the icon tip as origin, so you will most likely specify a negative y value, so that the popup appears above the icon.
popupAnchor: [1, -34] // [x, y]
Finally when adjusting your anchor values, a useful trick is to add a normal default marker at the exact same Lat/Lng location as the marker with your custom icon, so that you can compare both icon tip positions easily.
I have a shapefile with a transverse mercator projection. It also has false northings/eastings values associated with it in ArcGIS.
I am trying to map the polygons onto a Europe map, but they are not showing up on it. When I use worldmap world the polygons show up in Africa, even though they are supposed to be in Ireland.
How can I plot my polygons in the right place? They have latitudes and longitudes associated with them, so I think it has to be the projection.
S = shaperead(polygons.shp','UseGeoCoords', true)
h = axesm('tranmerc','FalseEasting',60000,'FalseNorthing',750000,'Grid','on','Frame','on', 'MlabelParallel',0,'PlabelMeridian',0)
When I try to plot the latitudes and longitudes, they end up in Africa:
h = plot([S.Lat],[S.Lon], 'k:');
Or when I try and use geoshow they fill up the map completely with a color:
geoshow(S,'FaceColor',[1 1 .5],'EdgeColor',[.6 .6 .6]);