Рисование круга с радиусом в милях/метрах с помощью Mapbox GL JS
Я собираюсь преобразовать карту с помощью mapbox.js в mapbox-gl.js, и у меня возникают проблемы с созданием круга, который использует мили или метров радиуса, а не пикселей. Этот конкретный круг используется для отображения области для расстояния в любом направлении от центральной точки.
Раньше я мог использовать следующее, которое затем было добавлено в группу слоев:
// 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"
});
Единственная документация, которую я нашел для этого в Mapbox GL, выглядит следующим образом:
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
}
});
Но это отображает круг в пикселях, который не масштабируется с увеличением. Есть ли способ Mapbox GL для отображения слоя с кругом (или несколькими), который основан на расстоянии и масштабах с увеличением?
В настоящее время я использую v0.19.0 из Mapbox GL.
Ответы
Ответ 1
Разрабатывая ответ Лукаса, я придумал способ оценки параметров, чтобы нарисовать круг, основанный на определенном размере метрики.
Карта поддерживает уровни масштабирования между 0 и 20. Пусть мы определяем радиус следующим образом:
"circle-radius": {
stops: [
[0, 0],
[20, RADIUS]
],
base: 2
}
Карта будет отображать круг на всех уровнях масштабирования, так как мы определили значение для наименьшего уровня масштабирования (0) и самого большого (20). Для всех уровней масштабирования между ними получается радиус (приблизительно) RADIUS/2^(20-zoom)
. Таким образом, если мы установим RADIUS
в правильный размер пикселя, который соответствует нашему метрическому значению, мы получим правильный радиус для всех уровней масштабирования.
Таким образом, мы в основном после коэффициента преобразования, который преобразует метры в пиксель на уровне масштабирования 20. Конечно, этот фактор зависит от широты. Если мы измеряем длину горизонтальной линии на экваторе при максимальном уровне масштабирования 20 и делим на количество пикселей, которое занимает эта линия, мы получаем коэффициент ~ 0,075 м/пикселей (метры на пиксель). Применяя масштабный коэффициент масштабирования меркатора 1 / cos(phi)
, мы получаем правильное отношение метра к пикселю для любой широты:
const metersToPixelsAtMaxZoom = (meters, latitude) =>
meters / 0.075 / Math.cos(latitude * Math.PI / 180)
Таким образом, установка RADIUS
в metersToPixelsAtMaxZoom(radiusInMeters, latitude)
дает нам круг с правильным размером:
"circle-radius": {
stops: [
[0, 0],
[20, metersToPixelsAtMaxZoom(radiusInMeters, latitude)]
],
base: 2
}
Ответ 2
Я решил эту проблему для своих случаев использования с использованием полигона GeoJSON. Это не строго круг, а путем увеличения количества сторон на многоугольнике вы можете получить довольно близко.
Дополнительным преимуществом этого метода является то, что он автоматически изменит свой шаг, размер, подшипник и т.д. с помощью карты автоматически.
Вот функция генерации многоугольника GeoJSON
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]
}
}]
}
};
};
Вы можете использовать его следующим образом:
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
}
});
Если вам нужно обновить созданный вами круг, вы можете сделать это так (обратите внимание на необходимость захватить свойство data
для передачи в setData):
map.getSource('polygon').setData(createGeoJSONCircle([-93.6248586, 41.58527859], 1).data);
И результат выглядит следующим образом:
![Пример изображения]()
Ответ 3
Эта функция не встроена в GL JS, но вы можете эмулировать ее с помощью функций.
<!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>