web-dev-qa-db-ja.com

Mapbox GL JSでマイル/メートルの半径で円を描く

私はmapbox.jsmapbox-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を使用しています。

25
jrrdnx

Lucas 'answer について詳しく説明すると、特定のメトリックサイズに基づいて円を描くためにパラメーターを推定する方法を思いつきました。

マップは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
}
_
29
fphilipe

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

後で作成したサークルを更新する必要がある場合は、次のようにできます(setDataに渡すdataプロパティを取得する必要があることに注意してください)。

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

出力は次のようになります。

Example Image

60
Brad Dwyer

この機能はGL JSには組み込まれていませんが、 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>

重要な注意事項:

  • 特定の実世界の測定値の関数パラメーターを決定するのは簡単ではありません。フィーチャの経度/緯度に応じて変化します。
  • タイル化されたデータの性質とWebGL用にデータをパックする方法により、1024pxより大きい円は適切にレンダリングされません。
3