It has been proposed that this page or section be merged with TMS. (Discuss)
The first part of the URL specifies the tile server. The tile coordinates are typically specified by /zoom/x/y.png tail. Some tileservers will use a directory (e.g. "/cycle/") to specify a particular stylesheet. (Historically several subdomains were often provided to get around browser limitations on the number of simultaneous HTTP connections to each host - such as a.tile, b.tile, c.tile - but this is less important with modern browsers.)
Further tilesets are available from various '3rd party' sources.
Zoom levels
The zoom parameter is an integer between 0 (zoomed out) and 18 (zoomed in). 18 is normally the maximum, but some tile servers might go beyond that.
zoom level
tile coverage
number of tiles
tile size(*) in degrees
0
1 tile covers whole world
1 tile
360° x 170.1022°
1
2 × 2 tiles
4 tiles
180° x 85.0511°
2
4 × 4 tiles
16 tiles
90° x [variable]
n
2n × 2n tiles
22n tiles
360/2n° x [variable]
12
4096 x 4096 tiles
16 777 216
0.0879° x [variable]
16
232 ≈ 4 295 million tiles
17
17.2 billion tiles
18
68.7 billion tiles
19
Maximum zoom for Mapnik layer
274.9 billion tiles
(*) While the width (longitude) in degrees is constant, given a zoom level, for all tiles, this does not happen for the height. In general, tiles belonging to the same row have equal height in degrees, but it decreases moving from the equator to the poles.
This code returns the coordinate of the _upper left_ (northwest-most)-point of the tile.
Mathematics
Idem with mathematic signs (lat and lon in degrees):
Example: Convert a GPS coordinate to a pixel position in a Web Mercator tile
This example shows how to determine which tile (and what pixel coordinate within the tile)
the Hachiko Statue near the Shibuya scramble crossing can be found in:
This value will fall in the range (-π, π) for latitudes between 85.0511 °S and 85.0511 °N.
For the curious, the number 85.0511 is the result of arctan(sinh(π)). By using this bound, the entire map becomes a (very large) square.
0.66693624687
3. Transform the projected point onto the unit square
Variable
Formula
Example (Hachiko Statue)
x
0.5 + xEPSG:3857 / 360°
This value will fall in the range (0, 1).
x=0 is the left (180° west) edge of the map.
x=1 is the right (180° east) edge of the map.
x=0.5 is the middle, the prime meridian.
0.8880574425
y
0.5 − yEPSG:3857 / (2π)
This value will fall in the range (0, 1).
y=0 is the top (north) edge of the map, at 85.0511 °N.
y=1 is the bottom (south) edge of the map, at 85.0511 °S.
y=0.5 is the middle, the equator.
0.3938537996
4. Determine the zoom level and horizontal/vertical location of individual tiles
Variable
Formula
Example (Hachiko Statue)
zoom
At a zoom level of 0, the entire earth is shown in a single tile.
At a zoom level of 18, detail is visible in individual city blocks.
18
N
2zoom
This is the number of tiles horizontally or vertically from one edge of the map to the other.
262144
xtile
N * x
The whole-number part of xtile is the "x" value of the tile overall.
The fractional part of xtile indicates the internal horizontal position of the coordinate within that tile.
The first tile will have a xtile=0, with a left edge at 180° W.
The last tile will have a xtile=2zoom−1, with a right edge at 180° E.
232798.930207
tile x = 232798
fractional x = 93.02%
ytile
N * y
The whole-number part of ytile is the "y" value of the tile overall.
The fractional part of ytile indicates the internal vertical position of the coordinate within that tile.
The first tile will have a ytile=0, with a top edge at 85.0511 °N.
The last tile will have a ytile=2zoom−1, with a bottom edge at 85.0511 °S.
103246.410442
tile y = 103246
fractional y = 41.04%
This results in the following tile URL:
Variable
Formula
Example (Hachiko Statue)
URL
Most tile servers use a path similar to /(zoom)/(xtile)/(ytile).png.
This returns the NW-corner of the square. Use the function with xtile+1 and/or ytile+1 to get the other corners. With xtile+0.5 & ytile+0.5 it will return the center of the tile.
Same as the Python implementation above, this returns the NW-corner of the square. Use the function with xtile+1 and/or ytile+1 to get the other corners. With xtile+0.5 & ytile+0.5 it will return the center of the tile.
useMath::Trig;subProject{my($X,$Y,$Zoom)=@_;my$Unit=1/(2**$Zoom);my$relY1=$Y*$Unit;my$relY2=$relY1+$Unit;# note: $LimitY = ProjectF(degrees(atan(sinh(pi)))) = log(sinh(pi)+cosh(pi)) = pi# note: degrees(atan(sinh(pi))) = 85.051128..#my $LimitY = ProjectF(85.0511);# so stay simple and more accuratemy$LimitY=pi;my$RangeY=2*$LimitY;$relY1=$LimitY-$RangeY*$relY1;$relY2=$LimitY-$RangeY*$relY2;my$Lat1=ProjectMercToLat($relY1);my$Lat2=ProjectMercToLat($relY2);$Unit=360/(2**$Zoom);my$Long1=-180+$X*$Unit;return($Lat2,$Long1,$Lat1,$Long1+$Unit);# S,W,N,E}subProjectMercToLat($){my$MercY=shift;returnrad2deg(atan(sinh($MercY)));}subProjectF{my$Lat=shift;$Lat=deg2rad($Lat);my$Y=log(tan($Lat)+sec($Lat));return$Y;}
Lon./lat. to bbox
useMath::Trig;subgetTileNumber{my($lat,$lon,$zoom)=@_;my$xtile=int(($lon+180)/360*2**$zoom);my$ytile=int((1-log(tan(deg2rad($lat))+sec(deg2rad($lat)))/pi)/2*2**$zoom);return($xtile,$ytile);}subgetLonLat{my($xtile,$ytile,$zoom)=@_;my$n=2**$zoom;my$lon_deg=$xtile/$n*360.0-180.0;my$lat_deg=rad2deg(atan(sinh(pi*(1-2*$ytile/$n))));return($lon_deg,$lat_deg);}# convert from permalink OSM format like:# https://www.openstreetmap.org/?lat=43.731049999999996&lon=15.79375&zoom=13&layers=M# to OSM "Export" iframe embedded bbox format like:# https://www.openstreetmap.org/export/embed.html?bbox=15.7444,43.708,15.8431,43.7541&layer=mapniksubLonLat_to_bbox{my($lat,$lon,$zoom)=@_;my$width=425;my$height=350;# note: must modify this to match your embed map width/height in pixelsmy$tile_size=256;my($xtile,$ytile)=getTileNumber($lat,$lon,$zoom);my$xtile_s=($xtile*$tile_size-$width/2) /$tile_size;my$ytile_s=($ytile*$tile_size-$height/2) /$tile_size;my$xtile_e=($xtile*$tile_size+$width/2) /$tile_size;my$ytile_e=($ytile*$tile_size+$height/2) /$tile_size;my($lon_s,$lat_s)=getLonLat($xtile_s,$ytile_s,$zoom);my($lon_e,$lat_e)=getLonLat($xtile_e,$ytile_e,$zoom);my$bbox="$lon_s,$lat_s,$lon_e,$lat_e";return$bbox;}
constEARTH_CIR_METERS=40075016.686;constTILE_SIZE=256constdegreesPerMeter=360/EARTH_CIR_METERS;constLIMIT_Y=toDegrees(Math.atan(Math.sinh(Math.PI)))// around 85.0511...functiontoRadians(degrees){returndegrees*Math.PI/180;}functiontoDegrees(radians){return(radians/Math.PI)*180}functiontile2long(x,z){return(x/Math.pow(2,z)*360-180);}functionlonOnTile(lon,zoom){return((lon+180)/360)*Math.pow(2,zoom)}functiontile2lat(y,z){constn=Math.PI-2*Math.PI*y/Math.pow(2,z);return(180/Math.PI*Math.atan(0.5*(Math.exp(n)-Math.exp(-n))));}functionlatOnTile(lat,zoom){return(((1-Math.log(Math.tan((lat*Math.PI)/180)+1/Math.cos((lat*Math.PI)/180))/Math.PI)/2)*Math.pow(2,zoom))}functionlatLngToBounds(lat,lng,zoom,width,height){// width and height must correspond to the iframe width/heightconstmetersPerPixelEW=EARTH_CIR_METERS/Math.pow(2,zoom+8);constshiftMetersEW=width/2*metersPerPixelEW;constshiftDegreesEW=shiftMetersEW*degreesPerMeter;constsouthTile=(TILE_SIZE*latOnTile(lat,zoom)+height/2)/TILE_SIZEconstnorthTile=(TILE_SIZE*latOnTile(lat,zoom)-height/2)/TILE_SIZEreturn{south:Math.max(tile2lat(southTile,zoom),-LIMIT_Y),west:lng-shiftDegreesEW,north:Math.min(tile2lat(northTile,zoom),LIMIT_Y),east:lng+shiftDegreesEW}}// Usage Example: create the src attribute for Open Street Map:constlatitude=47constlongitude=12constzoom=16constwidth=450constheight=350constbb=latLngToBounds(latitude,longitude,zoom,width,height);constsrc=["https://www.openstreetmap.org/export/embed.html?bbox=",bb.west,",",bb.south,",",bb.east,",",bb.north,"&layer=mapnik&marker=",latitude,",",longitude,].join('');
<xsl:transformxmlns:xsl="http://www.w3.org/1999/XSL/Transform"xmlns:m="http://exslt.org/math"extension-element-prefixes="m"version="1.0"><xsl:outputmethod="text"/><xsl:variablename="pi"select="3.14159265358979323846"/><xsl:templatename="tiley"><xsl:paramname="lat"/><xsl:paramname="zoomfact"/><xsl:variablename="a"select="($lat * $pi) div 180.0"/><xsl:variablename="b"select="m:log(m:tan($a) + (1.0 div m:cos($a)))"/><xsl:variablename="c"select="(1.0 - ($b div $pi)) div 2.0"/><xsl:value-ofselect="floor($c * $zoomfact)"/></xsl:template><xsl:templatename="tilename"><xsl:paramname="lat"/><xsl:paramname="lon"/><xsl:paramname="zoom"select="10"/><xsl:variablename="zoomfact"select="m:power(2,$zoom)"/><xsl:variablename="x"select="floor((360.0 + ($lon * 2)) * $zoomfact div 720.0)"/><xsl:variablename="y"><xsl:call-templatename="tiley"><xsl:with-paramname="lat"select="$lat"/><xsl:with-paramname="zoomfact"select="$zoomfact"/></xsl:call-template></xsl:variable><xsl:value-ofselect="concat($zoom,'/',$x,'/',$y)"/></xsl:template><xsl:templatematch="/"><xsl:call-templatename="tilename"><xsl:with-paramname="lat"select="49.867731999999997"/><xsl:with-paramname="lon"select="8.6295369999999991"/><xsl:with-paramname="zoom"select="14"/></xsl:call-template></xsl:template></xsl:transform>
function osmTileRef iLat, iLong, iZoom --> part path
local n, xTile, yTile
put (2 ^ iZoom) into n
put (iLong + 180) / 360 * n into xTile
multiply iLat by (pi / 180) -- convert to radians
put ((1 - ln(tan(iLat) + 1 / cos(iLat)) / pi) / 2) * n into yTile
return "/" & iZoom & "/" & trunc(xTile) & "/" & trunc(yTile)
end osmTileRef
function osmTileCoords xTile, yTile, iZoom --> coordinates
local twoPzoom, iLong, iLat, n
put (2 ^ iZoom) into twoPzoom
put xTile / twoPzoom * 360 - 180 into iLong
put pi - 2 * pi * yTile / twoPzoom into n
put "n1=" && n
put 180 / pi * atan(0.5 * (exp(n) - exp(-n))) into iLat
return iLat & comma & iLong
end osmTileCoords
deg2num<-function(lat_deg,lon_deg,zoom){lat_rad<-lat_deg*pi/180n<-2.0^zoomxtile<-floor((lon_deg+180.0)/360.0*n)ytile=floor((1.0-log(tan(lat_rad)+(1/cos(lat_rad)))/pi)/2.0*n)return(c(xtile,ytile))# return(paste(paste("https://tile.openstreetmap.org", zoom, xtile, ytile, sep="/"),".png",sep=""))}# Returns data frame containing detailed info for all zoomsdeg2num.all<-function(lat_deg,lon_deg){nums<-as.data.frame(matrix(ncol=6,nrow=21))colnames(nums)<-c('zoom','x','y','mapquest_osm','mapquest_aerial','osm')rownames(nums)<-0:20for (zoomin0:20){num<-deg2num(lat_deg,lon_deg,zoom)nums[1+zoom,'zoom']<-zoomnums[1+zoom,'x']<-num[1]nums[1+zoom,'y']<-num[2]nums[1+zoom,'mapquest_osm']<-paste('http://otile1.mqcdn.com/tiles/1.0.0/map/',zoom,'/',num[1],'/',num[2],'.jpg',sep='')nums[1+zoom,'mapquest_aerial']<-paste('http://otile1.mqcdn.com/tiles/1.0.0/sat/',zoom,'/',num[1],'/',num[2],'.jpg',sep='')nums[1+zoom,'osm']<-paste('https://tile.openstreetmap.org/',zoom,'/',num[1],'/',num[2],'.png',sep='')}return(nums)}
Tile numbers to lat./lon. / Coordinates to tile numbers / Sample of usage, with optional tms-format support
xtile2long(){xtile=$1zoom=$2echo"${xtile}${zoom}"|awk'{printf("%.9f", $1 / 2.0^$2 * 360.0 - 180)}'}
long2xtile(){long=$1zoom=$2echo"${long}${zoom}"|awk'{ xtile = ($1 + 180.0) / 360 * 2.0^$2; xtile+=xtile<0?-0.5:0.5; printf("%d", xtile ) }'}
ytile2lat(){ytile=$1;zoom=$2;tms=$3;if[!-z"${tms}"]then# from tms_numbering into osm_numberingytile=`echo"${ytile}"${zoom}|awk'{printf("%d\n",((2.0^$2)-1)-$1)}'`;filat=`echo"${ytile}${zoom}"|awk-vPI=3.14159265358979323846'{ num_tiles = PI - 2.0 * PI * $1 / 2.0^$2; printf("%.9f", 180.0 / PI * atan2(0.5 * (exp(num_tiles) - exp(-num_tiles)),1)); }'`;echo"${lat}";}
lat2ytile(){lat=$1;zoom=$2;tms=$3;ytile=`echo"${lat}${zoom}"|awk-vPI=3.14159265358979323846'{ tan_x=sin($1 * PI / 180.0)/cos($1 * PI / 180.0); ytile = (1 - log(tan_x + 1/cos($1 * PI/ 180))/PI)/2 * 2.0^$2; ytile+=ytile<0?-0.5:0.5; printf("%d", ytile ) }'`;if[!-z"${tms}"]then# from oms_numbering into tms_numberingytile=`echo"${ytile}"${zoom}|awk'{printf("%d\n",((2.0^$2)-1)-$1)}'`;fiecho"${ytile}";}# ------------------------------------# Sample of use: # Position Brandenburg Gate, Berlin# ------------------------------------LONG=13.37771496361961;LAT=52.51628011262304;ZOOM=17;TILE_X=70406;TILE_Y=42987;TILE_Y_TMS=88084;TMS="";# when NOT empty: tms format assumed# ------------------------------------# assume input/output of y is in oms-format:LONG=$(xtile2long${TILE_X}${ZOOM});LAT=$(ytile2lat${TILE_Y}${ZOOM}${TMS});# Result should be longitude[13.375854492] latitude[52.517892228]TILE_X=$(long2xtile${LONG}${ZOOM});TILE_Y=$(lat2ytile${LAT}${ZOOM}${TMS});# Result should be x[70406] y_oms[42987] # ------------------------------------# assume input/output of y is in tms-format:TMS="tms";TILE_Y_TMS=$(lat2ytile${LAT}${ZOOM}${TMS});LAT_TMS=$(ytile2lat${TILE_Y_TMS}${ZOOM}${TMS});echo"Result should be y_oms[${TILE_Y}] latitude[${LAT}] ; y_tms[${TILE_Y_TMS}] latitude_tms[${LAT_TMS}] "# latitude and latitude_tms should have the same value ; y_oms and y_tms should have the given start values:# Result should be y_oms[42987] latitude[52.517892228] ; y_tms[88084] latitude_tms[52.517892228]# ------------------------------------
Tile bounding box and center
n=$(ytile2lat`expr${TILE_Y}`${ZOOM})s=$(ytile2lat`expr${TILE_Y}+1`${ZOOM})e=$(xtile2long`expr${TILE_X}+1`${ZOOM})w=$(xtile2long`expr${TILE_X}`${ZOOM})echo"bbox=$w,$s,$e,$n"echo"-I-> Result should be [bbox=13.375854492,52.516220864,13.378601074,52.517892228]";center_lat=`echo"$s$n"|awk'{printf("%.8f", ($1 + $2) / 2.0)}'`center_lon=`echo"$w$e"|awk'{printf("%.8f", ($1 + $2) / 2.0)}'`echo"center=$center_lat,$center_lon"echo"-I-> Result should be [center=52.51705655,13.37722778]";
If you're looking at tile x,y and want to zoom in, the subtiles are (in the next zoom-level's coordinate system):
2x, 2y
2x + 1, 2y
2x, 2y + 1
2x + 1, 2y + 1
Similarly, zoom out by halving x and y (in the previous zoom level)
Resolution and Scale
Exact length of the equator (according to Wikipedia) is 40075.016686 km in WGS-84. At zoom 0, one pixel would equal 156543.03 meters (assuming a tile size of 256 px):
And here is the table to rid you of those calculations. All values are shown for equator, and you have to multiply them by cos(latitude) to adjust to a given latitude. For example, divide those by 2 for latitude 60 (Oslo, Helsinki, Saint-Petersburg).
(note: Slippy tiles and Google map tiles count tile 0,0 down from the top-left of the tile grid; the TMS spec specifies tiles count up from 0,0 in the lower-left!)