Skip to content

Tiling schemes

Tiling schemes or tile matrix sets are used in tiled map services to index and reference fixed size map tiles in different zoom levels (map scales).

🗄️ Scalable coordinates

As already introduced in the chapter about coordinates scalable coordinates are coordinates associated with a level of detail (LOD) or a zoom level. They are used for example by tiling schemes to represent pixels or tiles in tile matrices.

As a recap the Scalable2i class represents projected x, y coordinates at zoom level, with all values as integers. The following sample represents pixels.

// A pixel with a zoom level (or LOD = level of detail) coordinates.
const pixel = Scalable2i(zoom: 9, x: 23, y: 10);
// Such coordinates can be scaled to other zoom levels.
pixel.zoomIn(); // => Scalable2i(zoom: 10, x: 46, y: 20);
pixel.zoomOut(); // => Scalable2i(zoom: 8, x: 11, y: 5);
pixel.zoomTo(13); // => Scalable2i(zoom: 13, x: 368, y: 160));

Also references to tiles can be represented with similar objects. This shall be clarified on descriptions of two tiling schemes in this chapter. At first let’s see how the most common Web Mercator scheme is built.

🗺️ Web Mercator Quad

WebMercatorQuad is a “Google Maps Compatible” tile matrix set with tiles defined in the WGS 84 / Web Mercator projection (“EPSG:3857”).

Using WebMercatorQuad involves following coordinates:

  • position: geographic coordinates (longitude, latitude)
  • world: a position projected to the pixel space of the map at level 0
  • pixel: pixel coordinates (x, y) in the pixel space of the map at zoom
  • tile: tile coordinates (x, y) in the tile matrix at zoom

See below how to calcalate between geographic positions, world coordinates, pixel coordinates and tile coordinates:

// "WebMercatorQuad" tile matrix set with 256 x 256 pixel tiles and with
// "top-left" origin for the tile matrix and map pixel space
const quad = WebMercatorQuad.epsg3857();
// source position as geographic coordinates
const position = Geographic(lon: -0.0014, lat: 51.4778);
// get world, tile and pixel coordinates for a geographic position
print(quad.positionToWorld(position)); // ~ x=127.999004 y=85.160341
print(quad.positionToTile(position, zoom: 2)); // zoom=2 x=1 y=1
print(quad.positionToPixel(position, zoom: 2)); // zoom=2 x=511 y=340
print(quad.positionToPixel(position, zoom: 4)); // zoom=4 x=2047 y=1362
// world coordinates can be instantiated as projected coordinates
// x range: (0.0, 256.0) / y range: (0.0, 256.0)
const world = Projected(x: 127.99900444444444, y: 85.16034098329446);
// from world coordinates to tile and pixel coordinates
print(quad.worldToTile(world, zoom: 2)); // zoom=2 x=1 y=1
print(quad.worldToPixel(world, zoom: 2)); // zoom=2 x=511 y=340
print(quad.worldToPixel(world, zoom: 4)); // zoom=4 x=2047 y=1362
// tile and pixel coordinates with integer values can be defined too
const tile = Scalable2i(zoom: 2, x: 1, y: 1);
const pixel = Scalable2i(zoom: 2, x: 511, y: 340);
// tile and pixel coordinates can be zoomed (scaled to other level of details)
print(pixel.zoomIn()); // zoom=3 x=1022 y=680
print(pixel.zoomOut()); // zoom=1 x=255 y=170
// get tile bounds and pixel position (accucy lost) as geographic coordinates
print(quad.tileToBounds(tile)); // west: -90 south: 0 east: 0 north: 66.51326
print(quad.pixelToPosition(pixel)); // longitude: -0.17578 latitude: 51.50874
// world coordinates returns geographic positions still accurately
print(quad.worldToPosition(world)); // longitude: -0.00140 latitude: 51.47780
// aligned points (world, pixel and position coordinates) inside tile or edges
print(quad.tileToWorld(tile, align: Aligned.northWest));
print(quad.tileToPixel(tile, align: Aligned.center));
print(quad.tileToPosition(tile, align: Aligned.center));
print(quad.tileToPosition(tile, align: Aligned.southEast));
// get zoomed tile at the center of a source tile
final centerOfTile2 = quad.tileToWorld(tile, align: Aligned.center);
final tile7 = quad.worldToTile(centerOfTile2, zoom: 7);
print('tile at zoom 2: $tile => center of tile: $centerOfTile2 '
'=> tile at zoom 7: $tile7');
// a quad key is a string identifier for tiles
print(quad.tileToQuadKey(tile)); // "03"
print(quad.quadKeyToTile('03')); // zoom=2 x=1 y=1
print(quad.quadKeyToTile('0321')); // zoom=4 x=5 y=6
// tile size and map bounds can be checked dynamically
print(quad.tileSize); // 256
print(quad.mapBounds()); // ~ west: -180 south: -85.05 east: 180 north: 85.05
// matrix width and height tells number of tiles in a given zoom level
print('${quad.matrixWidth(2)} x ${quad.matrixHeight(2)}'); // 4 x 4
print('${quad.matrixWidth(10)} x ${quad.matrixHeight(10)}'); // 1024 x 1024
// map width and height tells number of pixels in a given zoom level
print('${quad.mapWidth(2)} x ${quad.mapHeight(2)}'); // 1024 x 1024
print('${quad.mapWidth(10)} x ${quad.mapHeight(10)}'); // 262144 x 262144
// ground resolutions and scale denominator for zoom level 10 at the Equator
print(quad.tileGroundResolution(10)); // ~ 39135.76 (meters)
print(quad.pixelGroundResolution(10)); // ~ 152.87 (meters)
print(quad.scaleDenominator(10)); // ~ 545978.77
// inverse: zoom from ground resolution and scale denominator
print(quad.zoomFromPixelGroundResolution(152.87)); // ~ 10.0 (double value)
print(quad.zoomFromScaleDenominator(545978.77)); // ~ 10.0 (double value)
// ground resolutions and scale denominator for zoom level 10 at lat 51.4778
print(quad.pixelGroundResolutionAt(latitude: 51.4778, zoom: 10)); // ~ 95.21
print(quad.scaleDenominatorAt(latitude: 51.4778, zoom: 10)); // ~ 340045.31
// inverse: zoom from ground resolution and scale denominator at lat 51.4778
print(
quad.zoomFromPixelGroundResolutionAt(
latitude: 51.4778,
resolution: 95.21,
),
); // ~ 10.0 (double value)
print(
quad.zoomFromScaleDenominatorAt(
latitude: 51.4778,
denominator: 340045.31,
),
); // ~ 10.0 (double value)

