Zoom Levels and Tile Grid
Purpose
TomTom Maps use the Spherical Mercator projection coordinate system ( EPSG:3857 ).
Zoom levels
The world is divided into square tiles.
- Maps Raster
- Map tiles has 23 zoom levels, numbered
0through22. - Satellite tiles has 20 zoom levels, numbered
0through19. - Hillshade tiles has 14 zoom levels, numbered
0through13.
- Map tiles has 23 zoom levels, numbered
- Maps Vector has 23 zoom levels, numbered
0through22.
At zoom level 0, the entire world fits on a single tile:
Zoom level 1 uses 4 tiles to render the world: a 2 x 2 square:
Each subsequent zoom level quad divides the tiles of the previous one, creating a grid of 2zoom x 2zoom. For example, zoom level 22 is a grid 222 x 222, or 4,194,304 x 4,194,304 tiles (result: 17,592,186,044,416 in total).
To discover the real-world size of a single tile on a given zoom level, we can use the formula circumference of earth / 2zoom level that produces number of meters per tile side, where the circumference of the earth equals 40,075,017 meters. The full data table of values for zoom levels is here:
zoom level | meters/pixel | meters/tile side |
|---|---|---|
| 156543 | 40075017 |
| 78271.5 | 20037508 |
| 39135.8 | 10018754 |
| 19567.88 | 5009377.1 |
| 9783.94 | 2504688.5 |
| 4891.97 | 1252344.3 |
| 2445.98 | 626172.1 |
| 1222.99 | 313086.1 |
| 611.5 | 156543 |
| 305.75 | 78271.5 |
| 152.87 | 39135.8 |
| 76.44 | 19567.9 |
| 38.219 | 9783.94 |
| 19.109 | 4891.97 |
| 9.555 | 2445.98 |
| 4.777 | 1222.99 |
| 2.3887 | 611.496 |
| 1.1943 | 305.748 |
| 0.5972 | 152.874 |
| 0.2986 | 76.437 |
| 0.14929 | 38.2185 |
| 0.074646 | 19.10926 |
| 0.037323 | 9.55463 |
Tile grid
Tiles are called by zoom level and the x and y coordinates corresponding to the tile’s position on the grid for that zoom level.
When determining which zoom level to use, remember that each location is in a fixed position on its tile.
- This means that the number of tiles needed to display a given expanse of territory is dependent on the specific placement of zoom grid on the world.
- For instance, if there are two points 900 meters apart, it may only take three tiles to display a route between them at zoom level
17.
However, if the western point is on the right side of its tile, and the eastern point on its left side, it may take four tiles as shown in the following diagram:
Once the zoom level is determined, the x and y coordinate values can be calculated:
- The top-left tile in each zoom grid is ,
**x=0**.**y=0** - The bottom-right tile is at ,
**x=2 zoom -1**.**y=2 zoom -1**
Here is the zoom grid for zoom level 1:
Coordinates conversion
Convert latitude/longitude coordinates to tile z/x/y coordinates.
| Coordinates | Zoom level | Result | |||||||
|---|---|---|---|---|---|---|---|---|---|
function latLonToTileZXY(lat, lon, zoomLevel) { const MIN_ZOOM_LEVEL = 0 const MAX_ZOOM_LEVEL = 22 const MIN_LAT = -85.051128779807 const MAX_LAT = 85.051128779806 const MIN_LON = -180.0 const MAX_LON = 180.0
if ( zoomLevel == undefined || isNaN(zoomLevel) || zoomLevel < MIN_ZOOM_LEVEL || zoomLevel > MAX_ZOOM_LEVEL ) { throw new Error( "Zoom level value is out of range [" + MIN_ZOOM_LEVEL.toString() + ", " + MAX_ZOOM_LEVEL.toString() + "]" ) }
if (lat == undefined || isNaN(lat) || lat < MIN_LAT || lat > MAX_LAT) { throw new Error( "Latitude value is out of range [" + MIN_LAT.toString() + ", " + MAX_LAT.toString() + "]" ) }
if (lon == undefined || isNaN(lon) || lon < MIN_LON || lon > MAX_LON) { throw new Error( "Longitude value is out of range [" + MIN_LON.toString() + ", " + MAX_LON.toString() + "]" ) }
let z = Math.trunc(zoomLevel) let xyTilesCount = Math.pow(2, z) let x = Math.trunc(Math.floor(((lon + 180.0) / 360.0) * xyTilesCount)) let y = Math.trunc( Math.floor( ((1.0 - Math.log( Math.tan((lat * Math.PI) / 180.0) + 1.0 / Math.cos((lat * Math.PI) / 180.0) ) / Math.PI) / 2.0) * xyTilesCount ) )
return z.toString() + "/" + x.toString() + "/" + y.toString()}std::string latLonToTileZXY(double lat, double lon, unsigned int z){ constexpr unsigned int MAX_ZOOM_LEVEL = 22; constexpr double MIN_LAT = -85.051128779807; constexpr double MAX_LAT = 85.051128779806; constexpr double MIN_LON = -180.0; constexpr double MAX_LON = 180.0;
if (z > MAX_ZOOM_LEVEL) { throw std::invalid_argument("Zoom level value is out of range [0, " + std::to_string(MAX_ZOOM_LEVEL) + "]"); }
if (!std::isfinite(lat) || (lat < MIN_LAT) || (lat > MAX_LAT)) { throw std::invalid_argument("Latitude value is out of range [" + std::to_string(MIN_LAT) + ", " + std::to_string(MAX_LAT) + "]"); }
if (!std::isfinite(lon) || (lon < MIN_LON) || (lon > MAX_LON)) { throw std::invalid_argument("Longitude value is out of range [" + std::to_string(MIN_LON) + ", " + std::to_string(MAX_LON) + "]"); }
const int xyTilesCount = 1 << z; int x = floor((lon + 180.0) / 360.0 * xyTilesCount); int y = floor((1.0 - log(tan(lat * M_PI / 180.0) + 1.0 / cos(lat * M_PI / 180.0)) / M_PI) / 2.0 * xyTilesCount);
return std::to_string(z) + "/" + std::to_string(x) + "/" + std::to_string(y);}public static String latLonToTileZXY(double lat, double lon, int z){ final int MIN_ZOOM_LEVEL = 0; final int MAX_ZOOM_LEVEL = 22; final double MIN_LAT = -85.051128779807; final double MAX_LAT = 85.051128779806; final double MIN_LON = -180.0; final double MAX_LON = 180.0;
if ((z < MIN_ZOOM_LEVEL) || (z > MAX_ZOOM_LEVEL)) { throw new IllegalArgumentException("Zoom level value is out of range [" + Integer.toString(MIN_ZOOM_LEVEL) + ", " + Integer.toString(MAX_ZOOM_LEVEL) + "]"); }
if (!Double.isFinite(lat) || (lat < MIN_LAT) || (lat > MAX_LAT)) { throw new IllegalArgumentException("Latitude value is out of range [" + Double.toString(MIN_LAT) + ", " + Double.toString(MAX_LAT) + "]"); }
if (!Double.isFinite(lon) || (lon < MIN_LON) || (lon > MAX_LON)) { throw new IllegalArgumentException("Longitude value is out of range [" + Double.toString(MIN_LON) + ", " + Double.toString(MAX_LON) + "]"); }
int xyTilesCount = (int)Math.pow(2, z); int x = (int) Math.floor((lon + 180.0) / 360.0 * xyTilesCount); int y = (int) Math.floor((1.0 - Math.log(Math.tan(lat * Math.PI / 180.0) + 1.0 / Math.cos(lat * Math.PI / 180.0)) / Math.PI) / 2.0 * xyTilesCount);
return Integer.toString(z) + "/" + Integer.toString(x) + "/" + Integer.toString(y);}Convert tile z/x/y coordinates to latitude/longitude coordinates.
| Tile coordinates | Result | ||||||||
|---|---|---|---|---|---|---|---|---|---|
function tileZXYToLatLon(zoomLevel, x, y) { const MIN_ZOOM_LEVEL = 0 const MAX_ZOOM_LEVEL = 22
if ( zoomLevel == undefined || isNaN(zoomLevel) || zoomLevel < MIN_ZOOM_LEVEL || zoomLevel > MAX_ZOOM_LEVEL ) { throw new Error( "Zoom level value is out of range [" + MIN_ZOOM_LEVEL.toString() + "," + MAX_ZOOM_LEVEL.toString() + "]" ) }
let z = Math.trunc(zoomLevel) let minXY = 0 let maxXY = Math.pow(2, z) - 1
if (x == undefined || isNaN(x) || x < minXY || x > maxXY) { throw new Error( "Tile x value is out of range [" + minXY.toString() + "," + maxXY.toString() + "]" ) }
if (y == undefined || isNaN(y) || y < minXY || y > maxXY) { throw new Error( "Tile y value is out of range [" + minXY.toString() + "," + maxXY.toString() + "]" ) }
let lon = (x / Math.pow(2, z)) * 360.0 - 180.0
let n = Math.PI - (2.0 * Math.PI * y) / Math.pow(2, z) let lat = (180.0 / Math.PI) * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)))
return lat.toString() + "/" + lon.toString()}std::string tileZXYToLatLon(unsigned int z, unsigned int x, unsigned int y){ constexpr unsigned int MAX_ZOOM_LEVEL = 22; if (z > MAX_ZOOM_LEVEL) { throw std::invalid_argument("Zoom level value is out of range [0, " + std::to_string(MAX_ZOOM_LEVEL) + "]"); }
const long maxXY = (1 << z) - 1; if (x > maxXY) { throw std::invalid_argument("Tile x value is out of range [0, " + std::to_string(maxXY) + "]"); }
if (y > maxXY) { throw std::invalid_argument("Tile y value is out of range [0, " + std::to_string(maxXY) + "]"); }
const int xyTilesCount = 1 << z; const double lon = static_cast<double>(x) / xyTilesCount * 360.0 - 180.0;
const double n = M_PI - 2.0 * M_PI * static_cast<double>(y) / xyTilesCount; const double lat = 180.0 / M_PI * atan(0.5 * (exp(n) - exp(-n)));
return std::to_string(lat) + "/" + std::to_string(lon);}public static String tileZXYToLatLon(int z, int x, int y){ final int MIN_ZOOM_LEVEL = 0; final int MAX_ZOOM_LEVEL = 22;
if ((z < MIN_ZOOM_LEVEL) || (z > MAX_ZOOM_LEVEL)) { throw new IllegalArgumentException("Zoom level value is out of range [" + Integer.toString(MIN_ZOOM_LEVEL) + ", " + Integer.toString(MAX_ZOOM_LEVEL) + "]"); }
int minXY = 0; int maxXY = (int)(Math.pow(2, z) - 1); if ((x < minXY) || (x > maxXY)) { throw new IllegalArgumentException("Tile x value is out of range [" + Integer.toString(minXY) + ", " + Integer.toString(maxXY) + "]"); }
if ((y < 0) || (y > maxXY)) { throw new IllegalArgumentException("Tile y value is out of range [" + Integer.toString(minXY) + ", " + Integer.toString(maxXY) + "]"); }
double lon = (double)x / Math.pow(2, z) * 360.0 - 180.0;
double n = Math.PI - 2.0 * Math.PI * (double)y / Math.pow(2, z); double lat = 180.0 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)));
return Double.toString(lat) + "/" + Double.toString(lon);}Convert tile z/x/y coordinates to latitude/longitude bounding box.
| Tile coordinates | Result | ||||||||
|---|---|---|---|---|---|---|---|---|---|
function tileZXYToLatLonBBox(zoomLevel, x, y) { const MIN_ZOOM_LEVEL = 0 const MAX_ZOOM_LEVEL = 22
if ( zoomLevel == undefined || isNaN(zoomLevel) || zoomLevel < MIN_ZOOM_LEVEL || zoomLevel > MAX_ZOOM_LEVEL ) { throw new Error( "Zoom level value is out of range [" + MIN_ZOOM_LEVEL.toString() + "," + MAX_ZOOM_LEVEL.toString() + "]" ) }
let z = Math.trunc(zoomLevel) let minXY = 0 let maxXY = Math.pow(2, z) - 1
if (x == undefined || isNaN(x) || x < minXY || x > maxXY) { throw new Error( "Tile x value is out of range [" + minXY.toString() + "," + maxXY.toString() + "]" ) }
if (y == undefined || isNaN(y) || y < minXY || y > maxXY) { throw new Error( "Tile y value is out of range [" + minXY.toString() + "," + maxXY.toString() + "]" ) }
let lon1 = (x / Math.pow(2, z)) * 360.0 - 180.0
let n1 = Math.PI - (2.0 * Math.PI * y) / Math.pow(2, z) let lat1 = (180.0 / Math.PI) * Math.atan(0.5 * (Math.exp(n1) - Math.exp(-n1)))
let lon2 = ((x + 1) / Math.pow(2, z)) * 360.0 - 180.0
let n2 = Math.PI - (2.0 * Math.PI * (y + 1)) / Math.pow(2, z) let lat2 = (180.0 / Math.PI) * Math.atan(0.5 * (Math.exp(n2) - Math.exp(-n2)))
return ( lat1.toString() + "/" + lon1.toString() + "/" + lat2.toString() + "/" + lon2.toString() )}std::string tileZXYToLatLonBBox(unsigned int z, unsigned int x, unsigned int y){ constexpr unsigned int MAX_ZOOM_LEVEL = 22; if (z > MAX_ZOOM_LEVEL) { throw std::invalid_argument("Zoom level value is out of range [0, " + std::to_string(MAX_ZOOM_LEVEL) + "]"); }
const long maxXY = (1 << z) - 1; if (x > maxXY) { throw std::invalid_argument("Tile x value is out of range [0, " + std::to_string(maxXY) + "]"); }
if (y > maxXY) { throw std::invalid_argument("Tile y value is out of range [0, " + std::to_string(maxXY) + "]"); }
const int xyTilesCount = 1 << z; const double lon1 = static_cast<double>(x) / xyTilesCount * 360.0 - 180.0;
const double n1 = M_PI - 2.0 * M_PI * static_cast<double>(y) / xyTilesCount; const double lat1 = 180.0 / M_PI * atan(0.5 * (exp(n1) - exp(-n1)));
const double lon2 = static_cast<double>(x + 1) / xyTilesCount * 360.0 - 180.0;
const double n2 = M_PI - 2.0 * M_PI * static_cast<double>(y + 1) / xyTilesCount; const double lat2 = 180.0 / M_PI * atan(0.5 * (exp(n2) - exp(-n2)));
return std::to_string(lat1) + "/" + std::to_string(lon1) + "/" + std::to_string(lat2) + "/" + std::to_string(lon2);}public static String tileZXYToLatLonBBox(int z, int x, int y){ final int MIN_ZOOM_LEVEL = 0; final int MAX_ZOOM_LEVEL = 22;
if ((z < MIN_ZOOM_LEVEL) || (z > MAX_ZOOM_LEVEL)) { throw new IllegalArgumentException("Zoom level value is out of range [" + Integer.toString(MIN_ZOOM_LEVEL) + ", " + Integer.toString(MAX_ZOOM_LEVEL) + "]"); }
int minXY = 0; int maxXY = (int)(Math.pow(2, z) - 1); if ((x < minXY) || (x > maxXY)) { throw new IllegalArgumentException("Tile x value is out of range [" + Integer.toString(minXY) + ", " + Integer.toString(maxXY) + "]"); }
if ((y < 0) || (y > maxXY)) { throw new IllegalArgumentException("Tile y value is out of range [" + Integer.toString(minXY) + ", " + Integer.toString(maxXY) + "]"); }
double lon1 = (double)x / Math.pow(2, z) * 360.0 - 180.0;
double n1 = Math.PI - 2.0 * Math.PI * (double)y / Math.pow(2, z); double lat1 = 180.0 / Math.PI * Math.atan(0.5 * (Math.exp(n1) - Math.exp(-n1)));
double lon2 = (double)(x + 1) / Math.pow(2, z) * 360.0 - 180.0;
double n2 = Math.PI - 2.0 * Math.PI * (double)(y + 1) / Math.pow(2, z); double lat2 = 180.0 / Math.PI * Math.atan(0.5 * (Math.exp(n2) - Math.exp(-n2)));
return Double.toString(lat1) + "/" + Double.toString(lon1) + "/" + Double.toString(lat2) + "/" + Double.toString(lon2);}