web-dev-qa-db-ja.com

Google Maps V3-特定の境界のズームレベルを計算する方法

V2 APIのgetBoundsZoomLevel()と同様に、Google Maps V3 APIを使用して特定の境界のズームレベルを計算する方法を探しています。

これが私がやりたいことです:

// These are exact bounds previously captured from the map object
var sw = new google.maps.LatLng(42.763479, -84.338918);
var ne = new google.maps.LatLng(42.679488, -84.524313);
var bounds = new google.maps.LatLngBounds(sw, ne);
var zoom = // do some magic to calculate the zoom level

// Set the map to these exact bounds
map.setCenter(bounds.getCenter());
map.setZoom(zoom);

// NOTE: fitBounds() will not work

残念ながら、特定のユースケースではfitBounds()メソッドを使用できません。マップ上のマーカーのフィッティングには適していますが、正確な境界の設定には適していません。 fitBounds()メソッドを使用できない理由の例を次に示します。

map.fitBounds(map.getBounds()); // not what you expect
127
Nick Clark

Googleグループでも同様の質問が行われています: http://groups.google.com/group/google-maps-js-api-v3/browse_thread/thread/e6448fc197c3c892

ズームレベルは離散的で、各ステップでスケールが2倍になります。そのため、一般的には、特定のマップサイズで非常に幸運でない限り、必要な境界を正確に合わせることができません。

別の問題は、辺の長さの比率です。正方形マップ内の細い長方形に境界を正確に合わせることができません。

マップdivのサイズを変更する場合でも、変更するサイズと対応するズームレベルを選択する必要があるため、正確な境界を合わせる方法について簡単な答えはありません(大まかに言って、拡大または縮小しますか?現在よりも?)。

ズームを保存するのではなく、本当に計算する必要がある場合は、次のトリックを実行する必要があります。

メルカトル図法は緯度をゆがめますが、経度の差は常にマップの幅の同じ割合を表します(度の角度差/ 360)。ズームゼロでは、世界地図全体が256x256ピクセルで、各レベルをズームすると幅と高さの両方が2倍になります。そのため、マップの幅がピクセル単位でわかっていれば、少し代数を加えた後、次のようにズームを計算できます。経度は折り返すため、角度が正であることを確認する必要があることに注意してください。

var GLOBE_WIDTH = 256; // a constant in Google's map projection
var west = sw.lng();
var east = ne.lng();
var angle = east - west;
if (angle < 0) {
  angle += 360;
}
var zoom = Math.round(Math.log(pixelWidth * 360 / angle / GLOBE_WIDTH) / Math.LN2);
101
Giles Gardam

答えをくれたGiles Gardamに感謝しますが、緯度ではなく経度のみを扱います。完全なソリューションでは、緯度に必要なズームレベルと経度に必要なズームレベルを計算してから、2つのうち小さい方(さらに外側)を取得する必要があります。

緯度と経度の両方を使用する関数は次のとおりです。

function getBoundsZoomLevel(bounds, mapDim) {
    var WORLD_DIM = { height: 256, width: 256 };
    var ZOOM_MAX = 21;

    function latRad(lat) {
        var sin = Math.sin(lat * Math.PI / 180);
        var radX2 = Math.log((1 + sin) / (1 - sin)) / 2;
        return Math.max(Math.min(radX2, Math.PI), -Math.PI) / 2;
    }

    function zoom(mapPx, worldPx, fraction) {
        return Math.floor(Math.log(mapPx / worldPx / fraction) / Math.LN2);
    }

    var ne = bounds.getNorthEast();
    var sw = bounds.getSouthWest();

    var latFraction = (latRad(ne.lat()) - latRad(sw.lat())) / Math.PI;

    var lngDiff = ne.lng() - sw.lng();
    var lngFraction = ((lngDiff < 0) ? (lngDiff + 360) : lngDiff) / 360;

    var latZoom = zoom(mapDim.height, WORLD_DIM.height, latFraction);
    var lngZoom = zoom(mapDim.width, WORLD_DIM.width, lngFraction);

    return Math.min(latZoom, lngZoom, ZOOM_MAX);
}

Demo on jsfiddle

パラメーター:

「bounds」パラメーター値はgoogle.maps.LatLngBoundsオブジェクトでなければなりません。

「mapDim」パラメーター値は、マップを表示するDOM要素の高さと幅を表す「height」および「width」プロパティを持つオブジェクトである必要があります。パディングを確実にする場合は、これらの値を小さくすることができます。つまり、境界内のマップマーカーがマップのエッジに近づきすぎないようにすることができます。

