Every web map you have ever panned is a pyramid of 256-pixel square tiles addressed {z}/{x}/{y}, and the grid is a published standard: WebMercatorQuad, tabulated in OGC 17-083r4 Annex D.1 and used by Google Maps, Bing Maps and OpenStreetMap tiles alike. This endpoint is the arithmetic of that grid — five pure functions, no dataset, no clock, no randomness. The same query returns the same bytes forever.
/from-point — a coordinate plus a zoom gives the tile that contains it, the quadkey, and the fractional global pixel position. x = floor(n·(lng + 180)/360) and y = floor(n·(0.5 − atanh(sin φ)/2π)) where n = 2^zoom.
/tile — a z/x/y address gives the tile's bounds in degrees and in EPSG:3857 metres, its quadkey, its TMS row, its ground resolution, its scale denominators, its parent and its four children. scheme=tms declares that the y you sent counts up from the south.
/quadkey — decode a Bing quadkey to exactly the same record. A quadkey's length is its zoom level.
/cover — every tile covering a bounding box at one zoom, with the exact count. It never truncates.
/mercator — project a coordinate to EPSG:3857 metres, or invert metres back to WGS 84.
Quadkeys
A quadkey is one base-4 digit per zoom level, most significant first: the X bit contributes 1 and the Y bit contributes 2, so 0 is the north-west child, 1 north-east, 2 south-west, 3 south-east. Microsoft's worked example is tileX = 3 (011₂), tileY = 5 (101₂) at level 3, interleaved to 100111₂ = "213". Two properties make quadkeys worth using: a parent's quadkey is a prefix of its children's (so "2" is the parent of "20"–"23"), and one string carries the whole address including the zoom. Zoom 0 is the exception — the rule "length equals zoom" would give the empty string, so quadkey is null there rather than "".
XYZ or TMS: which way is y counted?
OGC 17-083r4 §6.2 calls this cornerOfOrigin, and its default is topLeft — Table 8 notes that topLeft is "sometimes known as 'xyz'" and bottomLeft is the one "used by Tile Map Service". Row 0 is the north edge in XYZ (Google, OSM, Bing, MapLibre, Leaflet) and the south edge in TMS (OSGeo TMS, MBTiles). Every record here reports both y (topLeft) and tmsY = 2^zoom − 1 − y, so you never have to guess the flip; x is identical in both.
Zoom levels, resolution and scale
| Zoom |
Matrix |
Tiles |
Ground resolution at the equator |
Scale denominator (0.28 mm) |
| 0 |
1 × 1 |
1 |
156,543.0339 m/px |
1 : 559,082,264 |
| 1 |
2 × 2 |
4 |
78,271.5170 m/px |
1 : 279,541,132 |
| 2 |
4 × 4 |
16 |
39,135.7585 m/px |
1 : 139,770,566 |
| 3 |
8 × 8 |
64 |
19,567.8792 m/px |
1 : 69,885,283 |
| 4 |
16 × 16 |
256 |
9,783.9396 m/px |
1 : 34,942,642 |
| 5 |
32 × 32 |
1,024 |
4,891.9698 m/px |
1 : 17,471,321 |
| 6 |
64 × 64 |
4,096 |
2,445.9849 m/px |
1 : 8,735,660 |
| 7 |
128 × 128 |
16,384 |
1,222.9925 m/px |
1 : 4,367,830 |
| 8 |
256 × 256 |
65,536 |
611.4962 m/px |
1 : 2,183,915 |
| 9 |
512 × 512 |
262,144 |
305.7481 m/px |
1 : 1,091,958 |
| 10 |
1,024 × 1,024 |
1,048,576 |
152.8741 m/px |
1 : 545,979 |
| 11 |
2,048 × 2,048 |
4,194,304 |
76.4370 m/px |
1 : 272,989 |
| 12 |
4,096 × 4,096 |
16,777,216 |
38.2185 m/px |
1 : 136,495 |
| 13 |
8,192 × 8,192 |
67,108,864 |
19.1093 m/px |
1 : 68,247 |
| 14 |
16,384 × 16,384 |
268,435,456 |
9.5546 m/px |
1 : 34,124 |
| 15 |
32,768 × 32,768 |
1,073,741,824 |
4.7773 m/px |
1 : 17,062 |
| 16 |
65,536 × 65,536 |
4,294,967,296 |
2.3887 m/px |
1 : 8,531 |
| 17 |
131,072 × 131,072 |
17,179,869,184 |
1.1943 m/px |
1 : 4,265 |
| 18 |
262,144 × 262,144 |
68,719,476,736 |
59.7164 cm/px |
1 : 2,133 |
| 19 |
524,288 × 524,288 |
274,877,906,944 |
29.8582 cm/px |
1 : 1,066 |
| 20 |
1,048,576 × 1,048,576 |
1,099,511,627,776 |
14.9291 cm/px |
1 : 533 |
| 21 |
2,097,152 × 2,097,152 |
4,398,046,511,104 |
7.4646 cm/px |
1 : 267 |
| 22 |
4,194,304 × 4,194,304 |
17,592,186,044,416 |
3.7323 cm/px |
1 : 133 |
| 23 |
8,388,608 × 8,388,608 |
70,368,744,177,664 |
1.8661 cm/px |
1 : 67 |
| 24 |
16,777,216 × 16,777,216 |
281,474,976,710,656 |
0.9331 cm/px |
1 : 33 |
Two honest caveats on those last two columns. Ground resolution is only valid near the equator in the E-W direction — OGC Annex D.1 NOTE 1 says so explicitly, because Mercator stretches the cell size towards the poles; that is why every record also returns groundResolution.atCenterLatitude, the same figure multiplied by cos φ. And there are two scale conventions in the wild: OGC §6.1.1 defines the scale denominator against a "standardized rendering pixel size" of 0.28 mm (the same value WMS 1.3.0 and WMTS 1.0 use — it was a common display's real pixel pitch in 2005), while Bing Maps computes map scale at 96 dpi, or about 0.2646 mm. The two differ by roughly 5.8 %. Both are returned, labelled, rather than picking one and leaving you to wonder.
Nothing is rounded, and that is deliberate
Every number is the unrounded float64 result of the published formula. Rounding degree bounds would break tile identity: at zoom 24 a column is 2.1e-5° wide, so rounding west to ten decimals can shift it across the edge into the previous column, and the value would no longer re-floor to the tile it came from. It also lets the endpoint check itself against the standard — at zoom 14 groundResolution.atEquator is 9.554628535647032 and scaleDenominator.atEquator is 34123.67334159654, digit for digit the values in Annex D.1. That exact agreement holds for 11 of the table's 25 rows; the other rows differ in the last digit or two because the published table is rounded to 15–16 significant digits, not because the arithmetic differs. The test suite pins all 25 rows to a relative tolerance of 1e-14 (worst observed: 3.6e-15).
The latitude limit is a 400, not a clamp
Web Mercator's square ends at arctan(sinh(π)) = 85.05112877980659°; there are no tile rows beyond it. Bing's reference implementation clips latitude to its published constant ±85.05112878 before projecting. This API does not clip. A latitude outside the accepted range returns 400 naming the value, because silently clamping answers a question you did not ask — the tile for 88° N is not the tile for 85.05° N.
There is one sub-millimetre subtlety, and it is worth stating precisely. Bing's constant 85.05112878 sits about 1.9e-10° outside the exact edge — 0.25 mm of projected northing — so a naive floor() at that input produces row −1. The tile index is therefore clamped into the matrix, and the same applies at lng = 180, whose column fraction is exactly n: it lands in the easternmost column 2^zoom − 1 and is never normalized to −180. That is index clamping inside a boundary sliver at the closed edge of the domain — not latitude clamping. Bounds are half-open, [west, east) by [north, south), so a point exactly on an internal edge belongs to the tile east and south of it: lat=0&lng=0 at zoom 1 is tile 1/1/1, quadkey "3".
/cover: exact counts, and a cap that refuses rather than truncates
/cover implements OGC Annex I.1's algorithm, including its epsilon guard of 1e-6 tile units — which is load-bearing, not a fudge. The exact north edge of row 1 at zoom 2 is 66.51326044311186°, and projecting it back lands on 0.9999999999999998 rows, one unit in the last place short of the boundary; without the epsilon that box picks up a spurious extra row. The cost is a tolerance band: a bbox edge within 1e-6 of a tile span of a boundary is treated as lying on it — about 40 m at zoom 0, 2.4 mm at zoom 14 — so a point inside that band can be reported by /from-point (an exact floor() point query) in the adjacent tile. Two deliberate deviations from the published pseudocode, both so that tileRange and tiles can only ever name tiles that exist. First: for a zero-area box on a boundary the pseudocode inverts the range, so the range is collapsed to the single tile containing the point, which is what /from-point answers for the same coordinate. Second: the pseudocode guards each end of the range on one side only, which assumes no bbox edge can land on the far edge of the matrix — in this closed domain one can, since lng = 180 gives a column fraction of exactly n and lat = -85.05112878 a row fraction a hair above n. Both ends are therefore clamped into 0..2^zoom − 1, the same closed-edge index clamping /from-point applies, so a box on the antimeridian or the south edge reports the easternmost column or southernmost row rather than a nonexistent index.
The enumerated list is capped by maxTiles (default 256, ceiling 1024) and the cap refuses instead of truncating, with the real count in the message. The Copenhagen box minLat=55.6&minLng=12.4&maxLat=55.8&maxLng=12.7 shows both refusals:
- at
zoom=15 it needs 924 tiles, so the request 400s while maxTiles is at its default 256 — and succeeds byte-for-byte identically with &maxTiles=1024;
- at
zoom=16 it needs 3,630, which no permitted maxTiles can satisfy, so that message does not offer the raise. Send includeTiles=false instead: it returns tileRange and the exact tileCount for any box at any zoom with no cap (the whole world at zoom 24 is 281,474,976,710,656 tiles, and the array stays empty).
There is deliberately no single bbox= param. Two orderings are both in live use — GeoJSON/OGC west,south,east,north and this platform's own lat-first minLat,minLng,maxLat,maxLng on /api/coordinates — and for many real boxes each reading is a valid box somewhere else on Earth. 12.4,55.6,12.7,55.8 is central Denmark under one reading and open water in the Arabian Sea off the Horn of Africa under the other; a misread box would return perfectly plausible tiles for the wrong continent, which is exactly the kind of silently-wrong answer this platform refuses to produce. Four named required params cannot be misread. The bbox response field uses OGC/GeoJSON order and says so.
/mercator and its honest closed-edge asymmetries
Forward is easting = R·λ, northing = R·atanh(sin φ) with R = 6378137; inverse is λ = x/(R·π)·180, φ = atan(sinh(y/R)). lat=0&lng=0 returns exactly 0, 0, and lng=180 returns exactly 20037508.342789244 — Annex D.1's pointOfOrigin magnitude.
The x/y bound is ±20037508.344, one symmetric limit a hair wider than R·π, and the reason is measured — but it belongs to the northing axis only. The forward easting never exceeds R·π: lng=180 lands exactly on 20037508.342789244. The forward northing does exceed it, reaching ±20037508.343038857 m at the extreme accepted latitude ±85.05112878, overshooting R·π by 0.25 mm, so a tighter shared bound would 400 this route's own result. Keeping one bound for both axes costs a sliver on each, and both slivers are stated here rather than left for you to find:
- the inverse of the outermost
y is 85.05112878074486°, roughly 7.4e-10° (0.08 mm) outside the lat bound, so a latitude read from the very edge of the y domain and fed straight back in can 400;
- the inverse of the outermost
x is 180.00000001087642°, roughly 1.1e-8° (1.2 mm) outside the lng bound — and outside WGS 84's longitude range — for the same reason, and it likewise 400s if fed back to lng.
Both are sub-millimetre-to-millimetre artefacts of a closed edge, not rounding bugs, and nothing is silently clamped to hide either one. Inside the domain the round trip is stable — every longitude inverts back to itself exactly, and so does every latitude short of the edge. The one exception is the edge itself: lat=85.05112878 projects to 20037508.343038857 and inverts to 85.05112878000003, 2.8e-14° past the lat bound. Same closed edge, smallest face of it.
What this endpoint honestly does not do
- It serves no map imagery. No tile images, no tile proxy, no upstream tile server, no URL templates for third-party providers — their terms are theirs to agree with.
zxy is a path string for your own cache keys.
- Web Mercator is spherical and meant for pictures. The EPSG registry's own remark calls 3857 "Not a recognised geodetic system", notes that it develops ellipsoidal coordinates spherically, and records that against WGS 84 / World Mercator (EPSG:3395) it can be off by 0.7 % in scale and by up to 43 km of northing in the map — about 21 km on the ground. OGC Annex D.1 adds that WebMercatorQuad's use "should be limited to visualization", and its NOTE 4 recommends
WorldMercatorWGS84Quad where positioning accuracy matters. Use /api/geodesy for real distances.
- WebMercatorQuad only — no
WorldMercatorWGS84Quad, WorldCRS84Quad or GNOSISGlobalGrid, no custom tile matrix sets, no tile sizes other than 256 px, no @2x retina variants, no metatiles.
- No vector-tile internals — no MVT extents or geometry coordinates, no TileJSON, and no MBTiles translation beyond the documented
tmsY flip.
- Zoom 0–24 only, because that is the range Annex D.1 tabulates. The standard allows more; this is an API boundary, not a universal one.
- No antimeridian-crossing bbox. The matrix does not wrap, so a crossing box is a 400 that tells you to split it in two.
- No geocoding and no coordinate parsing. Send decimal degrees;
/api/coordinate-formats turns DMS, DDM and Maidenhead text into them.
- No neighbour or prefix-cell search —
/api/geohash owns that kind of reasoning.
- No dependency on, or recommendation of, Bing Maps as a service. Microsoft's Bing Maps Tile System document is cited here because it is the canonical published definition of the quadkey addressing scheme — the one Azure Maps and a long tail of tile libraries implement — and of the 96-dpi map-scale convention. The Bing Maps for Enterprise service is retired for free accounts and sunsets for enterprise customers on 2028-06-30; nothing here should be read as advice to use it.
Provenance
Implemented from the formulas in OGC 17-083r4 (a royalty-free OGC member-approved standard: §6.1.1 geometry, §6.2 corner of origin, Annex D.1 WebMercatorQuad, Annex I.1 bbox to tile indices), Microsoft's Bing Maps Tile System document (quadkeys, ground resolution, map scale at 96 dpi), and the EPSG:3857 record in the EPSG Geodetic Parameter Dataset. NOTE 2 of Annex D.1 is worth knowing if you meet old code: 3857 is the official EPSG code, and the unofficial 900913 — GOOGLE spelled with numbers — is still seen in the wild. The page most developers already know, OpenStreetMap's Slippy map tilenames, documents the same arithmetic as a community reference; it is not cited as a standard, and no prose or code was copied from it or from Microsoft's C# sample — the TypeScript here is written from the mathematics.