bestsource

Google Maps V3 - 주어진 경계에 대한 줌 레벨을 계산하는 방법

bestsource 2023. 10. 26. 21:18
반응형

Google Maps V3 - 주어진 경계에 대한 줌 레벨을 계산하는 방법

다음과 같이 Google Maps V3 API를 사용하여 주어진 경계에 대한 줌 레벨을 계산하는 방법을 찾고 있습니다.getBoundsZoomLevel()V2 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

자일스 가담의 답변에 감사드립니다만, 이는 경도만 다루고 위도는 다루지 않습니다.완전한 솔루션은 위도에 필요한 확대/축소 수준과 경도에 필요한 확대/축소 수준을 계산한 다음 둘 중 작은 것(더 멀리)을 취해야 합니다.

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

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 요소의 높이와 너비를 나타내는 "높이" 및 "폭" 속성을 가진 개체여야 합니다.패딩을 확인하려는 경우 이러한 값을 줄일 수 있습니다.즉, 경계 내의 지도 마커가 지도의 가장자리에 너무 가까이 있지 않도록 할 수 있습니다.

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개에 불과했던 것 같습니다.)


설명:

구글 지도는 Mercator 프로젝션을 사용합니다.메르카토르 투영법에서는 경도의 선은 같은 간격이지만 위도의 선은 그렇지 않습니다.위도선 사이의 거리는 적도에서 극으로 갈수록 늘어납니다.사실 거리는 극에 도달함에 따라 무한대를 향해 나아갑니다.그러나 Google 지도에는 북위 약 85도 이상 또는 남위 약 -85도 이하의 위도가 표시되지 않습니다. (참조) (실제 컷오프는 +/-85.0511287980658도에서 계산합니다.)

따라서 경도보다 위도의 경우 경계에 대한 분수 계산이 더 복잡해집니다.저는 위도 분율을 계산하기 위해 위키피디아의 공식을 사용했습니다.구글 지도에서 사용하는 프로젝션과 일치한다고 가정합니다.결국 제가 위에 링크한 구글 지도 문서 페이지에는 같은 위키피디아 페이지로 연결되는 링크가 포함되어 있습니다.

기타 참고 사항:

  1. 줌 레벨의 범위는 0부터 최대 줌 레벨까지입니다.Zoom level 0은 지도가 완전히 축소된 것입니다.레벨이 높을수록 지도를 더 확대합니다. (참조)
  2. 줌 레벨 0에서는 256 x 256 픽셀의 영역에 전 세계를 표시할 수 있습니다. (참조)
  3. 각 더 높은 줌 레벨에 대해 동일한 영역을 표시하는 데 필요한 픽셀 수는 너비와 높이 모두에서 두 배로 증가합니다. (참조).
  4. 지도는 세로 방향으로 감겨지지만, 세로 방향으로는 감겨지지 않습니다.

구글 그룹에도 비슷한 질문이 올라왔습니다: http://groups.google.com/group/google-maps-js-api-v3/browse_thread/thread/e6448fc197c3c892

확대/축소 수준은 개별적이며, 각 단계에서 스케일이 두 배로 증가합니다.따라서 일반적으로 특정 지도 크기에 대해 운이 좋은 경우가 아니라면 원하는 범위를 정확하게 맞출 수 없습니다.

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

맵 디브의 크기를 변경할 의사가 있더라도 어떤 크기와 해당 줌 레벨로 변경할지 선택해야 하기 때문에 정확한 경계를 맞추는 방법에 대한 쉬운 답은 없습니다(대략적으로 말하면 현재보다 더 크게 만드나요, 작게 만드나요?).

줌을 저장하는 것이 아니라 꼭 줌을 계산해야 하는 경우에는 다음과 같은 효과가 있습니다.

Mercator 투영은 위도를 왜곡하지만 경도의 차이는 항상 지도 너비의 동일한 부분(도/360의 각도 차이)을 나타냅니다.줌 제로에서는 전 세계 지도가 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);

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

다트 버전:

  double latRad(double lat) {
    final double sin = math.sin(lat * math.pi / 180);
    final double radX2 = math.log((1 + sin) / (1 - sin)) / 2;
    return math.max(math.min(radX2, math.pi), -math.pi) / 2;
  }

  double getMapBoundZoom(LatLngBounds bounds, double mapWidth, double mapHeight) {
    final LatLng northEast = bounds.northEast;
    final LatLng southWest = bounds.southWest;

    final double latFraction = (latRad(northEast.latitude) - latRad(southWest.latitude)) / math.pi;

    final double lngDiff = northEast.longitude - southWest.longitude;
    final double lngFraction = ((lngDiff < 0) ? (lngDiff + 360) : lngDiff) / 360;

    final double latZoom = (math.log(mapHeight / 256 / latFraction) / math.ln2).floorToDouble();
    final double lngZoom = (math.log(mapWidth / 256 / lngFraction) / math.ln2).floorToDouble();

    return math.min(latZoom, lngZoom);
  }

