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()확대 / 축소를 초과하거나 확대하고 싶지 않기 때문에 (즉, 확대 / 축소 수준을 원하기 때문에) 불충분 한 경우 수행 할 작업을 묻습니다 .
John S

@Nick Clark : sw, ne 경계 설정을 어떻게 알 수 있습니까? 당신은 전에 그것을 어떻게 캡처 했습니까?
varaprakash

답변:


108

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

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

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

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

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

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

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

3
Math.round를 Math.floor로 변경하면 나에게 적합합니다. 정말 감사합니다.
Pete

3
위도를 고려하지 않은 경우 어떻게해야합니까? 적도 근처에서는 괜찮을 것입니다. 그러나 주어진 줌 레벨에서지도의 규모는 위도에 따라 변합니다!
Eyal

1
@Pete 좋은 점, 일반적으로 줌 레벨을 반올림하여 맵에서 원하는 것보다 조금 작게 맞추고 싶을 것입니다. OP의 상황에서 반올림하기 전의 값은 거의 정수이기 때문에 Math.round를 사용했습니다.
Giles Gardam

20
pixelWidth
albert Jegani

279

그의 대답에 대해 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. 맵은 세로 방향으로 랩핑하지만 위도 방향으로는 랩핑하지 않습니다.

6
훌륭한 대답은 경도와 위도를 모두 설명하기 때문에 가장 많이 투표되어야합니다. 지금까지 완벽하게 작동했습니다.
Peter Wooster

1
@ John S-이것은 환상적인 솔루션이며 나에게 제공되는 기본 Google지도 fitBounds 방법을 사용하여 이것을 고려하고 있습니다. fitBounds가 때로는 하나의 확대 / 축소 수준 (확대 / 축소) 인 것을 알았지 만 추가하는 패딩에서 나온 것으로 가정합니다. 이 방법과 fitBounds 방법의 유일한 차이점은 둘 사이의 확대 / 축소 수준 변경을 설명하는 패딩 크기입니다.
johntrepreneur

1
@johntrepreneur-장점 # 1 : 맵을 생성하기 전에이 방법을 사용하여 결과를 초기 맵 설정에 제공 할 수 있습니다. 와 함께 fitBounds, 당신은지도를 만든 다음 "한 bounds_changed"이벤트를 기다릴 필요가있다.
John S

1
@ MarianPaździoch-그것은 그 경계를 위해 작동합니다, 여기를 참조하십시오 . 해당 지점이지도의 정확한 모서리에 오도록 확대 할 수있을 것으로 기대하십니까? 확대 / 축소 수준이 정수 값이므로 불가능합니다. 이 함수는 여전히지도의 전체 경계를 포함 할 가장 높은 줌 레벨을 반환합니다.
John S

1
@CarlMeyer-내 대답에는 언급하지 않았지만 위의 의견 에서이 기능의 장점 중 하나는 "지도를 작성하기 전에이 방법을 사용할 수 있다는 것"이라고 말합니다. 를 사용 map.getProjection()하면 일부 수학 (및 투영에 대한 가정)을 제거 할 수 있지만,지도를 만들고 "projection_changed"이벤트가 시작될 때까지 함수를 호출 할 수 없습니다.
John 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
맵을 초기화하는 동안 최소 줌 레벨을 설정하지 않는 이유는 다음과 같습니다. var mapOptions = {maxZoom : 15,};
Kush

2
@Kush, 좋은 지적. 하지만 maxZoom에서 사용자 방지 할 수 수동 줌. 내 예제는 DefaultZoom 만 변경하고 필요한 경우에만 변경합니다.
d.raev

1
fitBounds를 수행하면 현재 뷰에서 애니메이션을 적용하는 대신 경계에 맞게 점프합니다. 가장 좋은 해결책은 이미 언급 한 것 getBoundsZoomLevel입니다. 이렇게하면 setZoom을 호출하면 원하는 확대 / 축소 수준으로 애니메이션이 적용됩니다. 거기에서 panTo를하는 것은 문제가되지 않으며 당신은 경계에 맞는 아름다운 맵 애니메이션으로 끝납니다
user151496

애니메이션 은 질문이나 대답에서 논의 된 곳이 아닙니다. 주제에 대한 유용한 예가있는 경우 예와 사용 방법 및시기와 함께 건설적인 답변을 작성하십시오.
d.raev

어떤 이유로 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 2016 년

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


0

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

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.