Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Drawing a circle with the radius in miles/meters with Mapbox GL JS

Tags:

mapbox-gl-js

People also ask

Is Mapbox GL JS free?

Is Mapbox GL JS open source? The previous version, Mapbox GL JS v1, is indeed a free and open-source, BSD3 licensed software. Anyone can contribute to it, or start their own derived work based on it, for free. As of February 2021, the v1 source code is still available from the Mapbox Github.


I've solved this problem for my use cases by using a GeoJSON polygon. It's not strictly a circle but by increasing the number of sides on the polygon you can get pretty close.

The added benefit to this method is that it will correctly change its pitch, size, bearing, etc with the map automatically.

Here is the function to generate the GeoJSON Polygon

var createGeoJSONCircle = function(center, radiusInKm, points) {
    if(!points) points = 64;

    var coords = {
        latitude: center[1],
        longitude: center[0]
    };

    var km = radiusInKm;

    var ret = [];
    var distanceX = km/(111.320*Math.cos(coords.latitude*Math.PI/180));
    var distanceY = km/110.574;

    var theta, x, y;
    for(var i=0; i<points; i++) {
        theta = (i/points)*(2*Math.PI);
        x = distanceX*Math.cos(theta);
        y = distanceY*Math.sin(theta);

        ret.push([coords.longitude+x, coords.latitude+y]);
    }
    ret.push(ret[0]);

    return {
        "type": "geojson",
        "data": {
            "type": "FeatureCollection",
            "features": [{
                "type": "Feature",
                "geometry": {
                    "type": "Polygon",
                    "coordinates": [ret]
                }
            }]
        }
    };
};

You can use it like this:

map.addSource("polygon", createGeoJSONCircle([-93.6248586, 41.58527859], 0.5));

map.addLayer({
    "id": "polygon",
    "type": "fill",
    "source": "polygon",
    "layout": {},
    "paint": {
        "fill-color": "blue",
        "fill-opacity": 0.6
    }
});

If you need to update the circle you created later you can do it like this (note the need to grab the data property to pass to setData):

map.getSource('polygon').setData(createGeoJSONCircle([-93.6248586, 41.58527859], 1).data);

And the output looks like this:

Example Image


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
}

Although all answers are complicated, here is the simplest answer

docs

var center = [84.82512804700335, 26.241818082937552];
var radius = 5;
var options = {steps: 50, units: 'kilometers', properties: {foo: 'bar'}};
var circle = turf.circle(center, radius, options);

DEMO LINK

result

enter image description here


The simple way using @turf/turf

import * as turf from "@turf/turf";
import mapboxgl from "mapbox-gl";

map.on('load', function(){
let _center = turf.point([longitude, latitude]);
let _radius = 25;
let _options = {
  steps: 80,
  units: 'kilometers' // or "mile"
};

let _circle = turf.circle(_center, _radius, _options);

map.addSource("circleData", {
      type: "geojson",
      data: _circle,
    });

map.addLayer({
      id: "circle-fill",
      type: "fill",
      source: "circleData",
      paint: {
        "fill-color": "yellow",
        "fill-opacity": 0.2,
      },
    });


});

Important note

In this case used mapboxgl v1 If you using mapboxgl v2 You get an error

**Uncaught ReferenceError: _createClass is not defined**

To solve this error, you must use the following method https://github.com/mapbox/mapbox-gl-js-docs/blob/6d91ce00e7e1b2495872dac969e497366befb7d7/docs/pages/api/index.md#transpiling-v2


This functionality is not built into GL JS but you can emulate it using functions.

<!DOCTYPE html>
<html>

<head>
  <meta charset='utf-8' />
  <title></title>
  <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
  <script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.19.0/mapbox-gl.js'></script>
  <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.19.0/mapbox-gl.css' rel='stylesheet' />
  <style>
    body {
      margin: 0;
      padding: 0;
    }
    #map {
      position: absolute;
      top: 0;
      bottom: 0;
      width: 100%;
    }
  </style>
</head>

<body>

  <div id='map'></div>
  <script>
    mapboxgl.accessToken = 'pk.eyJ1IjoibHVjYXN3b2oiLCJhIjoiNWtUX3JhdyJ9.WtCTtw6n20XV2DwwJHkGqQ';
    var map = new mapboxgl.Map({
      container: 'map',
      style: 'mapbox://styles/mapbox/streets-v8',
      center: [-74.50, 40],
      zoom: 9,
      minZoom: 5,
      maxZoom: 15
    });

    map.on('load', function() {
      map.addSource("source_circle_500", {
        "type": "geojson",
        "data": {
          "type": "FeatureCollection",
          "features": [{
            "type": "Feature",
            "geometry": {
              "type": "Point",
              "coordinates": [-74.50, 40]
            }
          }]
        }
      });

      map.addLayer({
        "id": "circle500",
        "type": "circle",
        "source": "source_circle_500",
        "paint": {
          "circle-radius": {
            stops: [
              [5, 1],
              [15, 1024]
            ],
            base: 2
          },
          "circle-color": "red",
          "circle-opacity": 0.6
        }
      });
    });
  </script>

</body>

</html>

Important Caveats:

  • Determining the function parameters for a particular real-world measurement isn't straightforward. They change with the longitude / latitude of the feature.
  • Circles larger than 1024px aren't going to render properly due to the nature of tiled data and the way we pack data for WebGL

I found this MapboxCircle module

You only need to import the script

<script src='https://npmcdn.com/mapbox-gl-circle/dist/mapbox-gl-circle.min.js'></script>

And print your circle

var myCircle = new MapboxCircle({lat: 39.984, lng: -75.343}, 25000, {
    editable: true,
    minRadius: 1500,
    fillColor: '#29AB87'
}).addTo(myMapboxGlMap);