Destringing Geojson Element for Data Driven Circle Radius - mapbox

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.

Related

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

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)

vue-chartjs separate x-axis from values

I have an Vue2 app where users capture mileages for their vehicles. I then plot these values on a linegraph using vue-chartjs:
These values are added as follows:
let chartData = {
labels: [],
datasets: [
{
label: "Total Mileage",
backgroundColor: "#ed3237",
data: [],
},
{
label: "Mileage Increase",
backgroundColor: "#333",
data: [],
},
],
};
...
chartData.labels.push(dateHelper.formatDateShort(hist.dateCreated));
chartData.datasets[0].data.push(hist.vehicleMileage);
chartData.datasets[1].data.push(mileageIncrease);
The problem is, this graph does not paint a very accurate picture as the x-axis values are evenly spaced with each dataset value/label combination, even though the dates might not be (and almost certainly would not be) equal durations from each other.
What I think would make the graph more accurate is if I can add a vertical line for exactly every month and then have the points of the line plotted to be correct in relation to these months. I've recreated this in Excel using the same data:
How do I have a custom x-axis lines and labels like that, but still draw my line with the points being correct in relation to this x-acis?

PostGIS: Linestring length

I am using PostGIS to calculate length of a user-defined linestring. The column is defined as geography(LineString,4326).
The linestring is represented by this GeoJSON:
"track": {
"type": "LineString",
"coordinates": [
[
49.364325571013,
16.785549033597
],
[
49.363254969491,
16.642149334451
]
]
}
SELECT ST_Length("geography") FROM table; returns 15945.7486086962 but the length measured on Google Maps is ~10 km.
What am I doing wrong? How to measure the length to get the same value as from Google Maps?
I believe it is the classic issue of switching x,y positions.
Considering x,y:
SELECT
ST_Length(
ST_GeogFromText('SRID=4326;LINESTRING(49.364325571013 16.785549033597,49.363254969491 16.642149334451)'),true);
st_length
------------------
15869.9069442778
and the "same" LineString switching to y,x ..
SELECT
ST_Length(
ST_GeogFromText('SRID=4326;LINESTRING(16.785549033597 49.364325571013,16.642149334451 49.363254969491)'),true)
st_length
------------------
10416.8606521809
Figured out the problem.
I was using ST_GeomFromText('LINESTRING(lat lon, lat lon)') to create the line. The correct order is lon lat, so ST_GeomFromText('LINESTRING(lon lat, lon lat)').
GeoJSON uses the same order of coordinates: 49.363254969491 = latitude, 16.642149334451 = longitude.
The reason I didn't realize this was because I used Leaflet to draw the line. I basically took the points by geoJSON.getLayers()[0].feature.geometry.coordinates and passed them to a Polyline object. It created the Polyline points by taking the first coordinate from the GeoJSON as Lat, the second as Lng. This way it got reversed the second time and got rendered correctly.
So after switching lon lat in the query I had to use L.GeoJSON.coordsToLatLngs() function to correctly render the line.

Leaflet: inverse/reverse polygon style

I have a province boundary polygon in geojson format. What I want is to show this province on the map with the non-province area greyed out (opaque) and the province area shown without style. How can I achieve this?
From the Polygon api documentation:
You can also create a polygon with holes by passing an array of arrays of latlngs, with the first latlngs array representing the exterior ring while the remaining represent the holes inside.
So I think you can use that to make a really big polygon for the exterior ring, with the province cut out in as a hole inside. Of course, if you zoom to a level bigger than the big polygon, that's gonna look awkward. Perhaps best set a min zoom level so that this won't happen.
var polygon = L.polygon(
[[[52, -1],
[52, 1],
[50, 1],
[50, -1]], //outer ring
[[51.509, -0.08],
[51.503, -0.07],
[51.51, -0.047]]] // cutout
).addTo(map);
JSfiddle
Or set the outer polygon to cover the entire world:
[[90, -180],
[90, 180],
[-90, 180],
[-90, -180]]

Efficient way to match a point against regions

I have a list of circular regions identified by the centre (latitude, longitude) and a radius. And I have a list of points on the earth identified by their latitude and longitude. For each point, I want to find the regions that it comes under. Please note, both lists can run into millions.
What is the most efficient way of doing this?
Regards!
MongoDB has built in support for bounds queries, so you can look for points within a given shape. Circles (center + radius) are one of the supported types. So, assuming you have a geospatial index defined on a location field (db.places.ensureIndex( { loc : "2d" } )or similar), you can do something like this:
> center = [50, 50]
> radius = 10
> db.places.find({"loc" : {"$within" : {"$center" : [center, radius]}}})
More info to be found here:
http://www.mongodb.org/display/DOCS/Geospatial+Indexing#GeospatialIndexing-BoundsQueries