58

我正在将地图从使用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 目前是否有一种方法可以根据距离和缩放比例来渲染一个带有圆形(或多个)的图层?

我目前正在使用 Mapbox GL 的 v0.19.0。

4

9 回答 9

85

我已经通过使用 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);

输出如下所示:

示例图像

于 2016-08-17T21:14:01.467 回答
50

详细说明卢卡斯的回答,我想出了一种估计参数的方法,以便根据某个公制大小绘制一个圆。

地图支持 0 到 20 之间的缩放级别。假设我们将半径定义如下:

"circle-radius": {
  stops: [
    [0, 0],
    [20, RADIUS]
  ],
  base: 2
}

由于我们为最小缩放级别 (0) 和最大缩放级别 (20) 定义了一个值,因此地图将在所有缩放级别上渲染圆。对于介于两者之间的所有缩放级别,它的半径为 (大约) RADIUS/2^(20-zoom)。因此,如果我们设置RADIUS为与我们的度量值匹配的正确像素大小,我们将获得所有缩放级别的正确半径。

所以我们基本上是在将米转换为缩放级别 20 的像素大小的转换因子之后。当然,这个因子取决于纬度。如果我们在最大缩放级别 20 处测量赤道处水平线的长度并除以这条线跨越的像素数,我们得到一个因子 ~0.075m/px(米/像素)。应用 的墨卡托纬度比例因子1 / cos(phi),我们得到任何纬度的正确米像素比:

const metersToPixelsAtMaxZoom = (meters, latitude) =>
  meters / 0.075 / Math.cos(latitude * Math.PI / 180)

因此,设置RADIUSmetersToPixelsAtMaxZoom(radiusInMeters, latitude)让我们得到一个大小正确的圆圈:

"circle-radius": {
  stops: [
    [0, 0],
    [20, metersToPixelsAtMaxZoom(radiusInMeters, latitude)]
  ],
  base: 2
}
于 2016-06-13T16:00:35.580 回答
12

虽然所有的答案都很复杂,但这里是最简单的答案

文档

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

演示链接

结果

在此处输入图像描述

于 2021-01-13T13:31:45.580 回答
7

使用@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,
      },
    });


});

重要的提示

在这种情况下使用mapboxgl v1如果你使用mapboxgl v2你会得到一个错误

**Uncaught ReferenceError: _createClass is not defined**

要解决此错误,您必须使用以下方法 https://github.com/mapbox/mapbox-gl-js-docs/blob/6d91ce00e7e1b2495872dac969e497366befb7d7/docs/pages/api/index.md#transpiling-v2

于 2021-01-18T20:27:06.093 回答
4

扩展@fphilipe 的答案并跟进评论:-

Mapbox 使用正确表达的方式是

'circle-radius': [
  'interpolate',
  ['exponential', 2],
  ['zoom'],
  0, 0,
  20, [
    '/',
    ['/', meters, 0.075],
    ['cos', ['*', ['get', 'lat'], ['/', Math.PI, 180]]],
  ],
],

这假定您的要素属性包含纬度作为名为“lat”的标签。你只需要替换meters变量。

另外:为了提高精度,建议在停止中包含缩放级别,我尝试了以下代码,但由于某种原因它不起作用。没有抛出错误,但圆半径不准确。

'circle-radius': [
  'interpolate',
  ['exponential', 2],
  ['zoom'],
  0, 0,
  20, [
    '/',
    ['/', meters, ['/', 78271.484, ['^', 2, ['zoom']]]],
    ['cos', ['*', ['get', 'lat'], ['/', Math.PI, 180]]],
  ],
]

如果有人知道这一点,请发表评论(不使用视口信息和状态管理动态传递缩放级别)。很抱歉没有将此作为后续评论发布。谢谢!

于 2021-12-23T06:29:41.647 回答
4

此功能未内置在 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>

重要警告:

  • 确定特定实际测量的函数参数并不简单。它们随着要素的经度/纬度而变化。
  • 由于平铺数据的性质以及我们为 WebGL 打包数据的方式,大于 1024 像素的圆圈无法正确渲染
于 2016-06-02T20:30:49.657 回答
3

我找到了这个MapboxCircle模块

你只需要导入脚本

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

并打印你的圈子

var myCircle = new MapboxCircle({lat: 39.984, lng: -75.343}, 25000, {
    editable: true,
    minRadius: 1500,
    fillColor: '#29AB87'
}).addTo(myMapboxGlMap);
于 2021-02-04T05:32:41.720 回答
0

卢卡斯和 fphilipe 的答案完美无缺!对于那些使用 react-native-mapbox 并在地图上绘图人,您必须考虑屏幕的像素密度,如下所示:

  pixelValue(latitude: number, meters: number, zoomLevel: number) {
    const mapPixels = meters / (78271.484 / 2 ** zoomLevel) / Math.cos((latitude * Math.PI) / 180);
    const screenPixel = mapPixels * Math.floor(PixelRatio.get());
    return screenPixel;
  }
于 2020-01-17T09:16:30.370 回答
0

Credits 属于 @Brad Dwyer,这是他的解决方案的 Ruby 版本:

def createGeoJSONCircle(coordinates, radius = 2, points = 64)
  coordinate = {
    longitude: coordinates.first[0].to_f,
    latitude: coordinates.first[1].to_f,
  }

  ret = []
  # radius is set in kilometers
  distanceX = radius / (111.320 * Math.cos(coordinate[:latitude] * Math::PI / 180))
  distanceY = radius / 110.574

  for i in 0..points
    theta = (i.to_f / points.to_f) * (2 * Math::PI)
    x = distanceX * Math.cos(theta)
    y = distanceY * Math.sin(theta)

    ret << [(coordinate[:longitude] + x).to_s, (coordinate[:latitude] + y).to_s]
  end
  ret << ret.first
  ret
end
于 2021-03-07T09:18:38.900 回答