Google Maps V3 - Cách tính mức thu phóng cho một giới hạn nhất định


138

Tôi đang tìm cách tính toán mức thu phóng cho một giới hạn nhất định bằng API V3 của Google Maps, tương tự như getBoundsZoomLevel()trong API V2.

Đây là những gì tôi muốn làm:

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

Thật không may, tôi không thể sử dụng fitBounds()phương pháp cho trường hợp sử dụng cụ thể của mình. Nó hoạt động tốt để đánh dấu phù hợp trên bản đồ, nhưng nó không hoạt động tốt để thiết lập giới hạn chính xác. Đây là một ví dụ về lý do tại sao tôi không thể sử dụng fitBounds()phương pháp này.

map.fitBounds(map.getBounds()); // not what you expect

4
Ví dụ cuối cùng là tuyệt vời và rất minh họa! +1. Tôi có cùng một vấn đề .
TMS

Xin lỗi, liên kết câu hỏi sai, đây là liên kết chính xác .
TMS

Câu hỏi này không phải là một bản sao của câu hỏi khác . Câu trả lời cho câu hỏi khác là sử dụng fitBounds(). Câu hỏi này hỏi phải làm gì khi fitBounds()không đủ - vì nó phóng to hoặc bạn không muốn thu phóng (nghĩa là bạn chỉ muốn mức thu phóng).
John S

@Nick Clark: Làm thế nào để bạn biết giới hạn sw, ne được thiết lập? Làm thế nào bạn chụp chúng trước đây?
varaprakash

Câu trả lời:


108

Một câu hỏi tương tự đã được hỏi trên nhóm Google: http://groups.google.com.vn/group/google-maps-js-api-v3/browse_thread/thread/e6448fc197c3c892

Các mức thu phóng là riêng biệt, với tỷ lệ nhân đôi trong mỗi bước. Vì vậy, nói chung, bạn không thể phù hợp với giới hạn bạn muốn chính xác (trừ khi bạn rất may mắn với kích thước bản đồ cụ thể).

Một vấn đề khác là tỷ lệ giữa các độ dài cạnh, ví dụ: bạn không thể khớp chính xác các giới hạn với một hình chữ nhật mỏng bên trong bản đồ hình vuông.

Không có câu trả lời dễ dàng nào về cách điều chỉnh giới hạn chính xác, bởi vì ngay cả khi bạn sẵn sàng thay đổi kích thước của div bản đồ, bạn phải chọn kích thước và mức thu phóng tương ứng mà bạn thay đổi (nói đại khái, bạn làm cho nó lớn hơn hay nhỏ hơn so với hiện tại là?).

Nếu bạn thực sự cần tính toán thu phóng, thay vì lưu trữ nó, điều này sẽ thực hiện mẹo:

Phép chiếu Mercator làm cong vênh vĩ độ, nhưng bất kỳ sự khác biệt nào về kinh độ luôn biểu thị cùng một phần của chiều rộng của bản đồ (độ lệch góc tính theo độ / 360). Ở mức thu phóng 0, toàn bộ bản đồ thế giới là 256x256 pixel và thu phóng mỗi cấp độ nhân đôi cả chiều rộng và chiều cao. Vì vậy, sau một chút đại số, chúng ta có thể tính toán thu phóng như sau, miễn là chúng ta biết chiều rộng của bản đồ tính bằng pixel. Lưu ý rằng vì kinh độ bao quanh, chúng ta phải đảm bảo góc dương.

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
Bạn sẽ không phải lặp lại điều này cho lat và sau đó chọn min của kết quả của 2 chứ? Tôi không nghĩ rằng nó sẽ hoạt động cho một giới hạn hẹp cao .....
whiteatom

3
Hoạt động tuyệt vời với tôi với sự thay đổi Math.round thành Math.floor. Cảm ơn rất nhiều.
Pete

3
Làm thế nào điều này có thể đúng nếu nó không tính đến vĩ độ? Gần xích đạo sẽ ổn nhưng tỷ lệ bản đồ ở mức thu phóng cho trước thay đổi tùy theo vĩ độ!
Mắt

1
@Pete điểm tốt, nói chung có lẽ bạn sẽ muốn làm tròn mức thu phóng sao cho phù hợp hơn một chút so với mong muốn trong bản đồ, thay vì ít hơn một chút. Tôi đã sử dụng Math.round vì trong tình huống của OP, giá trị trước khi làm tròn phải xấp xỉ bằng nhau.
Giles Gardam

20
giá trị của pixelWidth là gì
albert Jegani 23/2/2015

279

Cảm ơn Giles Gardam cho câu trả lời của mình, nhưng nó chỉ đề cập đến kinh độ và không phải là vĩ độ. Một giải pháp hoàn chỉnh sẽ tính toán mức thu phóng cần thiết cho vĩ độ và mức thu phóng cần thiết cho kinh độ, sau đó lấy mức nhỏ hơn (xa hơn) của cả hai.

