How to get bounds in degrees of google static map which has been returned, for example, for following request
http://maps.googleapis.com/maps/api/staticmap?center=0.0,0.0&zoom=10&size=640x640&sensor=false
As I know, full Earth map is 256x256 image. This means that n vertical pixels contain x degrees, but n horizontal pixels contain 2x degrees. Right?
As google says center defines the center of the map, equidistant from all edges of the map. As I understood equidistant in pixels (or in degrees?). And each succeeding zoom level doubles the precision in both horizontal and vertical dimensions. So, I can find delta value of Longitude of map for each zoom value as:
dLongitude = (HorizontalMapSizeInPixels / 256 ) * ( 360 / pow(2, zoom) );
Same calculations for Latitude:
dLatitude = (VerticalMapSizeInPixels / 256 ) * ( 180 / pow(2, zoom) );
VerticalMapSizeInPixels and HorizontalMapSizeInPixels are parameters of map size in URL.
It's good to calculate delta value of Longitude, but for Latitude it is wrong. I cannot find delta value of Latitude, there is some delta error.
getBounds() in Google Maps API v3 But in API v3 you will get “bounds is undefined” error. So to get our latitude and longitude we need to move getBounds(), to some event listener. Description of bounds_changed in documentation is: “This event is fired when the viewport bounds have changed.”
URL size restriction Maps Static API URLs are restricted to 8192 characters in size. In practice, you will probably not have need for URLs longer than this, unless you produce complicated maps with a high number of markers and paths.
As I know, full Earth map is 256x256 image.
Yes.
This means that n vertical pixels contain x degrees, but n horizontal pixels contain 2x degrees. Right?
No. One pixel will represent varying amounts of latitude depending on the latitude. One pixel at the Equator represents less latitude than one pixel near the poles.
The corners of the map will depend on center, zoom level and map size, and you'd need to use the Mercator projection to calculate them. If you don't want to load the full API, here's a MercatorProjection object:
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)
You can save that to a separate file, for example "MercatorProjection.js", and then include it in your application.
<script src="MercatorProjection.js"></script>
With the above file loaded, the following function calculates the SW and NE corners of a map of a given size and at a given zoom.
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);
}
and you'd call it like this:
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);
Thanks Marcelo for your answer. It has been quite helpful. In case anyone would be interested, here the Python version of the code (a rough translation of the PHP code, probably not as pythonic as it could be):
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,
}
Usage :
>>> 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'
Simple Python Version
Having spent a long time on a similar problem and using this thread for help, here is a simplified Python version of Marcelo (and Jmague)'s code:
import math
import requests
def latLngToPoint(mapWidth, mapHeight, lat, lng):
x = (lng + 180) * (mapWidth/360)
y = ((1 - math.log(math.tan(lat * math.pi / 180) + 1 / math.cos(lat * math.pi / 180)) / math.pi) / 2) * mapHeight
return(x, y)
def pointToLatLng(mapWidth, mapHeight, x, y):
lng = x / mapWidth * 360 - 180
n = math.pi - 2 * math.pi * y / mapHeight
lat = (180 / math.pi * math. atan(0.5 * (math.exp(n) - math.exp(-n))))
return(lat, lng)
def getImageBounds(mapWidth, mapHeight, xScale, yScale, lat, lng):
centreX, centreY = latLngToPoint(mapWidth, mapHeight, lat, lng)
southWestX = centreX - (mapWidth/2)/ xScale
southWestY = centreY + (mapHeight/2)/ yScale
SWlat, SWlng = pointToLatLng(mapWidth, mapHeight, southWestX, southWestY)
northEastX = centreX + (mapWidth/2)/ xScale
northEastY = centreY - (mapHeight/2)/ yScale
NElat, NElng = pointToLatLng(mapWidth, mapHeight, northEastX, northEastY)
return[SWlat, SWlng, NElat, NElng]
lat = 37.806716
lng = -122.477702
zoom = 16
picHeight = 640 #Resulting image height in pixels (x2 if scale parameter is set to 2)
picWidth = 640
mapHeight = 256 #Original map size - specific to Google Maps
mapWidth = 256
xScale = math.pow(2, zoom) / (picWidth/mapWidth)
yScale = math.pow(2, zoom) / (picHeight/mapWidth)
corners = getImageBounds(mapWidth, mapHeight, xScale, yScale, lat, lng)
Here I have used x and y to represent pixel values and lat lng as, Latitude and Longitude. lat, lng, zoom, picHeight and picWidth can all be changed to your specific use case. Changing the scale/ maptype etc will not affect this calculation.
I used this code to tile Static Maps images with no gaps/ overlap. If you want to see more of it in use/ how it can work in that sense there is more information on my GitHub.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With