I'm trying to georeference a set of image tiles downloaded from a Google Maps site. Based only on each image's Google Maps grid reference and zoom level, how can I calculate the real-world coordinates?

The page at http://www.maptiler.org/google-maps-coordinates-tile-bounds-projection shows the coordinates and Google Maps grid reference for web mercator tiles.

For example, the centre tile is `x=49, y=30, z=6` and has the corner coordinates `10644926.307106785, 626172.1357121654, 11271098.44281895, 1252344.27142432`:

Is it possible to calculate the corner coordinates based solely on the grid reference and zoom level (49, 30, 6)?

This is similar to How to georeference a web mercator tile correctly using gdal? but I need a completely programmatic solution, without needing to look anything up online. (Or, if I do need to look up values online it needs to be programmatic).

Edit: this screenshot shows the location of a sample tile (zoom level 11, x = 1593, y = 933). How would I georeference that tile?

• Does it come as a true tile? No surrounding? Commented Apr 7, 2016 at 4:41
• @FelixIP yeah all I know is the grid reference and zoom level of each individual JPG. I've added a screenshot to demonstrate this. PS I realise this is slightly dodgy, but sometimes it has to be done - plus it's an interesting exercise! Commented Apr 7, 2016 at 5:07
• i think you should be able to get the corner cordinates using only the zoom levels and grid reference, as far as I know, the grid reference system remains constant, so there should a way to do this. Since zoom level 0 has the world in 1 tile and zoom 1 in 4 and so on, it should be possible to get the coordinates. Commented Apr 7, 2016 at 5:21
• @StephenLead maybe try to divide the world into 4 parts in each zoom (iteration) and get the coordinates using that somehow. It'll have to be an iterative function though by my understanding. Commented Apr 7, 2016 at 5:43
• are you allowed to do this? that is, does the google maps terms allow this? Commented Apr 7, 2016 at 15:19

This page http://www.maptiler.org/google-maps-coordinates-tile-bounds-projection provides algorithms, written in python, to compute tile bounds in EPSG:900913 coordinates and in latutude/longitude using WGS84 datum.

The class `GlobalMercator` in this script http://www.maptiler.org/google-maps-coordinates-tile-bounds-projection/globalmaptiles.py contains two methods, `TileBounds()` and `TileLatLonBounds()` to do this.

A modified version of the script to show tile bounds coordinates:

``````#!/usr/bin/env python
###############################################################################
# \$Id\$
#
# Project:  GDAL2Tiles, Google Summer of Code 2007 & 2008
#           Global Map Tiles Classes
# Purpose:  Convert a raster into TMS tiles, create KML SuperOverlay EPSG:4326,
#           generate a simple HTML viewers based on Google Maps and OpenLayers
# Author:   Klokan Petr Pridal, klokan at klokan dot cz
# Web:      http://www.klokan.cz/projects/gdal2tiles/
#
###############################################################################
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
###############################################################################

"""
tilebounds.py

Global Map Tiles as defined in Tile Map Service (TMS) Profiles
==============================================================

Functions necessary for generation of global tiles used on the web.
It contains classes implementing coordinate conversions for:

- GlobalMercator (based on EPSG:900913 = EPSG:3785)
for Google Maps, Yahoo Maps, Microsoft Maps compatible tiles
- GlobalGeodetic (based on EPSG:4326)
for OpenLayers Base Map and Google Earth compatible tiles

http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification
http://wiki.osgeo.org/wiki/WMS_Tiling_Client_Recommendation
http://msdn.microsoft.com/en-us/library/bb259689.aspx

Created by Klokan Petr Pridal on 2008-07-03.
Google Summer of Code 2008, project GDAL2Tiles for OSGEO.

In case you use this class in your product, translate it to another language
My email: klokan at klokan dot cz.
I would like to know where it was used.

Class is available under the open-source GDAL license (www.gdal.org).
"""

import math

class GlobalMercatorLight(object):

def __init__(self, tileSize=256):
"Initialize the TMS Global Mercator pyramid"
self.tileSize = tileSize
self.initialResolution = 2 * math.pi * 6378137 / self.tileSize
# 156543.03392804062 for tileSize 256 pixels
self.originShift = 2 * math.pi * 6378137 / 2.0
# 20037508.342789244

def MetersToLatLon(self, mx, my ):
"Converts XY point from Spherical Mercator EPSG:900913 to lat/lon in WGS84 Datum"

lon = (mx / self.originShift) * 180.0
lat = (my / self.originShift) * 180.0

lat = 180 / math.pi * (2 * math.atan( math.exp( lat * math.pi / 180.0)) - math.pi / 2.0)
return lat, lon

def PixelsToMeters(self, px, py, zoom):
"Converts pixel coordinates in given zoom level of pyramid to EPSG:900913"

res = self.Resolution( zoom )
mx = px * res - self.originShift
my = py * res - self.originShift
return mx, my

def TileBounds(self, tx, ty, zoom):
"Returns bounds of the given tile in EPSG:900913 coordinates"

minx, miny = self.PixelsToMeters( tx*self.tileSize, ty*self.tileSize, zoom )
maxx, maxy = self.PixelsToMeters( (tx+1)*self.tileSize, (ty+1)*self.tileSize, zoom )
return ( minx, miny, maxx, maxy )

def TileLatLonBounds(self, tx, ty, zoom ):
"Returns bounds of the given tile in latutude/longitude using WGS84 datum"

