How can I fit a circle (ideally an ellipse) to latitude/longitude scatter points in Cartopy? - coordinates

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)

Related

Destringing Geojson Element for Data Driven Circle Radius

When I upload my geojson as a tileset into Mapbox, all fields are imported as strings. Suppose I want circles to be larger for coordinates visited more often (# of visits ranging from 10-10,000). The code I use is below. But because "visits" was imported as a string, all coordinates are plotted with the same circle size on my map.
'circle-radius': ["sqrt", ["/", ["get", "visits"], 10000] ],
How do I destring "visits" so that circle radius can vary? Thanks!
That's simple, use a to-number expression to convert your strings:
[
"sqrt",
[
"/",
["to-number", ["get", "visits"]],
10000
]
]
See the documentation here: https://www.mapbox.com/mapbox-gl-js/style-spec#expressions-types-to-number
But as Andrew suggests below: You should probably find a way to conserve the numbers when importing as a tileset.

how to generate a heatmap in ipyleaflet

I have multiple coordinates (latitude and longitude) and I would like to create a heatmap. I have checked all the documentation online and examples and cannot find anything which helps my to create a heatmap on an ipyleaflet map.
Please could someone advise how I generate and add a heatmap layer onto an ipyleaflet map.
I am working inside a jupyter notebook.
Thanks
Since the last version of ipyleaflet it is now possible to create a HeatMap:
from ipyleaflet import Map, Heatmap
from random import uniform
m = Map(center=[0, 0], zoom=2)
locations = [
[uniform(-80, 80), uniform(-180, 180), uniform(0, 1000)] # lat, lng, intensity
for i in range(1000)
]
heat = Heatmap(locations=locations, radius=20, blur=10)
m.add_layer(heat)
# Change some attributes of the heatmap
heat.radius = 30
heat.blur = 50
heat.max = 0.5
heat.gradient = {0.4: 'red', 0.6: 'yellow', 0.7: 'lime', 0.8: 'cyan', 1.0: 'blue'}
m

How to convert from the image coordinates to Cartesian coordinates

I have this 3D image generated from the simple code below.
% Input Image size
imageSizeY = 200;
imageSizeX = 120;
imageSizeZ = 100;
%# create coordinates
[rowsInImage, columnsInImage, pagesInImage] = meshgrid(1:imageSizeY, 1:imageSizeX, 1:imageSizeZ);
%# get coordinate array of vertices
vertexCoords = [rowsInImage(:), columnsInImage(:), pagesInImage(:)];
centerY = imageSizeY/2;
centerX = imageSizeX/2;
centerZ = imageSizeZ/2;
radius = 28;
%# calculate distance from center of the cube
sphereVoxels = (rowsInImage - centerY).^2 + (columnsInImage - centerX).^2 + (pagesInImage - centerZ).^2 <= radius.^2;
%# Now, display it using an isosurface and a patch
fv = isosurface(sphereVoxels,0);
patch(fv,'FaceColor',[0 0 .7],'EdgeColor',[0 0 1]); title('Binary volume of a sphere');
view(45,45);
axis equal;
grid on;
xlabel('x-axis [pixels]'); ylabel('y-axis [pixels]'); zlabel('z-axis [pixels]')
I have tried plotting the image with isosurface and some other volume visualization tools, but there remains quite a few surprises for me from the plots.
The code has been written to conform to the image coordinate system (eg. see: vertexCoords) which is a left-handed coordinate system I presume. Nonetheless, the image is displayed in the Cartesian (right-handed) coordinate system. I have tried to see this displayed as the figure below, but that’s simply not happening.
I am wondering if the visualization functions have been written to display the image the way they do.
Image coordinate system:
Going forward, there are other aspects of the code I am to write for example if I have an input image sphereVoxels as in above, in addition to visualizing it, I would want to find north, south east, west, top and bottom locations in the image, as well as number and count the coordinates of the vertices, plus more.
I foresee this would likely become confusing for me if I don’t stick to one coordinate system, and considering that the visualization tools predominantly use the right-hand coordinate system, I would want to stick with that from the onset. However, I really do not know how to go about this.
Right-hand coordinate system:
Any suggestions to get through this?
When you call meshgrid, the dimensions x and y axes are switched (contrary to ndgrid). For example, in your case, it means that rowsInImage is a [120x100x200] = [x,y,z] array and not a [100x120x200] = [y,x,z] array even if meshgrid was called with arguments in the y,x,z order. I would change those two lines to be in the classical x,y,z order :
[columnsInImage, rowsInImage, pagesInImage] = meshgrid(1:imageSizeX, 1:imageSizeY, 1:imageSizeZ);
vertexCoords = [columnsInImage(:), rowsInImage(:), pagesInImage(:)];

How do I map/plot my polygon onto a Europe map in MATLAB?

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]);

How to interpolate ECEF coordinates on an WGS84 ellipsoid

Is there a direct method (not involving converting the coordinates to lat/lon) to interpolate between 2 ECEF coordinates (xyz) in order for the interpolated point to be located on the WGS84 ellispoid. The original 2 points are computed from geodetic coordinates.
Interpolating on a sphere seem obvious but I can't seem to derive a solution for the ellipsoid.
Thank you in advance.
Let assume you got 2 points p0(x,y,z) and p1(x,y,z) and want to interpolate some p(t) where t=<0.0,1.0> between the two.
you can:
rescale your ellipsoid to sphere
simply like this:
const double mz=6378137.00000/6356752.31414; // [m] equatoreal/polar radius of Earth
p0.z*=mz;
p1.z*=mz;
now you got Cartesian coordinates refering to spherical Earth model.
interpolate
simple linear interpolation would do
p(t) = p0+(p1-p0)*t
but of coarse you also need to normalize to earth curvature so:
r0 = |p0|
r1 = |p1|
p(t) = p0+(p1-p0)*t
r(t) = r0+(r1-r0)*t
p(t)*=r/|p(t)|
where |p0| means length of vector p0.
rescale back to ellipsoid
by dividing with the same value
p(t).z/=mz
This is simple and cheap but the interpolated path will not have linear time scale.
Here C++ example:
void XYZ_interpolate(double *pt,double *p0,double *p1,double t)
{
const double mz=6378137.00000/6356752.31414;
const double _mz=6356752.31414/6378137.00000;
double p[3],r,r0,r1;
// compute spherical radiuses of input points
r0=sqrt((p0[0]*p0[0])+(p0[1]*p0[1])+(p0[2]*p0[2]*mz*mz));
r1=sqrt((p1[0]*p1[0])+(p1[1]*p1[1])+(p1[2]*p1[2]*mz*mz));
// linear interpolation
r = r0 +(r1 -r0 )*t;
p[0]= p0[0]+(p1[0]-p0[0])*t;
p[1]= p0[1]+(p1[1]-p0[1])*t;
p[2]=(p0[2]+(p1[2]-p0[2])*t)*mz;
// correct radius and rescale back
r/=sqrt((p[0]*p[0])+(p[1]*p[1])+(p[2]*p[2]));
pt[0]=p[0]*r;
pt[1]=p[1]*r;
pt[2]=p[2]*r*_mz;
}
And preview:
Yellow squares are the used p0,p1 Cartesian coordinates, the White curve is the interpolated path where t=<0.0,1.0> ...