코틀린 버전의 함수는 다음과 같습니다.

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

높은 지지를 받은 어떤 답변도 제게 맞지 않았습니다.그들은 정의되지 않은 다양한 오류를 던져 결국 각도에 대한 inf/n을 계산했습니다.아마도 LatLngBounds의 행동이 시간이 지남에 따라 변화한 것 같습니다.어쨌든 저는 이 코드가 제 요구에 맞는다는 것을 알게 되었습니다. 아마도 누군가에게 도움이 될 것입니다.

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 getZoom(lat_a, lng_a, lat_b, lng_b) {

      let latDif = Math.abs(latRad(lat_a) - latRad(lat_b))
      let lngDif = Math.abs(lng_a - lng_b)

      let latFrac = latDif / Math.PI 
      let lngFrac = lngDif / 360 

      let lngZoom = Math.log(1/latFrac) / Math.log(2)
      let latZoom = Math.log(1/lngFrac) / Math.log(2)

      return Math.min(lngZoom, latZoom)

}

감사합니다, 폴리라인을 정확하게 표시할 수 있는 가장 적합한 줌 팩터를 찾는 데 많은 도움이 되었습니다.추적해야 할 점들 중에서 최대 및 최소 좌표를 찾고 경로가 매우 "수직"인 경우를 대비하여 몇 줄의 코드를 추가했습니다.

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

실제로 이상적인 줌 팩터는 줌 팩터-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) 에 따르면).

발레리오의 해결책은 거의 맞지만 논리적인 실수가 있습니다.

먼저 각도2가 각도보다 큰지 확인하고 음의 값으로 360을 추가해야 합니다.

그렇지 않으면 당신은 항상 각도보다 더 큰 값을 가집니다.

따라서 올바른 솔루션은 다음과 같습니다.

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;

델타는 제가 키보다 큰 폭을 가지고 있기 때문에 거기에 있습니다.

map.getBounds()순간적인 동작이 아니기 때문에 유사한 경우의 이벤트 핸들러를 사용합니다.여기 커피 대본에 있는 제 예시가 있습니다.

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

작업 예제에서 반응-구글-맵을 사용하여 평균 기본 센터 찾기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>

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

줌 레벨을 계산하여 영역의 두 개의 교차 모서리가 포함된 지도를 표시하고 특정 높이의 화면 부분에 지도를 표시합니다.

좌표 두 개 최대 lat/long min lat/long

픽셀 높이로 표시 영역

      double getZoomLevelNew(context, 
             double maxLat, double maxLong, 
             double minLat, double minLong, 
             double height){
  try {
    double _zoom;
    MediaQueryData queryData2;
    queryData2 = MediaQuery.of(context);
    double _zLat =
        Math.log(
            (globals.factor(height) / queryData2.devicePixelRatio / 256.0) *
                180 / (maxLat - minLat).abs()) / Math.log(2);
    double _zLong =
        Math.log((globals.factor(MediaQuery
            .of(context)
            .size
            .width) / queryData2.devicePixelRatio / 256.0) * 360 /
            (maxLong - minLong).abs()) / Math.log(2);
    _zoom = Math.min(_zLat, _zLong)*globals.zoomFactorNew;
    if (_zoom < 0) {
      _zoom = 0;
    }
    return _zoom;
  } catch(e){
    print("getZoomLevelNew - excep - " + e.toString());
  }

빠른 버전용

func getBoundsZoomLevel(bounds: GMSCoordinateBounds, mapDim: CGSize) -> Double {
        var bounds = bounds
        let WORLD_DIM = CGSize(width: 256, height: 256)
        let ZOOM_MAX: Double = 21.0
        func latRad(_ lat: Double) -> Double {
            let sin2 = sin(lat * .pi / 180)
            let radX2 = log10((1 + sin2) / (1 - sin2)) / 2
            return max(min(radX2, .pi), -.pi) / 2
        }
        func zoom(_ mapPx: CGFloat,_ worldPx: CGFloat,_ fraction: Double) -> Double {
            return floor(log10(Double(mapPx) / Double(worldPx) / fraction / log10(2.0)))
        }
        let ne = bounds.northEast
        let sw = bounds.southWest
        let latFraction = (latRad(ne.latitude) - latRad(sw.latitude)) / .pi
        let lngDiff = ne.longitude - sw.longitude
        let lngFraction = lngDiff < 0 ? (lngDiff + 360) : (lngDiff / 360)
        let latZoom = zoom(mapDim.height, WORLD_DIM.height, latFraction);
        let lngZoom = zoom(mapDim.width, WORLD_DIM.width, lngFraction);
        return min(latZoom, lngZoom, ZOOM_MAX)
    }

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

반응형