XYZ Tiles — Slippy Map: URL schema for tile-based maps in web and mobile
XYZ Tiles, or Slippy Map, is not a file format but a convention for tile URL addressing. A single tile is a raster or vector image 256×256 (or 512×512 on retina devices) representing a square Earth fragment at a specific zoom level. The URL template looks like https://tile.openstreetmap.org/{z}/{x}/{y}.png — the browser substitutes z (zoom 0–22), x (column), y (row), fetches the image, assembles the mosaic. This simple protocol powers Google Maps, OpenStreetMap, Bing Maps, Yandex Maps, 2GIS, MapLibre, Leaflet, and hundreds of other services. The XYZ schema is what let maps "slide" smoothly across the screen — hence the name Slippy Map.
History: from Google Maps to OpenStreetMap
The XYZ-schema story begins with Google Maps in February 2005. The Where 2 Technologies team — acquired by Google in 2004 — devised a way to show a large map in the browser without page reloads: a mosaic of small square PNG images loaded asynchronously via XMLHttpRequest. This was one of the first public applications of AJAX. Before, web maps were either rendered server-side as whole pages or required Flash/Java applets.
Google used the Web Mercator projection (EPSG:3857) — a simplified variant of the original 1569 Mercator, where Earth is treated as a sphere, not an ellipsoid. This yields small distortions (around 0.5%) compared to the true WGS84 ellipsoid but lets you write simple coordinate-transformation math that doesn't depend on the reference ellipsoid.
In 2006 the young OpenStreetMap project, lacking its own renderer, copied exactly the same XYZ schema — which let the community reuse the JavaScript libraries (OpenLayers, then Leaflet starting in 2011) written for Google Maps. In parallel TMS (Tile Map Service) appeared — an OGC spec from 2003 with the same concept but an inverted Y axis (0 = south, not north). This spawned 20 years of confusion: different libraries read {y} differently, requiring conversion.
By 2010 XYZ became the de facto standard for web maps; by 2020 — for mobile (via MapLibre Native, Mapbox SDK, native iOS/Android mapping libraries). As of May 2026 XYZ remains the primary tile-delivery method and continues to grow with new capabilities (vector tiles in MVT, terrain in Terrain-RGB, WebP/AVIF encoding for bandwidth savings).
What's inside the XYZ URL schema
The tile pyramid is the concept at XYZ's core. Picture the whole Earth in Web Mercator as a square -180..180 longitude and roughly -85.0511..85.0511 latitude (poles go to infinity in Mercator, so they're clipped). At zoom 0 the entire Earth is one tile 256×256 pixels. At zoom 1 — a 2×2 mosaic (4 tiles); zoom 2 — 4×4 (16); and so on. Each next zoom doubles the tile count along each axis.
Coordinate z is the zoom level from 0 to usually 22 (some services go to 23–25, but OSM data accuracy rarely needs above 19). x is the column index from west to east (0 = western edge, 2^z - 1 = eastern). y is the row index from north to south (0 = northern edge, 2^z - 1 = southern). At zoom 17 (typical city max in OSM) the tile grid is 131072×131072 tiles — 17 billion in total.
Tile size is conventionally 256×256 pixels, but retina devices and some modern services use 512×512 (Mapbox Streets v8, Stadia Maps). MVT vector tiles usually have a 4096-unit extent (internal tile coordinate system) that scales to any DPR (device pixel ratio).
Lat/lon ↔ XYZ math is captured in three formulas. Longitude lon to column x at zoom z: x = floor((lon + 180) / 360 * 2^z). Latitude lat to row y (for XYZ, not TMS): y = floor((1 - log(tan(rad(lat)) + 1/cos(rad(lat))) / π) / 2 * 2^z). The inverse is equally simple and easily Googleable — all libraries ship ready-made functions. Quadkey (Bing Maps) is an alternative notation where z/x/y is encoded as a single string of digits 0–3 of length z (e.g. "031313" for some tile at zoom 6).
XYZ vs TMS: one difference that breaks integrations
Tile Map Service (TMS) is an OGC specification published in 2003, before Google Maps "invented" XYZ. Conceptually it's the same: tile pyramid, Web Mercator, z/x/y coordinates. The one difference: the Y axis in TMS points bottom-up (0 = southern edge), in XYZ — top-down (0 = northern). Conversion is trivial: tms_y = 2^z - 1 - xyz_y.
This difference breaks integrations daily. GeoServer by default serves tiles in TMS schema; Leaflet expects XYZ. MBTiles (Mapbox format) internally uses TMS, but on export to a file structure usually converts to XYZ. PMTiles is also TMS internally. Most external web services hand out XYZ. If your tiles look flipped — you've hit this difference.
Modern libraries handle conversion automatically: Leaflet's L.tileLayer.tms() for TMS sources, MapLibre's tiles[].scheme: "tms" in style.json. But if you're writing a custom loader — check the schema first.
Who needs XYZ tiles: five roles
Web developer with an interactive map. The most common case — show a district map on a website where the user can zoom and pan. Leaflet + an XYZ source (your own or a public OSM tile server) is 20 lines of JavaScript with no backend dependency. You can swap the tile server for your own stylized OSM via osm2cdr.ru to avoid OSM Foundation's restrictions on commercial use of their tiles.
Mobile developer. Native iOS/Android apps also work on XYZ via MapLibre Native or Mapbox SDK. The advantage — offline cache: tiles are downloaded once and stored locally as files or SQLite (MBTiles). This enables internet-free mapping apps — city navigation, tourist guides, hunting/fishing maps.
Cartographer publishing a thematic map. Suppose you're making an electric-transport map of your city or a memorials map. Your own OSM style in MapLibre, export to MBTiles or PMTiles, host on S3 or Cloudflare R2 — and you have a working map with a custom style and no traffic dependency on tile.openstreetmap.org. XYZ schema is the lingua franca for such publications.
Embed developer. Many sites need a small static map — real estate, restaurants, tourist objects. Through osm2cdr.ru you can generate an XYZ tile bundle for a specific bbox with a specific style and embed it on any partner site. This removes the dependency on Google Maps API with its billing surprises.
Enterprise cartographer for air-gapped infrastructure. Military, security, oil and gas — some corporate clients can't hit the public internet. They need a complete pre-rendered tile set for their region packaged in a standard XYZ directory structure. Import into their internal tile server, access via the corporate GIS portal.
Software for XYZ tiles: seven programs
Leaflet — the most popular JavaScript library for XYZ. Lightweight, simple to use, great docs. Supports raster XYZ natively, vector XYZ via the Leaflet.VectorGrid plugin. Free, open-source. leafletjs.com
OpenLayers — older (since 2006) and more flexible alternative to Leaflet. Heavier in kilobytes but supports many projections beyond Web Mercator, OGC WMS/WMTS standards, and works better with heterogeneous sources. openlayers.org
MapLibre GL JS — open-source fork of Mapbox GL JS (before Mapbox moved to a commercial license in 2020). Renders vector XYZ tiles via WebGL — smooth zoom, rotation, tilt. The standard for modern vector maps. maplibre.org
MapLibre Native — the same for iOS and Android — native SDK with offline cache, vector rendering, custom styles. Used by most open-source mobile cartographic apps. github.com/maplibre/maplibre-native
GeoServer — open-source server delivering XYZ tiles from any sources (Shapefile, PostGIS, GeoPackage). Simultaneously WMS, WMTS, TMS, XYZ — chosen by URL endpoint. Good choice for enterprises with heterogeneous data. geoserver.org
TileServer GL — Node.js server for XYZ vector tiles. Takes MBTiles or PMTiles, serves via XYZ endpoints (/data/{z}/{x}/{y}.pbf), also renders raster fallback for old clients. Minimal, easy to deploy. tileserver.readthedocs.io
Martin — modern Rust server for vector tiles. Connects directly to PostGIS, generates MVT on the fly. Fast, low-RAM. Used on osm2cdr.ru production servers. github.com/maplibre/martin
Pitfalls and tips
Web Mercator distorts areas at high latitudes. This is a known Mercator property: Greenland looks the size of Africa (though Africa is 14× larger). At latitudes above 70° the distortion is so large that Arctic and Antarctic maps in XYZ schema are practically useless for scientific work. For polar regions use alternative projections (EPSG:3413 for the Arctic, EPSG:3031 for Antarctica) via specialized tile servers.
Tile-pyramid math ignores Earth's ellipsoid. Web Mercator treats Earth as a sphere of radius 6378137 m (WGS84 semi-major axis), not an ellipsoid. Real lat/lon coordinates transformed to Web Mercator and back have errors up to 0.5% — at city scale that's tens of meters. For cadastral applications or geodesy use PostGIS ST_Transform with the correct ellipsoid.
HTTP cache headers are critical. One zoom 17 = billions of tiles, and every request — even a few-kilobyte PNG — is server load. Cloudflare and similar CDNs offer free traffic if you cache aggressively: Cache-Control: public, max-age=31536000, immutable. On production osm2cdr.ru tiles are CDN-cached for a year — refreshed only when styling-version changes (cache busting via query parameter).
Directory size on disk. Storing tiles as files in a {z}/{x}/{y}.png hierarchy, a typical volume for a 500K-population city across zoom 0–17 is around 5 GB raster or 1 GB vector. For a 5-million-person region — 50–100 GB. File systems handle millions of small files poorly. Recommendation: use PMTiles (single-file pyramid) or MBTiles (SQLite-based) instead of file hierarchies for production deployments.
CORS headers for cross-origin embed. If your tile server delivers tiles to another domain (e.g. tiles host on tiles.example.com, map shown on myblog.com), CORS headers are needed: Access-Control-Allow-Origin: * or a whitelist of specific origins. Without them Leaflet errors in console and tiles fail to load.
Subdomain rotation for parallel downloads. HTTP/1.1 caps 6 parallel connections to one hostname. To speed up loading, a.tile, b.tile, c.tile are used — Leaflet rotates subdomains across tile requests, effectively multiplying bandwidth. In HTTP/2 this optimization is unnecessary (multiplexing within one connection), but for legacy serving it's still relevant.
Retina (HiDPI) tile size. Modern displays have DPR (device pixel ratio) 2 or 3. To keep maps crisp, you render tiles 512×512 pixels but display them as 256×256 CSS pixels (via tileSize: 512, zoomOffset: -1 in Leaflet). This doubles bandwidth but yields sharp text and precise lines.
Attribution is mandatory for OSM-derived tiles. If your tile bundle is built from OSM data (via osm2cdr.ru or otherwise), the © OpenStreetMap contributors attribution must be visible on the map at all times — an ODbL license requirement. Leaflet does this automatically through the attribution parameter; for embeds on third-party sites verify the attribution block stays visible.
How to make an XYZ tile bundle via osm2cdr.ru
osm2cdr.ru generates tile bundles in MBTiles or PMTiles format for a specific bbox with any of 20 built-in map styles. Further conversion to a "flat" XYZ directory structure is a one-off step after which tiles are ready to host on S3, Cloudflare R2, or your own nginx.
Step 1. Visit osm2cdr.ru, outline the desired region (district, city, oblast). Step 2. Choose MBTiles (for compatibility) or PMTiles (for the modern single-file approach). Step 3. Choose a map style — 20 built-in variants cover typographic, neon, blueprint, vintage, and other visual themes. Step 4. Get the file.
Step 5. MBTiles to XYZ directory conversion:
pip install mbutil
mb-util --image_format=png area.mbtiles output_xyz_folder
# Creates output_xyz_folder/{z}/{x}/{y}.png
Step 6. Hook up in Leaflet:
const map = L.map('map').setView([59.93, 30.31], 14);
L.tileLayer('https://yoursite.com/tiles/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors, osm2cdr.ru',
maxZoom: 19,
tileSize: 256
}).addTo(map);
Step 7. If you chose PMTiles — hook up via MapLibre with the pmtiles:// protocol:
import { Protocol } from 'pmtiles';
const protocol = new Protocol();
maplibregl.addProtocol("pmtiles", protocol.tile);
const map = new maplibregl.Map({
container: 'map',
style: {
version: 8,
sources: {
'osm2cdr': {
type: 'vector',
url: 'pmtiles://https://yoursite.com/area.pmtiles'
}
},
layers: [/* ... */]
}
});
With PMTiles, HTTP range requests replace file-tree decomposition — one file on S3 answers any {z}/{x}/{y} via byte-range queries. Cheaper to host, simpler to manage, but requires MapLibre or specialized clients.
Related formats and resources
- MVT — Mapbox Vector Tile inside XYZ schema
- PMTiles — single-file replacement for XYZ directories
- MBTiles — SQLite container for tile catalogs
- TileJSON — metadata standard for XYZ sources
- WMTS — OGC tile service standard as XYZ alternative
- PMTiles landing page
Sources
- OSM Wiki, Slippy Map Tilenames — wiki.openstreetmap.org/wiki/Slippy_map_tilenames
- OGC Tile Map Service Specification — opengeospatial.github.io/e-learning/wmts/text/main.html
- Web Mercator EPSG:3857 — epsg.io/3857
- Leaflet documentation, TileLayer — leafletjs.com/reference.html#tilelayer
- MapLibre style specification — maplibre.org/maplibre-style-spec
- Mapbox tile-coordinates math — docs.mapbox.com/help/glossary/zoom-level
- PMTiles specification — github.com/protomaps/PMTiles/blob/main/spec/v3/spec.md