Raster Tiles
A point map of offices or incidents reads well at country scale, but once the reader zooms into a city the points need streets to be read against. geo.tiles draws raster tiles from any server that cuts the world into {z}/{x}/{y} squares (an OpenStreetMap-style server, or a commercial provider's raster endpoint) underneath every series, and pans and zooms them with the map.
const map = new ApexMaps(document.querySelector('#map'), {
geo: {
map: 'world/countries@110m',
projection: 'webMercator',
tiles: {
url: 'https://{s}.tiles.example.com/{z}/{x}/{y}.png',
attribution: '© Your tile provider',
minZoom: 4,
},
},
series: [
{
type: 'marker',
name: 'Offices',
data: [{ name: 'London', lon: -0.12, lat: 51.5 }],
},
],
})
await map.render()
Nothing is requested unless you give a url. There is no default tile provider, so no map built with ApexMaps contacts a third party its host did not configure. Before you point url at a server, read that provider's usage policy and attribution terms: many public tile servers limit request volume, forbid heavy or commercial use, or require a specific credit line, and meeting those terms is your responsibility.
Options
geo.tiles option | Default | Description |
|---|---|---|
url | (none) | Tile URL template. {z}, {x} and {y} are the tile; {s} is one of subdomains. Required: without it the layer is off. |
subdomains | ['a', 'b', 'c'] | Hosts to fill {s} with, so a server's hosts share the load. A string is split into characters, so 'abc' is the same as the default. |
attribution | (none) | Plain-text credit line the tile provider requires, shown in the corner of the map while tiles are on screen. |
minZoom | 0 | Web-map zoom level below which no tiles are drawn, so the map shows its own geometry until the reader has zoomed in this far. Measured on the view, as viewChange reports it, so it does not change with tileSize. |
maxZoom | 19 | Highest {z} the server has, in its own levels. Past it the last level is stretched rather than requested. |
tileSize | 256 | Tile edge in pixels at its own level. It decides which {z} is requested: 512px tiles cover the view one level shallower than 256px ones, so each is drawn at its own size. Set it to match your server. Before 1.2.1 the level ignored it, and 512px tiles were drawn at half their size. |
opacity | 1 | Opacity of the whole tile layer. |
request | (none) | (url, tile) => TileRequest | Promise<TileRequest>. Called for every tile before it is fetched. See Keys, headers and proxies. |
It needs a webMercator map
Tiles are cut in spherical Mercator, so they only line up with a map drawn in it. Set geo.projection to 'webMercator' (or its aliases 'mercator' and 'epsg:3857'), and leave it unturned: a spec object with a non-zero rotate, an angle, or reflectX / reflectY cuts every tile into a shape the tile is not.
On any other projection the tile layer stays empty rather than drawing tiles that do not line up. With debug on, the console says why. The default projection is equalEarth, so a map that only adds geo.tiles draws no tiles until it also sets the projection.
What the tiles do on the map
- They move with the geometry. The tile layer sits inside the same transform as the regions and points, so a pan frame costs no tile work and the streets cannot drift off the points drawn on them. Tiles are only fetched when a new one comes into view or the zoom level changes, and tiles just outside the view are kept so panning back and forth across an edge does not fetch them again.
- They sit under every series, above the sphere and the graticule.
- The automatic basemap gives way to them. While tiles are showing, the land drawn under a point-only map (see Basemap) fades its fill and keeps its outlines. A choropleth is data and keeps its fill; give the series an
opacityif the streets should show through it. - A zoom keeps the old level on screen under the new one until the new tiles in view have loaded, so the map does not blank on every zoom step. A wheel or pinch that passes through several levels waits for the zoom to settle before requesting the level it lands on.
- A tile the server cannot supply is left out rather than drawn broken; the level underneath, if any, still covers its square.
- Tiles fade in as they arrive, and appear at once under
prefers-reduced-motion. - The provider's credit shows only while tiles are on screen, ahead of any attribution the geometry pack itself requires.
Waiting for the reader to zoom in
minZoom is a web-map zoom level: 0 shows the whole world 256 pixels wide, and each level doubles it. A world map a few hundred pixels wide opens at a level of about 1, so a minZoom of around 4 keeps the opening view clean and brings the streets in once the reader has zoomed into a region. Below minZoom nothing is requested at all.
To see the level the map is at while you tune it, log it from the viewChange event or read it once with map.getView():
map.on('viewChange', ({ zoom }) => console.log(zoom))
Keys, headers and proxies
request is called for every tile with the filled-in URL and the tile's { z, x, y }, before anything is fetched. Use it to sign a URL, add a key, or send the request through your own proxy. Return either a URL to load as an image, or { url, headers } to fetch it with those headers. It may return a promise.
tiles: {
url: 'https://tiles.example.com/{z}/{x}/{y}.png',
attribution: '© Your tile provider',
// A signed or keyed URL, loaded as an image
request: (url) => `${url}?key=${TILE_KEY}`,
}
tiles: {
url: 'https://tiles.example.com/{z}/{x}/{y}.png',
// Headers cannot ride on an image request, so this tile is fetched instead
request: async (url, { z, x, y }) => ({
url,
headers: { Authorization: `Bearer ${await getToken()}` },
}),
}
| Type | Shape |
|---|---|
TileCoord | { z: number; x: number; y: number } |
TileRequest | string | { url: string; headers?: Record<string, string> } |
When a request carries headers, the tile is fetched with fetch() and the image is pointed at the downloaded bytes, so the tile server has to accept that cross-origin request with those headers. The bytes are released when the tile leaves the map, and a request still in flight is aborted. Without request, the template URL is loaded as is.
Exports leave the tiles out
exportSVG(), exportPNG(), getSvgString() and dataURI() export the map without its tiles. An exported image cannot fetch them, and an exported file that silently depends on a tile server and a key is not one a reader can open anywhere else. See Export.
Drawing your own layer instead
If you already have a map layer of your own (a canvas, an iframe, another rendering stack), you do not need geo.tiles to keep it in step with ApexMaps. The viewChange event fires synchronously on every pan and zoom frame with the visible bounds, the center and the web-map zoom level, which is enough to drive a layer drawn under or beside the map. See Following the view.
See also
- Projections for
webMercatorand the other built-in projections. - Zoom, Pan & Camera for the gestures and camera moves the tiles follow.
- Licensing for which features need Premium.