This article describes the file naming conventions for the Slippy Map application.
The slippy map expects tiles to be served up at URLs following this scheme, so all tile server URLs look pretty similar. 目录[隐藏]
Tile servers
The first part of the URL specifies the tile server, and perhaps other parameters which might influence the style.
That all comes before the Here are some examples:
Further tilesets are available from various '3rd party' sources. Zoom levelsThe 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.
See Zoom levels for more details X and Y
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. Derivation of tile names
ImplementationsPseudo-codeFor those who like pseudo-code, here's some hints: sec = 1/cos arsinh(x) = log(x + (x^2 + 1)^0.5) sec^2(x) = tan^2(x) + 1 → arsinh(tan(x)) = log(tan(x) + sec(x)) Please note that "log" represents the natural logarithm (also known as ln or loge), not decimal logarithm (log10), as used on some calculators. Lon./lat. to tile numbersn = 2 ^ zoom xtile = n * ((lon_deg + 180) / 360) ytile = n * (1 - (log(tan(lat_rad) + sec(lat_rad)) / π)) / 2 Tile numbers to lon./lat.n = 2 ^ zoom lon_deg = xtile / n * 360.0 - 180.0 lat_rad = arctan(sinh(π * (1 - 2 * ytile / n))) lat_deg = lat_rad * 180.0 / π MathematicsIdem with mathematic signs (lat and lon in degrees): PythonLon./lat. to tile numbersimport math def deg2num(lat_deg, lon_deg, zoom): lat_rad = math.radians(lat_deg) n = 2.0 ** zoom xtile = int((lon_deg + 180.0) / 360.0 * n) ytile = int((1.0 - math.log(math.tan(lat_rad) + (1 / math.cos(lat_rad))) / math.pi) / 2.0 * n) return (xtile, ytile) Tile numbers to lon./lat.import math def num2deg(xtile, ytile, zoom): n = 2.0 ** zoom lon_deg = xtile / n * 360.0 - 180.0 lat_rad = math.atan(math.sinh(math.pi * (1 - 2 * ytile / n))) lat_deg = math.degrees(lat_rad) return (lat_deg, lon_deg) 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. See also tilenames.py and the 'mercantile' library RubyLon./lat. to tile numbersdef get_tile_number(lat_deg, lng_deg, zoom) lat_rad = lat_deg/180 * Math::PI n = 2.0 ** zoom x = ((lng_deg + 180.0) / 360.0 * n).to_i y = ((1.0 - Math::log(Math::tan(lat_rad) + (1 / Math::cos(lat_rad))) / Math::PI) / 2.0 * n).to_i {:x => x, :y =>y} end Tile numbers to lon./lat.def get_lat_lng_for_number(xtile, ytile, zoom) n = 2.0 ** zoom lon_deg = xtile / n * 360.0 - 180.0 lat_rad = Math::atan(Math::sinh(Math::PI * (1 - 2 * ytile / n))) lat_deg = 180.0 * (lat_rad / Math::PI) {:lat_deg => lat_deg, :lng_deg => lon_deg} end 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. PerlLon./lat. to tile numbersuse Math::Trig; sub getTileNumber { 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); } Tile numbers to lon./lat.use Math::Trig; sub Project { 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 accurate my $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 } sub ProjectMercToLat($){ my $MercY = shift; return rad2deg(atan(sinh($MercY))); } sub ProjectF { my $Lat = shift; $Lat = deg2rad($Lat); my $Y = log(tan($Lat) + sec($Lat)); return $Y; } Lon./lat. to bboxuse Math::Trig; sub getTileNumber { 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); } sub getLonLat { 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: # http://www./?lat=43.731049999999996&lon=15.79375&zoom=13&layers=M # to OSM "Export" iframe embedded bbox format like: # http://www./export/embed.html?bbox=15.7444,43.708,15.8431,43.7541&layer=mapnik sub LonLat_to_bbox { my ($lat, $lon, $zoom) = @_; my $width = 425; my $height = 350; # note: must modify this to match your embed map width/height in pixels my $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; } PHPLon./lat. to tile numbers$xtile = floor((($lon + 180) / 360) * pow(2, $zoom)); $ytile = floor((1 - log(tan(deg2rad($lat)) + 1 / cos(deg2rad($lat))) / pi()) /2 * pow(2, $zoom)); Tile numbers to lon./lat.$n = pow(2, $zoom); $lon_deg = $xtile / $n * 360.0 - 180.0; $lat_deg = rad2deg(atan(sinh(pi() * (1 - 2 * $ytile / $n)))); ECMAScript (JavaScript/ActionScript, etc.)function long2tile(lon,zoom) { return (Math.floor((lon+180)/360*Math.pow(2,zoom))); } function lat2tile(lat,zoom) { return (Math.floor((1-Math.log(Math.tan(lat*Math.PI/180) + 1/Math.cos(lat*Math.PI/180))/Math.PI)/2 *Math.pow(2,zoom))); } Inverse process: function tile2long(x,z) { return (x/Math.pow(2,z)*360-180); } function tile2lat(y,z) { var n=Math.PI-2*Math.PI*y/Math.pow(2,z); return (180/Math.PI*Math.atan(0.5*(Math.exp(n)-Math.exp(-n)))); } Example: Tilesname WebCalc V1.0 C/C++int long2tilex(double lon, int z) { return (int)(floor((lon + 180.0) / 360.0 * pow(2.0, z))); } int lat2tiley(double lat, int z) { return (int)(floor((1.0 - log( tan(lat * M_PI/180.0) + 1.0 / cos(lat * M_PI/180.0)) / M_PI) / 2.0 * pow(2.0, z))); } double tilex2long(int x, int z) { return x / pow(2.0, z) * 360.0 - 180; } double tiley2lat(int y, int z) { double n = M_PI - 2.0 * M_PI * y / pow(2.0, z); return 180.0 / M_PI * atan(0.5 * (exp(n) - exp(-n))); } Goimport ( "math" ) type Tile struct { Z int X int Y int Lat float64 Long float64 } type Conversion interface { deg2num(t *Tile) (x int, y int) num2deg(t *Tile) (lat float64, long float64) } func (*Tile) Deg2num(t *Tile) (x int, y int) { x = int(math.Floor((t.Long + 180.0) / 360.0 * (math.Exp2(float64(t.Z))))) y = int(math.Floor((1.0 - math.Log(math.Tan(t.Lat*math.Pi/180.0)+1.0/math.Cos(t.Lat*math.Pi/180.0))/math.Pi) / 2.0 * (math.Exp2(float64(t.Z))))) return } func (*Tile) Num2deg(t *Tile) (lat float64, long float64) { n := math.Pi - 2.0*math.Pi*float64(t.Y)/math.Exp2(float64(t.Z)) lat = 180.0 / math.Pi * math.Atan(0.5*(math.Exp(n)-math.Exp(-n))) long = float64(t.X)/math.Exp2(float64(t.Z))*360.0 - 180.0 return lat, long } Javapublic class slippytest { public static void main(String[] args) { int zoom = 10; double lat = 47.968056d; double lon = 7.909167d; System.out.println("http://tile./" + getTileNumber(lat, lon, zoom) + ".png"); } public static String getTileNumber(final double lat, final double lon, final int zoom) { int xtile = (int)Math.floor( (lon + 180) / 360 * (1<<zoom) ) ; int ytile = (int)Math.floor( (1 - Math.log(Math.tan(Math.toRadians(lat)) + 1 / Math.cos(Math.toRadians(lat))) / Math.PI) / 2 * (1<<zoom) ) ; if (xtile < 0) xtile=0; if (xtile >= (1<<zoom)) xtile=((1<<zoom)-1); if (ytile < 0) ytile=0; if (ytile >= (1<<zoom)) ytile=((1<<zoom)-1); return("" + zoom + "/" + xtile + "/" + ytile); } } Tile bounding boxclass 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))); } VB.NetPrivate Function CalcTileXY(ByVal lat As Single, ByVal lon As Single, ByVal zoom As Long) As Point CalcTileXY.X = CLng(Math.Floor((lon + 180) / 360 * 2 ^ zoom)) CalcTileXY.Y = CLng(Math.Floor((1 - Math.Log(Math.Tan(lat * Math.PI / 180) + 1 / Math.Cos(lat * Math.PI / 180)) / Math.PI) / 2 * 2 ^ zoom)) End Function C#public PointF WorldToTilePos(double lon, double lat, int zoom) { PointF p = new Point(); p.X = (float)((lon + 180.0) / 360.0 * (1 << zoom)); p.Y = (float)((1.0 - Math.Log(Math.Tan(lat * Math.PI / 180.0) + 1.0 / Math.Cos(lat * Math.PI / 180.0)) / Math.PI) / 2.0 * (1 << zoom)); return p; } public PointF TileToWorldPos(double tile_x, double tile_y, int zoom) { PointF p = new Point(); double n = Math.PI - ((2.0 * Math.PI * tile_y) / Math.Pow(2.0, zoom)); p.X = (float)((tile_x / Math.Pow(2.0, zoom) * 360.0) - 180.0); p.Y = (float)(180.0 / Math.PI * Math.Atan(Math.Sinh(n))); return p; } XSLTRequires math extensions from . <xsl:transform xmlns:xsl="http://www./1999/XSL/Transform" xmlns:m="http:///math" extension-element-prefixes="m" version="1.0"> <xsl:output method="text"/> <xsl:variable name="pi" select="3.14159265358979323846"/> <xsl:template name="tiley"> <xsl:param name="lat"/> <xsl:param name="zoomfact"/> <xsl:variable name="a" select="($lat * $pi) div 180.0"/> <xsl:variable name="b" select="m:log(m:tan($a) + (1.0 div m:cos($a)))"/> <xsl:variable name="c" select="(1.0 - ($b div $pi)) div 2.0"/> <xsl:value-of select="floor($c * $zoomfact)"/> </xsl:template> <xsl:template name="tilename"> <xsl:param name="lat"/> <xsl:param name="lon"/> <xsl:param name="zoom" select="10"/> <xsl:variable name="zoomfact" select="m:power(2,$zoom)"/> <xsl:variable name="x" select="floor((360.0 + ($lon * 2)) * $zoomfact div 720.0)"/> <xsl:variable name="y"> <xsl:call-template name="tiley"> <xsl:with-param name="lat" select="$lat"/> <xsl:with-param name="zoomfact" select="$zoomfact"/> </xsl:call-template> </xsl:variable> <xsl:value-of select="concat($zoom,'/',$x,'/',$y)"/> </xsl:template> <xsl:template match="/"> <xsl:call-template name="tilename"> <xsl:with-param name="lat" select="49.867731999999997"/> <xsl:with-param name="lon" select="8.6295369999999991"/> <xsl:with-param name="zoom" select="14"/> </xsl:call-template> </xsl:template> </xsl:transform> Haskell-- https://github.com/j4/HSlippyMap long2tilex lon z = floor((lon + 180.0) / 360.0 * (2.0 ** z)) lat2tiley lat z = floor((1.0 - log( tan(lat * pi/180.0) + 1.0 / cos(lat * pi/180.0)) / pi) / 2.0 * (2.0 ** z)) tilex2long x z = x / (2.0 ** z) * 360.0 - 180 tiley2lat y z = 180.0 / pi * atan(0.5 * (exp(n) - exp(-n))) where n = pi - 2.0 * pi * y / (2.0 ** z) -- Example main = do --print $ long2tilex 2.2712 17 --print $ lat2tiley 48.8152 17 --print $ tilex2long 66362 17 --print $ tiley2lat 45115 17 putStrLn "gps: (lat=48.8152,long=2.2712)" putStrLn $ "http://tile./17/" ++ show x ++ "/" ++ show y ++ ".png" where z = 17 x = long2tilex 2.2712 z y = lat2tiley 48.8152 z Scalaimport scala.math._ case class Tile(x: Int,y: Int, z: Short){ def toLatLon = new LatLonPoint( toDegrees(atan(sinh(Pi * (1.0 - 2.0 * y.toDouble / (1<<z))))), x.toDouble / (1<<z) * 360.0 - 180.0, z) def toURI = new java.net.URI("http://tile./"+z+"/"+x+"/"+y+".png") } case class LatLonPoint(lat: Double, lon: Double, z: Short){ def toTile = new Tile( ((lon + 180.0) / 360.0 * (1<<z)).toInt, ((1 - log(tan(toRadians(lat)) + 1 / cos(toRadians(lat))) / Pi) / 2.0 * (1<<z)).toInt, z) } //Usage: val point = LatLonPoint(51.51202,0.02435,17) val tile = point.toTile // ==> Tile(65544,43582,17) val uri = tile.toURI // ==> http://tile./17/65544/43582.png Revolution/Transcriptfunction 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 MathematicaDeg2Num[lat_, lon_, zoom_] := {IntegerPart[(2^(-3 + zoom)*(180 + lon))/45], IntegerPart[2^(-1 + zoom)*(1 - Log[Sec[Degree*lat] + Tan[Degree*lat]]/Pi)]} Num2Deg[xtile_,ytile_,zoom_] := {ArcTan[Sinh[Pi*(1 - 2*(ytile/2^zoom))]]/Degree, (xtile/2^zoom)*360 - 180} // N TclFirst of all, you need to use the package map::slippy from Tcllib: package require map::slippy Lat./lon. to tile numbermap::slippy geo 2tile [list $zoom $lat $lon] Tile number to lat/lonmap::slippy tile 2geo [list $zoom $row $col] Pascal(translated from the Pythoncode above to Pascal) Coordinates to tile numbersuses {...}, Math; {...} var zoom: Integer; lat_rad, lat_deg, lon_deg, n: Real; begin lat_rad := DegToRad(lat_deg); n := Power(2, zoom); xtile := Trunc(((lon_deg + 180) / 360) * n); ytile := Trunc((1 - (ln(Tan(lat_rad) + (1 /Cos(lat_rad))) / Pi)) / 2 * n); end; Tile numbers to coordinatesuses {...}, Math; {...} var lat_rad, n: Real; begin n := Power(2, zoom); lat_rad := Arctan (Sinh (Pi * (1 - 2 * ytile / n))); lat_deg := RadtoDeg (lat_rad); lon_deg := xtile / n * 360.0 - 180.0; end; RCoordinates to tile numbersdeg2num<-function(lat_deg, lon_deg, zoom){ lat_rad <- lat_deg * pi /180 n <- 2.0 ^ zoom xtile <- 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("http://a.tile.", zoom, xtile, ytile, sep="/"),".png",sep="")) } # Returns data frame containing detailed info for all zooms deg2num.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:20 for (zoom in 0:20) { num <- deg2num(lat_deg, lon_deg, zoom) nums[1+zoom,'zoom'] <- zoom nums[1+zoom,'x'] <- num[1] nums[1+zoom,'y'] <- num[2] nums[1+zoom,'mapquest_osm'] <- paste('http://otile1./tiles/1.0.0/map/', zoom, '/', num[1], '/', num[2], '.jpg', sep='') nums[1+zoom,'mapquest_aerial'] <- paste('http://otile1./tiles/1.0.0/sat/', zoom, '/', num[1], '/', num[2], '.jpg', sep='') nums[1+zoom,'osm'] <- paste('http://a.tile./', zoom, '/', num[1], '/', num[2], '.png', sep='') } return(nums) } Bourne shell with AwkTile numbers to lat./lon. / Coordinates to tile numbers / Sample of usage, with optional tms-format supportxtile2long() { xtile=$1 zoom=$2 echo "${xtile} ${zoom}" | awk '{printf("%.9f", $1 / 2.0^$2 * 360.0 - 180)}' } long2xtile() { long=$1 zoom=$2 echo "${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_numbering ytile=`echo "${ytile}" ${zoom} | awk '{printf("%d\n",((2.0^$2)-1)-$1)}'`; fi lat=`echo "${ytile} ${zoom}" | awk -v PI=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 -v PI=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_numbering ytile=`echo "${ytile}" ${zoom} | awk '{printf("%d\n",((2.0^$2)-1)-$1)}'`; fi echo "${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 centern=$(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]"; OctaveLon./lat. to tile numbers% convert the degrees to radians rho = pi/180; lon_rad = lon_deg * rho; lat_rad = lat_deg * rho; n = 2 ^ zoom xtile = n * ((lon_deg + 180) / 360) ytile = n * (1 - (log(tan(lat_rad) + sec(lat_rad)) / pi)) / 2 Tile numbers to lon./lat.n=2^zoom lon_deg = xtile / n * 360.0 - 180.0 lat_rad = arctan(sinh(pi * (1 - 2 * ytile / n))) lat_deg = lat_rad * 180.0 / pi Emacs-lisp(defun longitude2tile (lon zoom) (* (expt 2 zoom) (/ (+ lon 180) 360))) (defun tile2longitude (x zoom) (- (/ (* x 360) (expt 2 zoom)) 180)) (defun latitude2tile (lat zoom) (* (expt 2 zoom) (/ (- 1 (/ (log (+ (tan (/ (* lat pi) 180)) (/ 1 (cos (/ (* lat pi) 180))))) pi)) 2))) (defun sinh (value) (/ (- (exp value) (exp (- value))) 2)) (defun tile2latitude (y zoom) (/ (* 180 (atan (sinh (* pi (- 1 (* 2 (/ y (expt 2 zoom)))))))) pi)) Erlang-module(slippymap). -export([deg2num/3]). -export([num2deg/3]). deg2num(Lat,Lon,Zoom)-> X=math:pow(2, Zoom) * ((Lon + 180) / 360), Sec=1/math:cos(deg2rad(Lat)), R = math:log(math:tan(deg2rad(Lat)) + Sec)/math:pi(), Y=math:pow(2, Zoom) * (1 - R) / 2, {round(X),round(Y)}. num2deg(X,Y,Zoom)-> N=math:pow(2, Zoom), Lon=X/N*360-180, Lat_rad=math:atan(math:sinh(math:pi()*(1-2*Y/N))), Lat=Lat_rad*180/math:pi(), {Lon,Lat}. deg2rad(C)-> C*math:pi()/180. Luafunction deg2num(lon, lat, zoom) local n = 2 ^ zoom local lon_deg = tonumber(lon) local lat_rad = math.rad(lat) local xtile = math.floor(n * ((lon_deg + 180) / 360)) local ytile = math.floor(n * (1 - (math.log(math.tan(lat_rad) + (1 / math.cos(lat_rad))) / math.pi)) / 2) return xtile, ytile end function num2deg(x, y, z) local n = 2 ^ z local lon_deg = x / n * 360.0 - 180.0 local lat_rad = math.atan(math.sinh(math.pi * (1 - 2 * y / n))) local lat_deg = lat_rad * 180.0 / math.pi return lon_deg, lat_deg end PostgreSQLCREATE OR REPLACE FUNCTION lon2tile(lon DOUBLE PRECISION, zoom INTEGER) RETURNS INTEGER AS $BODY$ BEGIN RETURN FLOOR( (lon + 180) / 360 * (1 << zoom) )::INTEGER; END; $BODY$ LANGUAGE plpgsql IMMUTABLE; CREATE OR REPLACE FUNCTION lat2tile(lat DOUBLE PRECISION, zoom INTEGER) RETURNS INTEGER AS $BODY$ BEGIN RETURN FLOOR( (1.0 - LN(TAN(RADIANS(lat)) + 1.0 / COS(RADIANS(lat))) / PI()) / 2.0 * (1 << zoom) )::INTEGER; END; $BODY$ LANGUAGE plpgsql IMMUTABLE; CREATE OR REPLACE FUNCTION tile2lat(y INTEGER, zoom INTEGER) RETURNS DOUBLE PRECISION AS $BODY$ DECLARE n FLOAT; sinh FLOAT; E FLOAT = 2.7182818284; BEGIN n = PI() - (2.0 * PI() * y) / POWER(2.0, zoom); sinh = (1 - POWER(E, -2*n)) / (2 * POWER(E, -n)); RETURN DEGREES(ATAN(sinh)); END; $BODY$ LANGUAGE plpgsql IMMUTABLE; CREATE OR REPLACE FUNCTION tile2lon(x INTEGER, zoom INTEGER) RETURNS DOUBLE PRECISION AS $BODY$ BEGIN RETURN x * 1.0 / (1 << zoom) * 360.0 - 180.0; END; $BODY$ LANGUAGE plpgsql IMMUTABLE; SubtilesIf you're looking at tile x,y and want to zoom in, the subtiles are (in the next zoom-level's coordinate system):
Similarly, zoom out by halving x and y (in the previous zoom level) Resolution and ScaleExact length of the equator (according to wikipedia) is 40075.016686 km in WGS-84. A horizontal tile size at zoom 0 would be 156543.03 meters: 40075.016686 * 1000 / 256 ≈ 6378137.0 * 2 * pi / 256 ≈ 156543.03 Which gives us a formula to calculate resolution at any given zoom: resolution = 156543.03 meters/pixel * cos(latitude) / (2 ^ zoomlevel) Some applications need to know a map scale, that is, how 1 cm on a screen translates to 1 cm of a map. scale = 1 : (screen_dpi * 39.37 in/m * resolution) 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).
See also Zoom levels Tools
References
|
|