Đây là một hàm sử dụng cả vĩ độ và kinh độ:

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

Thông số:

Giá trị tham số "giới hạn" phải là một google.maps.LatLngBoundsđối tượng.

Giá trị tham số "mapDim" phải là một đối tượng có thuộc tính "chiều cao" và "chiều rộng" đại diện cho chiều cao và chiều rộng của phần tử DOM hiển thị bản đồ. Bạn có thể muốn giảm các giá trị này nếu bạn muốn đảm bảo đệm. Đó là, bạn có thể không muốn các điểm đánh dấu bản đồ trong giới hạn quá gần với cạnh của bản đồ.

Nếu bạn đang sử dụng thư viện jQuery, mapDimgiá trị có thể đạt được như sau:

var $mapDiv = $('#mapElementId');
var mapDim = { height: $mapDiv.height(), width: $mapDiv.width() };

Nếu bạn đang sử dụng thư viện Prototype, giá trị mapDim có thể được lấy như sau:

var mapDim = $('mapElementId').getDimensions();

Giá trị trả về:

Giá trị trả về là mức thu phóng tối đa vẫn sẽ hiển thị toàn bộ giới hạn. Giá trị này sẽ nằm giữa 0và mức thu phóng tối đa, bao gồm.

Mức thu phóng tối đa là 21. (Tôi tin rằng nó chỉ là 19 cho API Google Maps v2.)


Giải trình:

Google Maps sử dụng phép chiếu Mercator. Trong phép chiếu Mercator, các đường kinh độ cách đều nhau, nhưng các đường vĩ độ thì không. Khoảng cách giữa các đường vĩ độ tăng khi chúng đi từ xích đạo đến các cực. Trong thực tế, khoảng cách có xu hướng về vô cực khi nó đạt đến cực. Tuy nhiên, bản đồ Google Maps không hiển thị các vĩ độ trên khoảng 85 độ Bắc hoặc dưới khoảng -85 độ Nam. ( tham khảo ) (Tôi tính mức cắt thực tế ở +/- 85.05112877980658 độ.)

Điều này làm cho việc tính toán các phân số cho giới hạn phức tạp hơn đối với vĩ độ so với kinh độ. Tôi đã sử dụng một công thức từ Wikipedia để tính toán phần vĩ độ. Tôi giả sử điều này khớp với phép chiếu được sử dụng bởi Google Maps. Rốt cuộc, trang tài liệu Google Maps mà tôi liên kết đến ở trên có chứa một liên kết đến cùng một trang Wikipedia.

Ghi chú khác:

  1. Mức thu phóng nằm trong khoảng từ 0 đến mức thu phóng tối đa. Thu phóng mức 0 là bản đồ được thu nhỏ hoàn toàn. Cấp độ cao hơn phóng to bản đồ hơn nữa. ( tham khảo )
  2. Ở mức thu phóng 0, toàn bộ thế giới có thể được hiển thị trong một khu vực có kích thước 256 x 256 pixel. ( tham khảo )
  3. Đối với mỗi mức thu phóng cao hơn, số pixel cần thiết để hiển thị cùng một khu vực nhân đôi cả về chiều rộng và chiều cao. ( tham khảo )
  4. Bản đồ bao bọc theo hướng dọc, nhưng không theo hướng vĩ độ.

6
câu trả lời tuyệt vời, đây nên được bình chọn hàng đầu vì nó chiếm cả kinh độ và vĩ độ. Làm việc hoàn hảo cho đến nay.
Peter Wooster

1
@ John S - Đây là một giải pháp tuyệt vời và tôi đang dự tính sử dụng phương pháp này qua phương pháp fitBound bản đồ google có sẵn cho tôi. Tôi nhận thấy fitBound đôi khi trở lại một mức thu phóng (thu nhỏ), nhưng tôi cho rằng đó là từ phần đệm được thêm vào. Có phải sự khác biệt duy nhất sau đó giữa phương thức này và phương thức fitBound, chỉ là số lượng phần đệm bạn muốn thêm mà tài khoản cho sự thay đổi mức thu phóng giữa hai phương thức?
johnt doanh nhân

1
@johnt doanh nhân - Ưu điểm số 1: Bạn có thể sử dụng phương pháp này trước khi tạo bản đồ, do đó bạn có thể cung cấp kết quả của nó cho các cài đặt bản đồ ban đầu. Với fitBounds, bạn cần tạo bản đồ và sau đó chờ đợi sự kiện "bound_changed".
John S

1
@ MarianPaździoch - Nó hoạt động cho giới hạn đó, xem tại đây . Bạn có mong đợi có thể phóng to để những điểm đó nằm ở các góc chính xác của bản đồ không? Điều đó là không thể bởi vì mức thu phóng là giá trị nguyên. Hàm trả về mức thu phóng cao nhất vẫn sẽ bao gồm toàn bộ giới hạn trên bản đồ.
John S

1
@CarlMeyer - Tôi không đề cập đến nó trong câu trả lời của mình, nhưng trong một nhận xét ở trên tôi nói rằng một ưu điểm của chức năng này là "Bạn có thể sử dụng phương pháp này trước khi bạn tạo bản đồ." Việc sử dụng map.getProjection()sẽ loại bỏ một số phép toán (và giả định về phép chiếu), nhưng điều đó có nghĩa là hàm này không thể được gọi cho đến khi bản đồ được tạo và sự kiện "chiếu_changed" đã kích hoạt.
John S

39

Đối với phiên bản 3 của API, việc này rất đơn giản và hiệu quả:

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

và một số thủ thuật tùy chọn:

//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
tại sao không đặt mức thu phóng tối thiểu trong khi khởi tạo bản đồ, đại loại như: var mapOptions = {maxZoom: 15,};
Kush

2
@Kush, điểm tốt. nhưng maxZoomsẽ ngăn chặn người dùng từ tay phóng to. Ví dụ của tôi chỉ thay đổi DefaultZoom và chỉ khi cần thiết.
d.raev

1
khi bạn thực hiện fitBound, nó chỉ nhảy để khớp với giới hạn thay vì hoạt hình ở đó từ chế độ xem hiện tại. giải pháp tuyệt vời là bằng cách sử dụng đã được đề cập getBoundsZoomLevel. theo cách đó, khi bạn gọi setZoom, nó sẽ hoạt động đến mức thu phóng mong muốn. từ đó, không có vấn đề gì khi thực hiện panTo và bạn kết thúc với một hình ảnh động bản đồ đẹp phù hợp với giới hạn
user151496

hoạt hình là không có nơi nào được thảo luận trong câu hỏi cũng như trong câu trả lời của tôi. Nếu bạn có ví dụ hữu ích về chủ đề này, chỉ cần tạo một câu trả lời mang tính xây dựng, với ví dụ và cách thức và thời điểm có thể sử dụng nó.
d.raev

Vì một số lý do, bản đồ của Google không phóng to khi gọi setZoom () ngay sau cuộc gọi map.fitBound (). (gmaps là v3.25 hiện tại)
kashiraja

4

Đây là phiên bản của hàm 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

Cảm ơn, điều đó đã giúp tôi rất nhiều trong việc tìm kiếm hệ số thu phóng phù hợp nhất để hiển thị chính xác một đa tuyến. Tôi tìm thấy tọa độ tối đa và tối thiểu trong số các điểm tôi phải theo dõi và, trong trường hợp đường dẫn rất "dọc", tôi chỉ cần thêm một vài dòng mã:

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

Trên thực tế, hệ số thu phóng lý tưởng là zoomfactor-1.


Tôi thích var zoomfactor = Math.floor(Math.log(960 * 360 / angle / GLOBE_WIDTH) / Math.LN2)-1;. Tuy nhiên, rất hữu ích.
Mojowen

1

Vì tất cả các câu trả lời khác dường như có vấn đề với tôi với một hoặc một tập hợp hoàn cảnh khác (chiều rộng / chiều cao bản đồ, giới hạn chiều rộng / chiều cao, v.v.) Tôi hình dung tôi sẽ đặt câu trả lời của mình ở đây ...

Có một tệp javascript rất hữu ích ở đây: http://www.polyarc.us/adjust.js

Tôi đã sử dụng nó như là một cơ sở cho việc này:

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

};

Bạn chắc chắn có thể làm sạch nó hoặc thu nhỏ nó nếu cần, nhưng tôi đã giữ các tên biến dài trong một nỗ lực để làm cho nó dễ hiểu hơn.

Nếu bạn đang tự hỏi OFFSET đến từ đâu, rõ ràng 268435456 là một nửa chu vi trái đất tính bằng pixel ở mức thu phóng 21 (theo http://www.appelsiini.net/2008/11/int sinhtion-to-marker -clustering-with-google -maps ).


0

Valerio gần như đúng với giải pháp của mình, nhưng có một số sai lầm logic.

trước tiên bạn phải kiểm tra wether angle2 lớn hơn góc, trước khi thêm 360 ở mức âm.

nếu không, bạn luôn có giá trị lớn hơn góc

Vì vậy, giải pháp chính xác là:

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 ở đó, bởi vì tôi có chiều rộng lớn hơn chiều cao.


0

map.getBounds()không phải là hoạt động nhất thời, vì vậy tôi sử dụng trong trường hợp xử lý sự kiện tương tự. Dưới đây là ví dụ của tôi trong Coffeescript

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

0

Ví dụ hoạt động để tìm trung tâm mặc định trung bình với phản ứng-google-maps trên 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

Việc tính toán mức độ phóng to cho các kinh độ của Giles Gardam hoạt động tốt với tôi. Nếu bạn muốn tính hệ số thu phóng cho vĩ độ, đây là một giải pháp dễ dàng hoạt động tốt:

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);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.