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


138

getBoundsZoomLevel()V2 API と同様に、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

4
最後の例は非常に優れており、非常にわかりやすくなっています。+1。私が持っている同じ問題を
TMS

申し訳ありませんが、間違った質問をリンクしましたこれは正しいリンクです。
TMS

この質問は他の質問と重複していません。他の質問への答えは使用することfitBounds()です。この質問fitBounds()は、不十分な場合にどうするかを尋ねます。ズームを超えているか、ズームしたくない(つまり、ズームレベルが必要な)ためです。
ジョンS

@ニック・クラーク:どうやってあなたはswを知っていますか?以前にどのようにそれらをキャプチャしましたか?
varaprakash 2014年

回答:


108

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

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

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

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

保存するのではなく、本当にズームを計算する必要がある場合は、これでうまくいきます:

メルカトル図法は緯度をゆがめますが、経度の違いは常にマップの幅の同じ割合を表します(度の角度の差/ 360)。ズーム0では、世界地図全体が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);

1
これを緯度で繰り返してから、2の結果の最小値を選択する必要はありませんか?これは高くて狭い境界線では機能しないと思います...
whiteatom 2012年

3
Math.roundをMath.floorに変更すると、私にとってはうまくいきます。どうもありがとう。
ピート

3
緯度が考慮されていない場合、これはどのように正しいのでしょうか?赤道付近では問題ないはずですが、特定のズームレベルでの地図の縮尺は緯度によって異なります。
Eyal

1
@Peteの良い点は、一般的には、ズームレベルを切り捨てて、マップに希望よりも少し多めに収めるようにすることです。OPの状況では、丸め前の値はほぼ整数であるため、Math.roundを使用しました。
Giles Gardam

20
pixelWidthの値は何ですか
albert Jegani

279

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

パラメーター:

「境界」パラメータ値は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度で実際のカットオフを計算します。)

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

その他の注意事項:

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

6
すばらしい回答です。経度と緯度の両方を考慮しているので、これがトップの投票になるはずです。これまでのところ完全に機能しました。
Peter Wooster 2013年

1
@John S-これは素晴らしい解決策であり、私も利用できるネイティブのGoogleマップのfitBoundsメソッドでこれを使用することを考えています。私はfitBoundsが1ズームレベル後(ズームアウト)になっていることに気づきましたが、それは追加されているパディングからのものであると思います。これとfitBoundsメソッドの唯一の違いは、2つのズームレベルの変化を説明するために追加するパディングの量だけですか?
johntrepreneur 2013年

1
@johntrepreneur-利点#1:マップを作成する前でもこの方法を使用できるので、その結果を初期マップ設定に提供できます。ではfitBounds、マップを作成してから、「bounds_changed」イベントを待つ必要があります。
John S

1
@MarianPaździoch-それはその境界で機能しますここを参照してください。これらのポイントがマップの正確な角になるようにズームできると思いますか?ズームレベルは整数値であるため、これは不可能です。この関数は、マップの境界全体を含む、最も高いズームレベルを返します。
John S

1
@CarlMeyer-私は答えでそれについて言及していませんが、上のコメントで、この関数の1つの利点は「マップを作成する前でもこのメソッドを使用できること」であると述べています。を使用map.getProjection()すると、一部の計算(および投影に関する仮定)が削除されますが、マップが作成されて "projection_changed"イベントが発生するまで、関数を呼び出すことができませんでした。
ジョンS

39

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

1
次のように、マップの初期化中に最小ズームレベルを設定しないでください。varmapOptions = {maxZoom:15、};
クッシュ14

2
@クッシュ、良い点。ただしmaxZoom、ユーザーが手動でズームすることはできません。私の例では、DefaultZoomを変更するのは、必要な場合のみです。
d.raev 2014

1
fitBoundsを実行すると、現在のビューからアニメーション化するのではなく、ジャンプして境界に合わせます。素晴らしい解決策は、すでに述べたものを使用することgetBoundsZoomLevelです。このようにして、setZoomを呼び出すと、目的のズームレベルにアニメーション化されます。そこからpanToを実行しても問題はありません。境界に収まる美しいマップアニメーションになります
user151496

アニメーションは質問でも私の回答でも議論されていません。トピックに関する有用な例がある場合は、例と、それをいつどのように使用できるかについて、建設的な答えを作成してください。
d.raev 2015

何らかの理由で、map.fitBounds()呼び出しの直後にsetZoom()を呼び出しても、Googleマップはズームしません。(gmapsは現在v3.25です)
kashiraja

4

ここに関数のKotlinバージョンがあります:

fun getBoundsZoomLevel(bounds: LatLngBounds, mapDim: Size): Double {
        val WORLD_DIM = Size(256, 256)
        val ZOOM_MAX = 21.toDouble();

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

        fun zoom(mapPx: Int, worldPx: Int, fraction: Double): Double {
            return floor(Math.log(mapPx / worldPx / fraction) / Math.log(2.0))
        }

        val ne = bounds.northeast;
        val sw = bounds.southwest;

        val latFraction = (latRad(ne.latitude) - latRad(sw.latitude)) / Math.PI;

        val lngDiff = ne.longitude - sw.longitude;
        val lngFraction = if (lngDiff < 0) { (lngDiff + 360) } else { (lngDiff / 360) }

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

        return minOf(latZoom, lngZoom, ZOOM_MAX)
    }

1

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

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です。


好きvar zoomfactor = Math.floor(Math.log(960 * 360 / angle / GLOBE_WIDTH) / Math.LN2)-1;です。それでも、非常に役立ちます。
Mojowen、2012年

1

他のすべての回答は、1つまたは別の一連の状況(マップの幅/高さ、境界の幅/高さなど)で問題があるようですので、ここに私の回答を入力すると思いました...

ここには非常に便利な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;
   }

};

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

オフセットがどこから来たのか疑問に思っている場合、どうやら268435456はズームレベル21(http://www.appelsiini.net/2008/11/introduction-to-marker-clustering-with-google -maps)。


0

バレリオは彼の解決策でほぼ正しいですが、いくつかの論理的な間違いがあります。

負の値に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

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

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

0

上の例で、react-google-mapsを使用して平均的なデフォルトの中心を見つけますES6

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

Giles Gardamの経度のズームレベルの計算は、私には問題なく機能します。緯度のズーム係数を計算する場合、これはうまく機能する簡単なソリューションです。

double minLat = ...;
double maxLat = ...;
double midAngle = (maxLat+minLat)/2;
//alpha is the non-negative angle distance of alpha and beta to midangle
double alpha  = maxLat-midAngle;
//Projection screen is orthogonal to vector with angle midAngle
//portion of horizontal scale:
double yPortion = Math.sin(alpha*Math.pi/180) / 2;
double latZoom = Math.log(mapSize.height / GLOBE_WIDTH / yPortion) / Math.ln2;

//return min (max zoom) of both zoom levels
double zoom = Math.min(lngZoom, latZoom);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.