I'm in the process of converting a map from using mapbox.js to mapbox-gl.js, and am having trouble drawing a circle that uses miles or meters for its radius instead of pixels. This particular circle is used to show the area for distance in any direction from a central point.
Previously I was able to use the following, which was then added to a layer group:
// 500 miles = 804672 meters
L.circle(L.latLng(41.0804, -85.1392), 804672, {
stroke: false,
fill: true,
fillOpacity: 0.6,
fillColor: "#5b94c6",
className: "circle_500"
});
The only documentation I've found to do this in Mapbox GL is the following:
map.addSource("source_circle_500", {
"type": "geojson",
"data": {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-85.1392, 41.0804]
}
}]
}
});
map.addLayer({
"id": "circle500",
"type": "circle",
"source": "source_circle_500",
"layout": {
"visibility": "none"
},
"paint": {
"circle-radius": 804672,
"circle-color": "#5b94c6",
"circle-opacity": 0.6
}
});
But this renders the circle in pixels, which does not scale with zoom. Is there currently a way with Mapbox GL to render a layer with a circle (or multiple) that's based on distance and scales with zoom?
I am currently using v0.19.0 of Mapbox GL.
Elaborating on Lucas' answer, I've come up with a way of estimating the parameters in order to draw a circle based on a certain metric size.
The map supports zoom levels between 0 and 20. Let's say we define the radius as follows:
"circle-radius": {
stops: [
[0, 0],
[20, RADIUS]
],
base: 2
}
The map is going to render the circle at all zoom levels since we defined a value for the smallest zoom level (0) and the largest (20). For all zoom levels in between it results in a radius of (approximately) RADIUS/2^(20-zoom)
. Thus, if we set RADIUS
to the correct pixel size that matches our metric value, we get the correct radius for all zoom levels.
So we're basically after a conversion factor that transforms meters to a pixel size at zoom level 20. Of course this factor depends on the latitude. If we measure the length of a horizontal line at the equator at the max zoom level 20 and divide by the number of pixels that this line spans, we get a factor ~0.075m/px (meters per pixel). Applying the mercator latitude scaling factor of 1 / cos(phi)
, we obtain the correct meter to pixel ratio for any latitude:
const metersToPixelsAtMaxZoom = (meters, latitude) =>
meters / 0.075 / Math.cos(latitude * Math.PI / 180)
Thus, setting RADIUS
to metersToPixelsAtMaxZoom(radiusInMeters, latitude)
gets us a circle with the correct size:
"circle-radius": {
stops: [
[0, 0],
[20, metersToPixelsAtMaxZoom(radiusInMeters, latitude)]
],
base: 2
}