[google-maps] Google Maps V3-특정 범위의 확대 / 축소 수준을 계산하는 방법

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



답변

Google 그룹에서도 비슷한 질문이 있습니다. http://groups.google.com/group/google-maps-js-api-v3/browse_thread/thread/e6448fc197c3c892

확대 / 축소 수준은 개별 단계이며 각 단계에서 배율이 두 배가됩니다. 따라서 일반적으로 특정 맵 크기로 운이 좋지 않은 한 정확하게 원하는 범위에 맞출 수 없습니다.

또 다른 문제는 측면 길이 사이의 비율입니다. 예를 들어 사각형 맵 내부의 얇은 사각형에 경계를 정확하게 맞출 수 없습니다.

정확한 범위를 맞추는 방법에 대한 쉬운 대답은 없습니다.지도 div의 크기를 기꺼이 변경하더라도 변경할 크기와 해당 줌 레벨을 선택해야하기 때문에 (대략 말하면 크게 또는 작게 만들까요) 현재보다?).

확대 / 축소를 저장하지 않고 계산해야하는 경우 다음과 같은 트릭을 수행해야합니다.

메르카토르 투영법은 위도를 왜곡하지만 경도의 차이는 항상지도 너비의 동일한 비율 (각도 / 360도)을 나타냅니다. 확대 / 축소 0에서 전 세계지도는 256×256 픽셀이며 각 수준을 확대 / 축소하면 너비와 높이가 두 배가됩니다. 약간의 대수 후에 맵의 너비를 픽셀 단위로 알고 있다면 다음과 같이 줌을 계산할 수 있습니다. 경도가 둘러싸 기 때문에 각도가 양수인지 확인해야합니다.

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


답변

그의 대답에 대해 Giles Gardam에게 감사하지만 위도가 아닌 경도 만 다룹니다. 완벽한 솔루션은 위도에 필요한 확대 / 축소 수준과 경도에 필요한 확대 / 축소 수준을 계산 한 다음 둘 중 더 작은 값을 가져와야합니다.

위도와 경도를 모두 사용하는 함수는 다음과 같습니다.

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. 높은 줌 레벨마다 동일한 영역을 표시하는 데 필요한 픽셀 수는 너비와 높이가 두 배가됩니다. ( 참고 )
  4. 맵은 세로 방향으로 랩핑하지만 위도 방향으로는 랩핑하지 않습니다.

답변

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


답변

다음은 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)
    }


답변

덕분에 폴리 라인을 올바르게 표시하는 데 가장 적합한 줌 배율을 찾는 데 많은 도움이되었습니다. 추적해야 할 점 중 최대 및 최소 좌표를 찾고 경로가 매우 “수직”인 경우 몇 줄의 코드를 추가했습니다.

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입니다.


답변

다른 모든 대답은 하나 또는 다른 상황 세트 (지도 너비 / 높이, 경계 너비 / 높이 등)에 문제가있는 것처럼 보이므로 여기에 대답을 넣을 것이라고 생각했습니다 …

여기에 매우 유용한 자바 스크립트 파일이 있습니다 : 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 ).


답변

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;

델타가 있습니다. 왜냐하면 높이보다 너비가 더 큽니다.