🌐 Global Geodetic Quad

GlobalGeodeticQuad (or “World CRS84 Quad” for WGS 84) is a tile matrix set with tiles defined in the Equirectangular Plate Carrée projection.

At the zoom level 0 the world is covered by two tiles (tile matrix width is 2 and matrix height is 1). The western tile (x=0, y=0) is for the negative longitudes and the eastern tile (x=1, y=0) for the positive longitudes.

// "World CRS 84" tile matrix set with 256 x 256 pixel tiles and with
// "top-left" origin for the tile matrix and map pixel space
const quad = GlobalGeodeticQuad.worldCrs84();
// source position as geographic coordinates
const position = Geographic(lon: -0.0014, lat: 51.4778);
// get world, tile and pixel coordinates for a geographic position
print(quad.positionToWorld(position)); // ~ x=255.998009 y=54.787129
print(quad.positionToTile(position, zoom: 2)); // zoom=2 x=3 y=0
print(quad.positionToPixel(position, zoom: 2)); // zoom=2 x=1023 y=219
print(quad.positionToPixel(position, zoom: 4)); // zoom=4 x=4095 y=876
// world coordinates can be instantiated as projected coordinates
// x range: (0.0, 512.0) / y range: (0.0, 256.0)
const world = Projected(x: 255.99800888888888, y: 54.78712888888889);
// from world coordinates to tile and pixel coordinates
print(quad.worldToTile(world, zoom: 2)); // zoom=2 x=3 y=0
print(quad.worldToPixel(world, zoom: 2)); // zoom=2 x=1023 y=219
print(quad.worldToPixel(world, zoom: 4)); // zoom=4 x=4095 y=876
// tile and pixel coordinates with integer values can be defined too
const tile = Scalable2i(zoom: 2, x: 3, y: 0);
const pixel = Scalable2i(zoom: 2, x: 1023, y: 219);
// get tile bounds and pixel position (accucy lost) as geographic coordinates
print(quad.tileToBounds(tile)); // west: -45 south: 45 east: 0 north: 90
print(quad.pixelToPosition(pixel)); // longitude: -0.08789 latitude: 51.41602
// world coordinates returns geographic positions still accurately
print(quad.worldToPosition(world)); // longitude: -0.00140 latitude: 51.4778
// tile size and map bounds can be checked dynamically
print(quad.tileSize); // 256
print(quad.mapBounds()); // west: -180 south: -90 east: 180 north: 90
// matrix width and height tells number of tiles in a given zoom level
print('${quad.matrixWidth(2)} x ${quad.matrixHeight(2)}'); // 8 x 4
print('${quad.matrixWidth(10)} x ${quad.matrixHeight(10)}'); // 2048 x 1024
// map width and height tells number of pixels in a given zoom level
print('${quad.mapWidth(2)} x ${quad.mapHeight(2)}'); // 2048 x 1024
print('${quad.mapWidth(10)} x ${quad.mapHeight(10)}'); // 524288 x 262144
// arc resolutions and scale denominator for zoom level 10 at the Equator
print(quad.tileArcResolution(10)); // ~ 0.175781 (° degrees)
print(quad.pixelArcResolution(10)); // ~ 0.000686646 (° degrees)
print(quad.scaleDenominator(10)); // ~ 272989.39
// inverse: zoom from scale denominator at the Equator
print(quad.zoomFromScaleDenominator(272989.39)); // ~ 10.0 (double value)