JQueryライブラリを使用している場合、mapDim値は次のように取得できます。

var $mapDiv = $('#mapElementId');
var mapDim = { height: $mapDiv.height(), width: $mapDiv.width() };

プロトタイプライブラリを使用している場合、mapDim値は次のように取得できます。

var mapDim = $('mapElementId').getDimensions();

戻り値:

戻り値は、境界全体を表示する最大ズームレベルです。この値は、0から最大ズームレベルまでの範囲になります。

最大ズームレベルは21です(Google Maps API v2の場合は19だったと思います)。


説明:

Googleマップはメルカトル図法を使用しています。メルカトル図法では、経度線は等間隔ですが、緯度線は等間隔ではありません。緯度の線の間の距離は、赤道から極に向かうにつれて増加します。実際、距離は極に到達するにつれて無限に向かう傾向があります。ただし、Googleマップの地図には、北緯約85度を超える、または南緯約-85度を下回る緯度は表示されません。 ( 参照 )(実際のカットオフは+/- 85.05112877980658度で計算します。)

これにより、境界の端数の計算は、経度よりも緯度の方が複雑になります。 Wikipediaの公式 を使用して、緯度分数を計算しました。これは、Googleマップで使用される投影法と一致すると仮定しています。結局のところ、上記のリンク先のGoogleマップドキュメントページには、同じWikipediaページへのリンクが含まれています。

