Skip to content

Instantly share code, notes, and snippets.

@tmcw
Last active August 20, 2024 16:47
Show Gist options
  • Save tmcw/4954720 to your computer and use it in GitHub Desktop.
Save tmcw/4954720 to your computer and use it in GitHub Desktop.
The difference between XYZ and TMS tiles and how to convert between them

The difference between XYZ and TMS tiles and how to convert between them

Lots of tile-based maps use either the XYZ or TMS scheme. These are the maps that have tiles ending in /0/0/0.png or something. Sometimes if it's a script, it'll look like &z=0&y=0&x=0 instead. Anyway, these are usually maps in Spherical Mercator.

Good examples are OpenStreetMap, Google Maps, MapBox, MapQuest, etc. Lots of maps.

Most of those are in XYZ. The best documentation for that is slippy map tilenames on the OSM Wiki, and Klokan's Tiles a la Google.

Some of them are in TMS instead. TMS is an OSGeo spec. Here's the wiki page on it. It's less popular and few services support the whole spec.

There are no advantages of XYZ over TMS or vice-versa for most maps*, but XYZ is more popular.

Converting

Let's get to the point. The only difference between the two is a flipped y coordinate.

In math:

y = (2^z) - y - 1

javascript

y = Math.pow(2, z) - y - 1;

php

y = pow(2, z) - y - 1;

python

y = (2 ** z) - y - 1

ruby

y = (2 ** z) - y - 1

Addendum

When I say 'no difference' or 'no advantage' I mean for most maps. If you have some weird projection, use TMS but ideally don't make a tiled map in a weird projection in the first place. If you're forced to use OSGeo standards, do that but try to find a different job.

This originally credited OGC with TMS. It was OSGeo. OGC has WMTS. Don't use that either.

@popkinj
Copy link

popkinj commented Jan 21, 2015

Awesome. Thanks for this Tom ☺.

@MikeGodin
Copy link

Just a note that in a lot of languages, a binary left shift is much faster than floating point math for getting 2^z. For example, in Javascript, the function would be:

y = (1 << z) - y - 1;

@hastebrot
Copy link

Thank you so much!

Klokan's great example also gives some links for the tile addressing systems:

Update: Google Code link is gone. Maybe these links are a replacement:

@spyhunter99
Copy link

are the equations above for converted from XYZ to TMS or vice verse?

@Bibi56
Copy link

Bibi56 commented Sep 28, 2016

@spyhunter99,
let call y_tms and y_xyz for clarification.
Let's say the equation is
y_tms = (1 << z) - y_xyz - 1
add (y_xyz - y_tms) to both sides of the equation.
Now you've
y_xyz = (1 << z) - y_tms - 1.
Pretty much the same isn't it?

@jrvanderveen
Copy link

jrvanderveen commented Nov 12, 2016

So im using gdal2tiles.py to tile a raster into a TMS but the software im using to display this uses XYZ scheme. Ive tested this math on my files and maybe im missing something but it gives me the wrong file names. For instance I want to convert level 4 aka z = 4, this folder has 8 x coordinate folders with 7 y coordinate png in them. I want the ...4/0/6.png to equal ...4/0/0.png and so on. y_xyz = 2^4 - 6 - 1 = 9. Am i missing something here? To get the right conversion I do y_xyz = 2^4 - 6 - 1 - (2^z - 1 - ymax)

@Murthy10
Copy link

Have a look at https://github.com/geometalab/pyGeoTile, it solves this problem and is available as PyPi package.

pip install pyGeoTile

@haggaishG
Copy link

haggaishG commented Oct 16, 2018

Fantastic article. Helped me a lot.
Please note that for for the tile index you may need to adjust with Math.floor(Math.pow(2, z) - y - 1)

@kristofgilicze
Copy link

If anyone stumbled upon this and using Leaflet, then you can easily resolve this on the client side.

      /* Loading tiles from server */
      L.tileLayer('/map-tiles/{z}/{x}/{y}.png', {

              tms: true, // Set to true for the flip
              maxZoom: 6

      }).addTo(this.map);

@barbosaale
Copy link

I implemented a python script that performs the conversion on the tiles, being able to later view them in frameworks that use the XYZ standard like Mapbox for example.

https://github.com/barbosaale/tms-to-xyz

@leandjb
Copy link

leandjb commented Jun 20, 2020

Thanks bro!

@soiqualang
Copy link

Many thanks! ^^

@fasiha
Copy link

fasiha commented Jun 4, 2021

Above a client-side solution for Leaftlet was shared. Here's the solution to QGIS: when you create an XYZ Tiles connection, instead of doing the usual /{z}/{x}/{y} use /{z}/{x}/{-y}, i.e., just add a dash to y to indicate the flipped y convention. (Of course this works if the server does z/y/x too.)

I'm not sure if this is documented anywhere 😢 I found out about it via qgis/QGIS#33834, which called it 'the "{-y}" trick' 😒.

@Bibi56
Copy link

Bibi56 commented Feb 7, 2022

I'm not sure if this is documented anywhere

@fasiha , the -y to call the TMS<>XYZ conversion is a common scheme. No need to document the obvious^^ except in a change request.
It's used by OpenLayers and in Leaflet since 1.0 too.

If you know where you expected to find it, just create a PR to update the documentation or a ticket on the repo so that others will find it.

@fasiha
Copy link

fasiha commented Feb 10, 2022

@Bibi56 thanks for that, I didn't know it was well-known! I will look for a good place to document it, at least in QGIS 🙏

@Bibi56
Copy link

Bibi56 commented Feb 10, 2022

@fasiha I was half joking as it's more a de facto standard well... under-documented. I forgot GeoServer (where you can use still the historical vendor parameter flipY=true). Some years ago I had to extend the MapBox environment to deal with the {-y} convention. With my professional hat I found this gist in order to help a customer to use our TMS system! Kind of easy to remember trick. And when you know it, you don't remember to write it on the documentation. BTW, with my professional hat, I'll ask to enhance our documentation and possibly offer a XYZ slippy map integrating OpenStreetMap data. Our documentation had been updated after my exchange with the customer.

@dudeuter
Copy link

dudeuter commented May 17, 2023

SQL dialect:

SELECT (1 << tiles.z) - tiles.y - 1 as y FROM tiles;

The SHL operation here is important for compatibility between different SQL dialects not just performance. Ex: a lot of versions of sqlite don't have the pow function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment