# TileStrata
[](https://www.npmjs.org/package/tilestrata)
[](https://travis-ci.org/naturalatlas/tilestrata)
[](https://codecov.io/github/naturalatlas/tilestrata)
*TileStrata is a pluggable "slippy map" tile server that emphasizes code-as-configuration.* The primary goal is painless extendability. It's clean, highly tested, performant, and integrates seamlessly with an elastic load balancer designed specifically for tile serving: [TileStrata Balancer](https://github.com/naturalatlas/tilestrata-balancer). Also, there's a built-in profiler and dashboard for debugging render times ([read more](#profiling--debugging-performance)).
```sh
$ npm install tilestrata --save
```
### Table of Contents
- [Introduction](#introduction)
- [Configuration](#configuration)
- [Basics](#configuration)
- [Express.js / Connect Integration](#integrate-with-expressjs--connect)
- [Metatile-Aware Load Balancing](#metatile-aware-load-balancing--layer-sharding)
- [Rebuilding the Tile Cache](#rebuilding-the-tile-cache)
- [Health Checks](#health-checks)
- [Profiling / Debugging Performance](#profiling--debugging-performance)
- [API Reference](#api-reference)
- [Plugin Developer Guide](#writing-tilestrata-plugins)
## Introduction
TileStrata consists of five main actors, usually implemented as plugins:
- [*"provider"*](#writing-providers) – Generates a new tile (e.g mapnik)
- [*"cache"*](#writing-caches) – Persists a tile for later requests (e.g. filesystem)
- [*"transform"*](#writing-transforms) – Takes a raw tile and transforms it (e.g. image scaling / compression)
- [*"request hook"*](#writing-request-hooks) – Called at the very beginning of a tile request.
- [*"response hook"*](#writing-response-hooks) – Called right before a tile is served to the client.
#### List of Plugins
- [tilestrata-mapnik](https://github.com/naturalatlas/tilestrata-mapnik) – Render tiles with [mapnik](http://mapnik.org/).
- [tilestrata-disk](https://github.com/naturalatlas/tilestrata-disk) – Cache map tiles to the filesystem (or serve from it)
- [tilestrata-dependency](https://github.com/naturalatlas/tilestrata-dependency) – Fetch tiles from other layers.
- [tilestrata-sharp](https://github.com/naturalatlas/tilestrata-sharp) – Compress, resize, transcode tiles (jpg, png, webp) using [libvips](https://www.npmjs.com/package/sharp).
- [tilestrata-gm](https://github.com/naturalatlas/tilestrata-gm) – Perform all sorts of image operations on tiles using [GraphicsMagick](https://www.npmjs.com/package/gm).
- [tilestrata-headers](https://github.com/naturalatlas/tilestrata-headers) – Set/override response headers.
- [tilestrata-blend](https://github.com/naturalatlas/tilestrata-blend) – Stack multiple layers together.
- [tilestrata-jsonp](https://github.com/naturalatlas/tilestrata-jsonp) – Serve utfgrids (and other JSON) as JSONP.
- [tilestrata-datadog](https://github.com/naturalatlas/tilestrata-datadog) – Send timing information to [Datadog](https://www.datadoghq.com/).
- [tilestrata-utfmerge](https://github.com/naturalatlas/tilestrata-utfmerge) – Merge UTF interactivity grids from mapnik.
- [tilestrata-vtile](https://github.com/naturalatlas/tilestrata-vtile) – Outputs mapnik vector tiles (protobufs).
- [tilestrata-vtile-raster](https://github.com/naturalatlas/tilestrata-vtile-raster) – Renders mapnik vector tiles into raster images.
- [tilestrata-vtile-composite](https://github.com/naturalatlas/tilestrata-vtile-composite) – Merge multiple vector tiles.
- [tilestrata-proxy](https://github.com/naturalatlas/tilestrata-proxy) – Fetches tiles from other servers
- [tilestrata-lru](https://github.com/naturalatlas/tilestrata-lru) – Caches tiles in memory (LRU)
- [tilestrata-etag](https://github.com/naturalatlas/tilestrata-etag) – Configurable Conditional GET support (with ETags)
- [tilestrata-bing](https://github.com/naturalatlas/tilestrata-bing) – Provider for Bing Maps tiles
- [tilestrata-underzoom](https://github.com/naturalatlas/tilestrata-underzoom) - Build mosaics of higher-zoom tiles
- [tilestrata-postgismvt](https://github.com/Stezii/tilestrata-postgismvt) – Outputs Mapbox Vector Tiles from a PostGIS database
- [tilestrata-postgis-geojson-tiles](https://github.com/naturalatlas/tilestrata-postgis-geojson-tiles) – Outputs GeoJSON tiles from a PostGIS database
## Configuration
```js
const tilestrata = require('tilestrata');
const disk = require('tilestrata-disk');
const sharp = require('tilestrata-sharp');
const mapnik = require('tilestrata-mapnik');
const dependency = require('tilestrata-dependency');
const strata = tilestrata();
// define layers
strata.layer('basemap')
.route('tile@2x.png')
.use(disk.cache({dir: '/var/lib/tiles/basemap'}))
.use(mapnik({
pathname: '/path/to/map.xml',
tileSize: 512,
scale: 2
}))
.route('tile.png')
.use(disk.cache({dir: '/var/lib/tiles/basemap'}))
.use(dependency('basemap', 'tile@2x.png'))
.use(sharp(function(image, sharp) {
return image.resize(256);
}));
// start accepting requests
strata.listen(8080);
```
Once configured and started, tiles can be accessed via:
```
/:layer/:z/:x:/:y/:filename
```
### Routing Without Filenames
As of [2.1.0](https://github.com/naturalatlas/tilestrata/releases/tag/v2.1.0), if you desire a routing scheme that's closer to other tile servers (where there's no filename) like outlined in [#21](https://github.com/naturalatlas/tilestrata/pull/21), use the following format when registering routes:
```js
.route('*.png') // /layer/0/0/0.png
.route('*@2x.png') // /layer/0/0/0@2x.png
```
### Integrate with [Express.js](http://expressjs.com/) / [Connect](https://github.com/senchalabs/connect)
TileStrata comes with middleware for Express that makes serving tiles from an existing application really simple, eliminating the need to call `listen` on `strata`.
```js
const tilestrata = require('tilestrata');
const strata = tilestrata();
strata.layer('basemap') /* ... */
strata.layer('contours') /* ... */
app.use(tilestrata.middleware({
server: strata,
prefix: '/maps'
}));
```
## Usage Notes
### Fault-Tolerant Initialization
By default, TileStrata will error when initializing if any of the layer handlers fail to initialize. If you would like to ignore errors so that _other_ layers are booted up and available, use the `skipFailures` option:
```js
var strata = tilestrata({ skipFailures: true });
```
### Metatile-Aware Load Balancing & Layer Sharding
TileStrata >= [2.0.0](https://github.com/naturalatlas/tilestrata/releases/tag/v2.0.0) supports integration with [TileStrata Balancer](https://github.com/naturalatlas/tilestrata-balancer), an elastic load balancer designed specifically for the nuances of tile serving – particularly [metatiles](http://wiki.openstreetmap.org/wiki/Meta_tiles). Generic load balancers have no knowledge of metatiles and thus will naively split tile requests out to multiple servers which leads to redundant rendering (slow and a waste of computing power).
As an added bonus, the balancer does not assume all servers in the pool have the same layers available. The balancer keeps track of the layers provided on each node so it knows where to route. In sum, the overview:
- **Fully elastic** w/minimal setup
- **Consistent routing** (improves local cache hits)
- **Metatile-aware** (prevents redundant rendering)
- **Layer-aware** (allows heterogeneous distribution of layers in the cluster)
[**View TileStrata Balancer Documentation →**](https://github.com/naturalatlas/tilestrata-balancer)
*Note: One could use cookie-persistence with traditional load balancers, but this forces users onto a single machine (not optimal).*
### Rebuilding the Tile Cache
If you update your map styles or data, you'll probably want to update your tiles. Rather than dump all of them at once and bring your tile server to a crawl, progressively rebuild the cache by requesting tiles with the `X-TileStrata-SkipCache` header. [TileMantle](https://github.com/naturalatlas/tilemantle) makes this process easy:
```
npm install -g tilemantle
tilemantle http://myhost.com/mylayer/{z}/{x}/{y}/t.png \
-p 44.9457507,-109.5939822 -b 30mi -z 10-14 \
-H "X-TileStrata-SkipCache:mylayer/t.png"
```
For the sake of the [tilestrata-dependency](https://github.com/naturalatlas/tilestrata-dependency) plugin, the value of the header is expected to be in the format:
```
X-TileStrata-SkipCache:*
X-TileStrata-SkipCache:[layer]/[file],[layer]/[file],...
```
In advanced use cases, it might be necessary for tiles to not be returned by the server until the cache is actually written (particularly when order matters due to dependencies). To achieve this, use:
```
X-TileStrata-CacheWait:1
```
### Health Checks
TileStrata includes a `/health` endpoint that will return a `200 OK` if it can accept connections. The response will always be JSON. By setting the `"healthy"` option to a function that accepts a callback you can take it a step further and control the status and data that it returns.
```js
// not healthy
const strata = tilestrata({
healthy: function(callback) {
callback(new Error('CPU is too high'), {loadavg: 3});
}
});
// healthy
const strata = tilestrata({
healthy: function(callback) {
callback(null, {loadavg: 1});
}
});
```
### Profiling / Debugging Performance
Unless the `TILESTRATA_NOPROFILE` environment variable is set, TileStrata keeps track of basic latency and size information (min, max, avg) for all steps in the tile serving flow for the lifetime of the process. Data is kept for every plugin on every route of every layer and is broken down by zoom level. To access it, visit: `/profile` in your browser. If this information needs to be kept private, you can set the `TILESTRATA_PASSWORD` environment variable to a password that TileStrata will prompt for (username is ignored). The page will have tables like the one below:
| t.png | z1 | z2 | z3 | z4 | z5 | z6 | z7 | z8 | z9 | |
|---|---|---|---|---|---|---|---|---|---|---|
reqhook#0 | errors | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
dur_samples | 1 | 1 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | |
dur_max | 45 | 44 | 43 | 46 | 52 | 50 | 58 | 61 | 81 | |
dur_min | 45 | 44 | 42 | 46 | 52 | 50 | 58 | 61 | 81 | |
dur_avg | 45 | 44 | 42.5 | 46 | 52 | 50 | 58 | 61 | 81 | |
cache#0.get | errors | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
dur_samples | 1 | 1 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | |
dur_max | 45 | 45 | 44 | 58 | 47 | 62 | 46 | 60 | 53 | |
dur_min | 45 | 45 | 44 | 58 | 47 | 62 | 46 | 60 | 53 | |
dur_avg | 45 | 45 | 44 | 58 | 47 | 62 | 46 | 60 | 53 | |
hits | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
misses | 1 | 1 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | |
provider#0 | errors | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
dur_samples | 1 | 1 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | |
dur_max | 34 | 43 | 96 | 122 | 119 | 108 | 115 | 103 | 129 | |
dur_min | 34 | 43 | 64 | 122 | 119 | 108 | 115 | 103 | 129 | |
dur_avg | 34 | 43 | 80 | 122 | 119 | 108 | 115 | 103 | 129 | |
size_samples | 1 | 1 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | |
size_max | 500B | 501B | 576B | 578B | 504B | 540B | 501B | 776B | 736B | |
size_min | 500B | 501B | 565B | 578B | 504B | 540B | 501B | 776B | 736B | |
size_avg | 500B | 501B | 571B | 578B | 504B | 540B | 501B | 776B | 736B | |
transform#0 | errors | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
dur_samples | 1 | 1 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | |
dur_max | 32 | 33 | 35 | 61 | 49 | 57 | 53 | 50 | 69 | |
dur_min | 32 | 33 | 34 | 61 | 49 | 57 | 53 | 50 | 69 | |
dur_avg | 32 | 33 | 34.5 | 61 | 49 | 57 | 53 | 50 | 69 | |
reshook#0 | errors | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
dur_samples | 1 | 1 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | |
dur_max | 45 | 43 | 45 | 63 | 63 | 55 | 48 | 60 | 68 | |
dur_min | 45 | 43 | 44 | 63 | 63 | 55 | 48 | 60 | 68 | |
dur_avg | 45 | 43 | 44.5 | 63 | 63 | 55 | 48 | 60 | 68 | |
cache#0.set | errors | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
dur_samples | 1 | 1 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | |
dur_max | 12 | 13 | 13 | 14 | 27 | 23 | 26 | 29 | 27 | |
dur_min | 12 | 13 | 10 | 14 | 27 | 23 | 26 | 29 | 27 | |
dur_avg | 12 | 13 | 11.5 | 14 | 27 | 23 | 26 | 29 | 27 | |