development

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

big-blog 2020. 7. 2. 07:17
반응형

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에서 전 세계지도는 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);

그의 대답에 대해 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. Maps wrap in the longitudinal direction, but not in the latitudinal direction.

For version 3 of the API, this is simple and working:

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

and some optional tricks:

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

Thanks, that helped me a lot in finding the most suitable zoom factor to correctly display a polyline. I find the maximum and minimum coordinates among the points I have to track and, in case the path is very "vertical", I just added few lines of code:

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

Actually, the ideal zoom factor is zoomfactor-1.


Since all of the other answers seem to have issues for me with one or another set of circumstances (map width/height, bounds width/height, etc.) I figured I'd put my answer here...

There was a very useful javascript file here: http://www.polyarc.us/adjust.js

I used that as a base for this:

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

};

You can certainly clean this up or minify it if needed, but I kept the variable names long in an attempt to make it easier to understand.

If you are wondering where OFFSET came from, apparently 268435456 is half of earth's circumference in pixels at zoom level 21 (according to http://www.appelsiini.net/2008/11/introduction-to-marker-clustering-with-google-maps).


Valerio is almost right with his solution, but there is some logical mistake.

you must firstly check wether angle2 is bigger than angle, before adding 360 at a negative.

otherwise you always have a bigger value than angle

So the correct solution is:

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;

Delta is there, because i have a bigger width than height.


map.getBounds() is not momentary operation, so I use in similar case event handler. Here is my example in Coffeescript

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

Work example to find average default center with react-google-maps on 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>

참고URL : https://stackoverflow.com/questions/6048975/google-maps-v3-how-to-calculate-the-zoom-level-for-a-given-bounds

반응형