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.