bounds = self.TileBounds( tx, ty, zoom)
minLat, minLon = self.MetersToLatLon(bounds[0], bounds[1])
maxLat, maxLon = self.MetersToLatLon(bounds[2], bounds[3])

return ( minLat, minLon, maxLat, maxLon )

def Resolution(self, zoom ):
"Resolution (meters/pixel) for given zoom level (measured at Equator)"

# return (2 * math.pi * 6378137) / (self.tileSize * 2**zoom)
return self.initialResolution / (2**zoom)

#---------------------

if __name__ == "__main__":
import sys, os

def Usage(s = ""):
print "Usage: tilebounds.py tx ty zoomlevel"
print
sys.exit(1)

profile = 'mercator'
zoomlevel = None
tx, ty = None, None

argv = sys.argv
i = 1
while i < len(argv):
arg = argv[i]
if tx is None:
tx = float(argv[i])
elif ty is None:
ty = float(argv[i])
elif zoomlevel is None:
zoomlevel = int(argv[i])
else:
Usage("ERROR: Too many parameters")

i = i + 1

if profile != 'mercator':
Usage("ERROR: Sorry, given profile is not implemented yet.")

if zoomlevel == None or tx == None or ty == None:
Usage("ERROR: Specify at least 'lat', 'lon' and 'zoomlevel'.")

tz = zoomlevel
mercator = GlobalMercatorLight()

minx, miny, maxx, maxy = mercator.TileBounds( tx, ty, tz )
print "Bounds of the given tile in EPSG:900913 coordinates: "
print "  upper-left corner: "
print (minx, miny)
print "  bottom-right corner: "
print (maxx, maxy)
print

minLat, minLon, maxLat, maxLon = mercator.TileLatLonBounds( tx, ty, tz )
print "Bounds of the given tile in latitude/longitude using WGS84 datum: "
print "  upper-left corner: "
print (minLat, minLon)
print "  bottom-right corner: "
print (maxLat, maxLon)
print
``````

Usage: `tilebounds xTile yTile zoom`.

For example, the output for the tile `x=49`, `y=30`, `z=6` is as follows:

``````\$./tilebounds.py 49 30 6
Bounds of the given tile in EPSG:900913 coordinates:
upper-left corner:
(10644926.307106785, -1252344.271424327)
bottom-right corner:
(11271098.44281895, -626172.1357121654)

Bounds of the given tile in latitude/longitude using WGS84 datum:
upper-left corner:
(-11.178401873711772, 95.625)
bottom-right corner:
(-5.61598581915534, 101.25000000000001)
``````

A tile can be downloaded with the url `http://mt.google.com/vt?x=xTile&y=yTile&z=zoom`, but this is forbidden to access it directly.

The software was originally written by Klokan Petr Přidal.

I hope this could help!

• Welcome to GIS Stack Exchange, and many thanks for this. My requirement has passed as I'm no longer working on this project, but this information should hopefully prove useful to someone else in the future Commented Feb 5, 2017 at 22:01

Just tossing this in as part of the discussion on tiles, cached tiles, georeferencing, etc.

Tiling schemes from what I know don't use an XY georeferencing system per se...

For example, when you load the ESRI basemap into QGIS, it will create a tile stache folder structure like this:

When viewing the top level images, you have requested the tiles, and they are stored in those various folders.

Here's the contents of the f / d folder:

Notice no georeferencing information in the file (world file, etc.)

When you bring this file into MS paint, for example, you can manipulate the image, and re-save it:

...so when you view the map again in QGIS - which is rendering the tiles - you can see your 'edited' tile:

So I guess all I'm saying is if you had a set of tiles from google earth, you could in theory re-create the tile structure and simply put the tiles into those folders, and they would draw in QGIS...

It's the software itself that knows where to draw the tiles based on the caching scheme and folder structure...this again goes back to my days teaching ArcGIS Server courses.

You could then export them from QGIS with georeferencing information and use them elsewhere.

But then again, you could also just load google maps into QGIS and export the image as a TIF with a world file...

(Again, not really an answer, just some discussion...)

• sorry for the late reply. To be honest I can't even remember why I was asking this, as the project is long over ;) I do remember though that I needed to recreate whatever voodoo the software is doing when it knows how to place images based on the folder structure Commented Feb 5, 2017 at 21:59

Encountered this problem recently, you can use the code provided in the wiki.openstreetmap.org

Here is the Java Code:

``````    class BoundingBox {
double north;
double south;
double east;
double west;
}
BoundingBox tile2boundingBox(final int x, final int y, final int zoom) {
BoundingBox bb = new BoundingBox();
bb.north = tile2lat(y, zoom);
bb.south = tile2lat(y + 1, zoom);
bb.west = tile2lon(x, zoom);
bb.east = tile2lon(x + 1, zoom);
return bb;
}

static double tile2lon(int x, int z) {
return x / Math.pow(2.0, z) * 360.0 - 180;
}

static double tile2lat(int y, int z) {
double n = Math.PI - (2.0 * Math.PI * y) / Math.pow(2.0, z);
return Math.toDegrees(Math.atan(Math.sinh(n)));
}
``````

For the underlying mathematical approach, please see my answer to a related question here: calculating bounding box from known centre coordinate and zoom

You will need to be able to convert from tile coordinates and pixels within a tile to pixel coordinates, but that's not too hard.