Как получить границы статической карты google?
Как получить границы в градусах статической карты google, которая была возвращена, например, для следующего запроса
http://maps.googleapis.com/maps/api/staticmap?center=0.0,0.0&zoom=10&size=640x640&sensor=false
Как я знаю, полная карта Земли имеет 256х256 изображений. Это означает, что n вертикальных пикселей содержат x градусов, но n горизонтальных пикселей содержат 2x градуса. Правильно?
Как говорится в google
центр определяет центр карты, равноудаленный от всех ребер карты. Как я понял равноудаленный в пикселях (или в градусах?). И каждый последующий уровень масштабирования удваивает точность как в горизонтальном, так и в вертикальном размерах.
Таким образом, я могу найти значение дельта долготы карты для каждого значения масштабирования как:
dLongitude = (HorizontalMapSizeInPixels / 256 ) * ( 360 / pow(2, zoom) );
Те же вычисления для Локатора:
dLatitude = (VerticalMapSizeInPixels / 256 ) * ( 180 / pow(2, zoom) );
VerticalMapSizeInPixels и HorizontalMapSizeInPixels являются параметрами размера карты в URL.
Хорошо рассчитать значение дельта долготы, но для Локатора это неправильно. Я не могу найти значение дельты Latitude, есть некоторая ошибка deltai > .
Ответы
Ответ 1
Как я знаю, полная карта Земли имеет 256х256 изображений.
Да.
Это означает, что n вертикальных пикселей содержат x градусов, но n горизонтальных пиксели содержат 2x градуса. Правильно?
Нет. Один пиксель будет представлять собой различные количества широты в зависимости от широты. Один пиксель в экваторе представляет меньшую ширину, чем один пиксель возле полюсов.
Углы карты будут зависеть от центра, уровня масштабирования и размера карты, и вам нужно будет использовать проекцию Меркатора для их расчета.
Если вы не хотите загружать полный API, здесь объект MercatorProjection:
var MERCATOR_RANGE = 256;
function bound(value, opt_min, opt_max) {
if (opt_min != null) value = Math.max(value, opt_min);
if (opt_max != null) value = Math.min(value, opt_max);
return value;
}
function degreesToRadians(deg) {
return deg * (Math.PI / 180);
}
function radiansToDegrees(rad) {
return rad / (Math.PI / 180);
}
function MercatorProjection() {
this.pixelOrigin_ = new google.maps.Point( MERCATOR_RANGE / 2, MERCATOR_RANGE / 2);
this.pixelsPerLonDegree_ = MERCATOR_RANGE / 360;
this.pixelsPerLonRadian_ = MERCATOR_RANGE / (2 * Math.PI);
};
MercatorProjection.prototype.fromLatLngToPoint = function(latLng, opt_point) {
var me = this;
var point = opt_point || new google.maps.Point(0, 0);
var origin = me.pixelOrigin_;
point.x = origin.x + latLng.lng() * me.pixelsPerLonDegree_;
// NOTE(appleton): Truncating to 0.9999 effectively limits latitude to
// 89.189. This is about a third of a tile past the edge of the world tile.
var siny = bound(Math.sin(degreesToRadians(latLng.lat())), -0.9999, 0.9999);
point.y = origin.y + 0.5 * Math.log((1 + siny) / (1 - siny)) * -me.pixelsPerLonRadian_;
return point;
};
MercatorProjection.prototype.fromPointToLatLng = function(point) {
var me = this;
var origin = me.pixelOrigin_;
var lng = (point.x - origin.x) / me.pixelsPerLonDegree_;
var latRadians = (point.y - origin.y) / -me.pixelsPerLonRadian_;
var lat = radiansToDegrees(2 * Math.atan(Math.exp(latRadians)) - Math.PI / 2);
return new google.maps.LatLng(lat, lng);
};
//pixelCoordinate = worldCoordinate * Math.pow(2,zoomLevel)
Вы можете сохранить это в отдельный файл, например "MercatorProjection.js", а затем включить его в свое приложение.
<script src="MercatorProjection.js"></script>
При загрузке вышеуказанного файла следующая функция вычисляет углы SW и NE карты заданного размера и при заданном масштабировании.
function getCorners(center,zoom,mapWidth,mapHeight){
var scale = Math.pow(2,zoom);
var centerPx = proj.fromLatLngToPoint(center);
var SWPoint = {x: (centerPx.x -(mapWidth/2)/ scale) , y: (centerPx.y + (mapHeight/2)/ scale)};
var SWLatLon = proj.fromPointToLatLng(SWPoint);
alert('SW: ' + SWLatLon);
var NEPoint = {x: (centerPx.x +(mapWidth/2)/ scale) , y: (centerPx.y - (mapHeight/2)/ scale)};
var NELatLon = proj.fromPointToLatLng(NEPoint);
alert(' NE: '+ NELatLon);
}
и вы бы назвали это следующим образом:
var proj = new MercatorProjection();
var G = google.maps;
var centerPoint = new G.LatLng(49.141404, -121.960988);
var zoom = 10;
getCorners(centerPoint,zoom,640,640);
Ответ 2
Спасибо Марсело за ваш ответ. Это было очень полезно. В случае, если кому-то будет интересно, вот версия кода на Python (грубый перевод кода PHP, возможно, не такой как пифонический, как это могло бы быть):
from __future__ import division
import math
MERCATOR_RANGE = 256
def bound(value, opt_min, opt_max):
if (opt_min != None):
value = max(value, opt_min)
if (opt_max != None):
value = min(value, opt_max)
return value
def degreesToRadians(deg) :
return deg * (math.pi / 180)
def radiansToDegrees(rad) :
return rad / (math.pi / 180)
class G_Point :
def __init__(self,x=0, y=0):
self.x = x
self.y = y
class G_LatLng :
def __init__(self,lt, ln):
self.lat = lt
self.lng = ln
class MercatorProjection :
def __init__(self) :
self.pixelOrigin_ = G_Point( MERCATOR_RANGE / 2, MERCATOR_RANGE / 2)
self.pixelsPerLonDegree_ = MERCATOR_RANGE / 360
self.pixelsPerLonRadian_ = MERCATOR_RANGE / (2 * math.pi)
def fromLatLngToPoint(self, latLng, opt_point=None) :
point = opt_point if opt_point is not None else G_Point(0,0)
origin = self.pixelOrigin_
point.x = origin.x + latLng.lng * self.pixelsPerLonDegree_
# NOTE(appleton): Truncating to 0.9999 effectively limits latitude to
# 89.189. This is about a third of a tile past the edge of the world tile.
siny = bound(math.sin(degreesToRadians(latLng.lat)), -0.9999, 0.9999)
point.y = origin.y + 0.5 * math.log((1 + siny) / (1 - siny)) * - self.pixelsPerLonRadian_
return point
def fromPointToLatLng(self,point) :
origin = self.pixelOrigin_
lng = (point.x - origin.x) / self.pixelsPerLonDegree_
latRadians = (point.y - origin.y) / -self.pixelsPerLonRadian_
lat = radiansToDegrees(2 * math.atan(math.exp(latRadians)) - math.pi / 2)
return G_LatLng(lat, lng)
#pixelCoordinate = worldCoordinate * pow(2,zoomLevel)
def getCorners(center, zoom, mapWidth, mapHeight):
scale = 2**zoom
proj = MercatorProjection()
centerPx = proj.fromLatLngToPoint(center)
SWPoint = G_Point(centerPx.x-(mapWidth/2)/scale, centerPx.y+(mapHeight/2)/scale)
SWLatLon = proj.fromPointToLatLng(SWPoint)
NEPoint = G_Point(centerPx.x+(mapWidth/2)/scale, centerPx.y-(mapHeight/2)/scale)
NELatLon = proj.fromPointToLatLng(NEPoint)
return {
'N' : NELatLon.lat,
'E' : NELatLon.lng,
'S' : SWLatLon.lat,
'W' : SWLatLon.lng,
}
Использование:
>>> import MercatorProjection
>>> centerLat = 49.141404
>>> centerLon = -121.960988
>>> zoom = 10
>>> mapWidth = 640
>>> mapHeight = 640
>>> centerPoint = MercatorProjection.G_LatLng(centerLat, centerLon)
>>> corners = MercatorProjection.getCorners(centerPoint, zoom, mapWidth, mapHeight)
>>> corners
{'E': -65.710988,
'N': 74.11120692972199,
'S': 0.333879313530149,
'W': -178.210988}
>>> mapURL = "http://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=%d&size=%dx%d&scale=2&maptype=roadmap&sensor=false"%(centerLat,centerLon,zoom,mapWidth,mapHeight)
>>> mapURL
http://maps.googleapis.com/maps/api/staticmap?center=49.141404,-121.960988&zoom=10&size=640x640&scale=2&maptype=roadmap&sensor=false'
Ответ 3
Вот строчный перевод кода Марсело в PHP, который, вероятно, можно немного почистить. Прекрасно работает! Спасибо Марсело за тяжелую работу.
define("MERCATOR_RANGE", 256);
function degreesToRadians($deg) {
return $deg * (M_PI / 180);
}
function radiansToDegrees($rad) {
return $rad / (M_PI / 180);
}
function bound($value, $opt_min, $opt_max) {
if ($opt_min != null) $value = max($value, $opt_min);
if ($opt_max != null) $value = min($value, $opt_max);
return $value;
}
class G_Point {
public $x,$y;
function G_Point($x=0, $y=0){
$this->x = $x;
$this->y = $y;
}
}
class G_LatLng {
public $lat,$lng;
function G_LatLng($lt, $ln){
$this->lat = $lt;
$this->lng = $ln;
}
}
class MercatorProjection {
private $pixelOrigin_, $pixelsPerLonDegree_, $pixelsPerLonRadian_;
function MercatorProjection() {
$this->pixelOrigin_ = new G_Point( MERCATOR_RANGE / 2, MERCATOR_RANGE / 2);
$this->pixelsPerLonDegree_ = MERCATOR_RANGE / 360;
$this->pixelsPerLonRadian_ = MERCATOR_RANGE / (2 * M_PI);
}
public function fromLatLngToPoint($latLng, $opt_point=null) {
$me = $this;
$point = $opt_point ? $opt_point : new G_Point(0,0);
$origin = $me->pixelOrigin_;
$point->x = $origin->x + $latLng->lng * $me->pixelsPerLonDegree_;
// NOTE(appleton): Truncating to 0.9999 effectively limits latitude to
// 89.189. This is about a third of a tile past the edge of the world tile.
$siny = bound(sin(degreesToRadians($latLng->lat)), -0.9999, 0.9999);
$point->y = $origin->y + 0.5 * log((1 + $siny) / (1 - $siny)) * -$me->pixelsPerLonRadian_;
return $point;
}
public function fromPointToLatLng($point) {
$me = $this;
$origin = $me->pixelOrigin_;
$lng = ($point->x - $origin->x) / $me->pixelsPerLonDegree_;
$latRadians = ($point->y - $origin->y) / -$me->pixelsPerLonRadian_;
$lat = radiansToDegrees(2 * atan(exp($latRadians)) - M_PI / 2);
return new G_LatLng($lat, $lng);
}
//pixelCoordinate = worldCoordinate * pow(2,zoomLevel)
}
function getCorners($center, $zoom, $mapWidth, $mapHeight){
$scale = pow(2, $zoom);
$proj = new MercatorProjection();
$centerPx = $proj->fromLatLngToPoint($center);
$SWPoint = new G_Point($centerPx->x-($mapWidth/2)/$scale, $centerPx->y+($mapHeight/2)/$scale);
$SWLatLon = $proj->fromPointToLatLng($SWPoint);
$NEPoint = new G_Point($centerPx->x+($mapWidth/2)/$scale, $centerPx->y-($mapHeight/2)/$scale);
$NELatLon = $proj->fromPointToLatLng($NEPoint);
return array(
'N' => $NELatLon->lat,
'E' => $NELatLon->lng,
'S' => $SWLatLon->lat,
'W' => $SWLatLon->lng,
);
}
Использование:
$centerLat = 49.141404;
$centerLon = -121.960988;
$zoom = 10;
$mapWidth = 640;
$mapHeight = 640;
$centerPoint = new G_LatLng($centerLat, $centerLon);
$corners = getCorners($centerPoint, $zoom, $mapWidth, $mapHeight);
$mapURL = "http://maps.googleapis.com/maps/api/staticmap?center={$centerLat},{$centerLon}&zoom={$zoom}&size={$mapWidth}x{$mapHeight}&scale=2&maptype=roadmap&sensor=false";
Ответ 4
Вот перевод на Delphi/Pascal с некоторыми оптимизациями, чтобы соответствовать более строгим языкам Pascal и управлению памятью.
unit Mercator.Google.Maps;
interface
uses System.Math;
type TG_Point = class(TObject)
private
Fx: integer;
Fy: integer;
public
property x: integer read Fx write Fx;
property y: integer read Fy write Fy;
constructor Create(Ax: integer = 0; Ay: integer = 0);
end;
type TG_LatLng = class(TObject)
private
FLat: double;
FLng: double;
public
property Lat: double read FLat write FLat;
property Lng: double read FLng write FLng;
constructor Create(ALat: double; ALng: double);
end;
type TMercatorProjection = class(TObject)
private
pixelOrigin_: TG_Point;
pixelsPerLonDegree_, pixelsPerLonRadian_: double;
function degreesToRadians(deg: double): double;
function radiansToDegrees(rad: double): double;
function bound(value: double; opt_min: double; opt_max: double): double;
public
constructor Create;
procedure fromLatLngToPoint(latLng: TG_LatLng; var point: TG_Point);
procedure fromPointToLatLng(point: TG_point; var latLng: TG_LatLng);
procedure getCorners(center: TG_LatLng; zoom: integer; mapWidth: integer; mapHeight: integer;
var NELatLon: TG_LatLng; var SWLatLon: TG_LatLng);
end;
implementation
const MERCATOR_RANGE = 256;
constructor TG_Point.Create(Ax: Integer = 0; Ay: Integer = 0);
begin
inherited Create;
Fx := Ax;
Fy := Ay
end;
// **************
constructor TG_LatLng.Create(ALat: double; ALng: double);
begin
inherited Create;
FLat := ALat;
FLng := ALng
end;
// **************
constructor TMercatorProjection.Create;
begin
inherited Create;
pixelOrigin_ := TG_Point.Create( Round(MERCATOR_RANGE / 2), Round(MERCATOR_RANGE / 2));
pixelsPerLonDegree_ := MERCATOR_RANGE / 360;
pixelsPerLonRadian_ := MERCATOR_RANGE / (2 * PI);
end;
// Translate degrees to radians
function TMercatorProjection.degreesToRadians(deg: double): double;
begin
Result := deg * (PI / 180);
end;
// Translate radians to degrees
function TMercatorProjection.radiansToDegrees(rad: double): double;
begin
Result := rad / (PI / 180);
end;
// keep value insid defined bounds
function TMercatorProjection.bound(value: double; opt_min: double; opt_max: double): double;
begin
if Value < opt_min then Result := opt_min
else if Value > opt_max then Result := opt_max
else Result := Value;
end;
procedure TMercatorProjection.fromLatLngToPoint(latLng: TG_LatLng; var point: TG_Point);
var
siny: double;
begin
if Assigned(point) then
begin
point.x := Round(pixelOrigin_.x + latLng.lng * pixelsPerLonDegree_);
// NOTE(appleton): Truncating to 0.9999 effectively limits latitude to
// 89.189. This is about a third of a tile past the edge of the world tile.
siny := bound(sin(degreesToRadians(latLng.lat)), -0.9999, 0.9999);
point.y := Round(pixelOrigin_.y + 0.5 * ln((1 + siny) / (1 - siny)) * -pixelsPerLonRadian_);
end;
end;
procedure TMercatorProjection.fromPointToLatLng(point: TG_point; var latLng: TG_LatLng);
var
latRadians: double;
begin
if Assigned(latLng) then
begin
latLng.lng := (point.x - pixelOrigin_.x) / pixelsPerLonDegree_;
latRadians := (point.y - pixelOrigin_.y) / -pixelsPerLonRadian_;
latLng.lat := radiansToDegrees(2 * arctan(exp(latRadians)) - PI / 2);
end;
end;
//pixelCoordinate = worldCoordinate * pow(2,zoomLevel)
procedure TMercatorProjection.getCorners(center: TG_LatLng; zoom: integer; mapWidth: integer; mapHeight: integer;
var NELatLon: TG_LatLng; var SWLatLon: TG_LatLng);
var
scale: double;
centerPx, SWPoint, NEPoint: TG_Point;
begin
scale := power(2, zoom);
centerPx := TG_Point.Create(0, 0);
try
fromLatLngToPoint(center, centerPx);
SWPoint := TG_Point.Create(Round(centerPx.x-(mapWidth/2)/scale), Round(centerPx.y+(mapHeight/2)/scale));
NEPoint := TG_Point.Create(Round(centerPx.x+(mapWidth/2)/scale), Round(centerPx.y-(mapHeight/2)/scale));
try
fromPointToLatLng(SWPoint, SWLatLon);
fromPointToLatLng(NEPoint, NELatLon);
finally
SWPoint.Free;
NEPoint.Free;
end;
finally
centerPx.Free;
end;
end;
end.
Пример использования:
with TMercatorProjection.Create do
try
CLatLon := TG_LatLng.Create(Latitude, Longitude);
SWLatLon := TG_LatLng.Create(0,0);
NELatLon := TG_LatLng.Create(0,0);
try
getCorners(CLatLon, Zoom,
MapWidth, MapHeight,
SWLatLon, NELatLon);
finally
ShowMessage('SWLat='+FloatToStr(SWLatLon.Lat)+' | SWLon='+FloatToStr(SWLatLon.Lng));
ShowMessage('NELat='+FloatToStr(NELatLon.Lat)+' | NELon='+FloatToStr(NELatLon.Lng));
SWLatLon.Free;
NELatLon.Free;
CLatLon.Free;
end;
finally
Free;
end;
Ответ 5
Для больших коэффициентов масштабирования ( >= 8), где можно пренебречь неравномерностью масштаба карты на оси y, существует гораздо более простой метод, когда мы просто принимаем корректировку 1/cos (широта) для пикселей /( степени широты). Исходное разрешение для масштабирования = 0 составляет 256 пикселей на 360 градусов как для x, так и для оси y при 0.
def get_static_map_bounds(lat, lng, zoom, sx, sy):
# lat, lng - center
# sx, sy - map size in pixels
# 256 pixels - initial map size for zoom factor 0
sz = 256 * 2 ** zoom
#resolution in degrees per pixel
res_lat = cos(lat * pi / 180.) * 360. / sz
res_lng = 360./sz
d_lat = res_lat * sy / 2
d_lng = res_lng * sx / 2
return ((lat-d_lat, lng-d_lng), (lat+d_lat, lng+d_lng))