その他の注意事項:

  1. ズームレベルの範囲は、0から最大ズームレベルまでです。ズームレベル0は、完全にズームアウトされたマップです。レベルを上げると、マップがさらに拡大されます。 ( 参照
  2. ズームレベル0では、256 x 256ピクセルの領域に全世界を表示できます。 ( 参照
  3. より高いズームレベルごとに、同じ領域を表示するために必要なピクセル数が幅と高さの両方で2倍になります。 ( 参照
  4. マップは縦方向にラップしますが、緯度方向にはラップしません。
249
John S

APIバージョン3の場合、これはシンプルで機能します。

var latlngList = [];
latlngList.Push(new google.maps.LatLng(lat, lng));

var bounds = new google.maps.LatLngBounds();
latlngList.each(function(n) {
    bounds.extend(n);
});

map.setCenter(bounds.getCenter()); //or use custom center
map.fitBounds(bounds);

そして、いくつかのオプションのトリック:

//remove one zoom level to ensure no marker is on the Edge.
map.setZoom(map.getZoom() - 1); 

// set a minimum zoom 
// if you got only 1 marker or all markers are on the same address map will be zoomed too much.
if(map.getZoom() > 15){
    map.setZoom(15);
}
34
d.raev

他のすべての答えには、状況(マップの幅/高さ、境界の幅/高さなど)の問題があるように見えるので、ここに答えを入れると思いました...

非常に便利なjavascriptファイルがここにありました: http://www.polyarc.us/adjust.js

これをベースとして使用しました。

var com = com || {};
com.local = com.local || {};
com.local.gmaps3 = com.local.gmaps3 || {};

com.local.gmaps3.CoordinateUtils = new function() {

   var OFFSET = 268435456;
   var RADIUS = OFFSET / Math.PI;

   /**
    * Gets the minimum zoom level that entirely contains the Lat/Lon bounding rectangle given.
    *
    * @param {google.maps.LatLngBounds} boundary the Lat/Lon bounding rectangle to be contained
    * @param {number} mapWidth the width of the map in pixels
    * @param {number} mapHeight the height of the map in pixels
    * @return {number} the minimum zoom level that entirely contains the given Lat/Lon rectangle boundary
    */
   this.getMinimumZoomLevelContainingBounds = function ( boundary, mapWidth, mapHeight ) {
      var zoomIndependentSouthWestPoint = latLonToZoomLevelIndependentPoint( boundary.getSouthWest() );
      var zoomIndependentNorthEastPoint = latLonToZoomLevelIndependentPoint( boundary.getNorthEast() );
      var zoomIndependentNorthWestPoint = { x: zoomIndependentSouthWestPoint.x, y: zoomIndependentNorthEastPoint.y };
      var zoomIndependentSouthEastPoint = { x: zoomIndependentNorthEastPoint.x, y: zoomIndependentSouthWestPoint.y };
      var zoomLevelDependentSouthEast, zoomLevelDependentNorthWest, zoomLevelWidth, zoomLevelHeight;
      for( var zoom = 21; zoom >= 0; --zoom ) {
         zoomLevelDependentSouthEast = zoomLevelIndependentPointToMapCanvasPoint( zoomIndependentSouthEastPoint, zoom );
         zoomLevelDependentNorthWest = zoomLevelIndependentPointToMapCanvasPoint( zoomIndependentNorthWestPoint, zoom );
         zoomLevelWidth = zoomLevelDependentSouthEast.x - zoomLevelDependentNorthWest.x;
         zoomLevelHeight = zoomLevelDependentSouthEast.y - zoomLevelDependentNorthWest.y;
         if( zoomLevelWidth <= mapWidth && zoomLevelHeight <= mapHeight )
            return zoom;
      }
      return 0;
   };

   function latLonToZoomLevelIndependentPoint ( latLon ) {
      return { x: lonToX( latLon.lng() ), y: latToY( latLon.lat() ) };
   }

   function zoomLevelIndependentPointToMapCanvasPoint ( point, zoomLevel ) {
      return {
         x: zoomLevelIndependentCoordinateToMapCanvasCoordinate( point.x, zoomLevel ),
         y: zoomLevelIndependentCoordinateToMapCanvasCoordinate( point.y, zoomLevel )
      };
   }

   function zoomLevelIndependentCoordinateToMapCanvasCoordinate ( coordinate, zoomLevel ) {
      return coordinate >> ( 21 - zoomLevel );
   }

   function latToY ( lat ) {
      return OFFSET - RADIUS * Math.log( ( 1 + Math.sin( lat * Math.PI / 180 ) ) / ( 1 - Math.sin( lat * Math.PI / 180 ) ) ) / 2;
   }

   function lonToX ( lon ) {
      return OFFSET + RADIUS * lon * Math.PI / 180;
   }

};

必要に応じて、これを確実にクリーンアップまたは縮小できますが、わかりやすくするために変数名は長くしました。

OFFSETがどこから来たのか疑問に思っているなら、どうやら268435456はズームレベル21でのピクセル単位での地球の円周の半分です( http://www.appelsiini.net/2008/11/introduction-to-marker-clusteringによると-with-google-maps )。

1
Shadow Man

おかげで、ポリラインを正しく表示するのに最適なズームファクターを見つけることができました。追跡する必要があるポイントの中で最大および最小の座標を見つけ、パスが非常に「垂直」である場合、コードを数行追加しました。

var GLOBE_WIDTH = 256; // a constant in Google's map projection
var west = <?php echo $minLng; ?>;
var east = <?php echo $maxLng; ?>;
*var north = <?php echo $maxLat; ?>;*
*var south = <?php echo $minLat; ?>;*
var angle = east - west;
if (angle < 0) {
    angle += 360;
}
*var angle2 = north - south;*
*if (angle2 > angle) angle = angle2;*
var zoomfactor = Math.round(Math.log(960 * 360 / angle / GLOBE_WIDTH) / Math.LN2);

実際、理想的なズーム率はzoomfactor-1です。

1
Valerio Giacosa

map.getBounds()は一時的な操作ではないため、同様の場合にイベントハンドラーを使用します。ここにCoffeescriptの私の例があります

@map.fitBounds(@bounds)
google.maps.event.addListenerOnce @map, 'bounds_changed', =>
  @map.setZoom(12) if @map.getZoom() > 12
0
MikDiet

Valerioは彼のソリューションにほぼ正しいですが、いくつかの論理的な間違いがあります。

マイナスで360を追加する前に、まずangle2がangleよりも大きいかどうかを確認する必要があります。

それ以外の場合は、常に角度よりも大きな値を持ちます

したがって、正しい解決策は次のとおりです。

var west = calculateMin(data.longitudes);
var east = calculateMax(data.longitudes);
var angle = east - west;
var north = calculateMax(data.latitudes);
var south = calculateMin(data.latitudes);
var angle2 = north - south;
var zoomfactor;
var delta = 0;
var horizontal = false;

if(angle2 > angle) {
    angle = angle2;
    delta = 3;
}

if (angle < 0) {
    angle += 360;
}

zoomfactor = Math.floor(Math.log(960 * 360 / angle / GLOBE_WIDTH) / Math.LN2) - 2 - delta;

私は高さが幅よりも大きいので、デルタがあります。

0
Lukas Olsen

ES6のreact-google-mapsを使用して平均のデフォルトセンターを見つけるための作業例:

const bounds = new google.maps.LatLngBounds();
paths.map((latLng) => bounds.extend(new google.maps.LatLng(latLng)));
const defaultCenter = bounds.getCenter();
<GoogleMap
 defaultZoom={paths.length ? 12 : 4}
 defaultCenter={defaultCenter}
>
 <Marker position={{ lat, lng }} />
</GoogleMap>
0
Gapur Kassym