Example
Historical Boundaries
The Newberry Library's Atlas of Historical County Boundaries records every change to U.S. state and county lines. This configuration serves it as vector tiles straight from PostGIS, where a single layer answers for any year, such as `boundaries_1860`.
MVT
The data is loaded into two tables with a tool such as shp2pgsql, from the atlas’s state and county shapefiles. Each row has a start_date and end_date for the period it was in effect.
|
Configuration
-
YAML
-
JSON
server:
tilejson:
enabled: true
datastores:
- name: postgresql (1)
id: history
host: db.internal
user: tilegroxy
password: env.POSTGRES_PASSWORD
database: history
cache:
name: disk
path: /var/cache/tilegroxy
analytics:
name: postgresql
datastore: history (2)
table: tile_events
fields:
- layerparams (3)
layers:
- id: boundaries
pattern: boundaries_{year} (4)
paramValidator:
year: "1[6-9][0-9]{2}|20[0-9]{2}" (5)
examples:
- boundaries_1790
- boundaries_1860
maxzoom: 12 (6)
cacheversion: "2" (7)
vectorlayers: (8)
- id: states
fields:
name: State or territory name
- id: counties
fields:
full_name: County name including state
provider:
name: compositemvt (9)
providers:
- name: postgismvt
datastore: history
layer: states (10)
table: public.us_histstateterr
gid: id_num
attributes:
- name
filter: "to_date({layer.year}, 'yyyy') BETWEEN start_date AND end_date" (11)
- name: postgismvt
datastore: history
layer: counties
table: public.us_histcounties
gid: id_num
attributes:
- full_name
filter: "to_date({layer.year}, 'yyyy') BETWEEN start_date AND end_date"
limit: 5000 (12)
| 1 | One connection pool serves both the vector tiles and analytics. See PostgreSQL. |
| 2 | Events are written to the same database as the data. The tile_events table must be created ahead of time as described in PostgreSQL analytics. |
| 3 | Records the placeholder values of each request, so the extra column shows which years people look at, such as {"layerparams": {"year": "1860"}}. |
| 4 | Any name matching the pattern is served by this layer, with the year available to providers as {layer.year}. See Layer Patterns. |
| 5 | Only four digit years from 1600 to 2099 are accepted. Anything else is rejected before a query runs. |
| 6 | Requests past zoom 12 are rejected rather than querying the database for tiles with no more detail. TileJSON advertises this limit, so map clients stretch zoom 12 tiles instead of requesting deeper ones. |
| 7 | Changing this value invalidates every cached tile for the layer without deleting anything. Bump it after reloading the tables. |
| 8 | Describes the sub-layers inside the tiles. It’s published through TileJSON, once for each name under examples, so a map client can style the data without inspecting a tile. |
| 9 | Merges the output of both queries into one tile. The children run in parallel and see the same {layer.year}. See Composite MVT. |
| 10 | Names the sub-layer inside the tile. Without it, both queries would use the requested layer name, such as boundaries_1860, and collide. |
| 11 | The year is sent to PostgreSQL as a prepared statement parameter rather than pasted into the SQL, so a request can’t inject SQL through it. The rest of the filter is operator input and is used as-is. See Security. |
| 12 | Caps the number of counties in a tile, protecting the database from the most expensive low zoom requests. |
{
"server": {
"tilejson": {
"enabled": true
}
},
"datastores": [
{
"name": "postgresql", (1)
"id": "history",
"host": "db.internal",
"user": "tilegroxy",
"password": "env.POSTGRES_PASSWORD",
"database": "history"
}
],
"cache": {
"name": "disk",
"path": "/var/cache/tilegroxy"
},
"analytics": {
"name": "postgresql",
"datastore": "history", (2)
"table": "tile_events",
"fields": [
"layerparams" (3)
]
},
"layers": [
{
"id": "boundaries",
"pattern": "boundaries_{year}", (4)
"paramValidator": {
"year": "1[6-9][0-9]{2}|20[0-9]{2}" (5)
},
"examples": [
"boundaries_1790",
"boundaries_1860"
],
"maxzoom": 12, (6)
"cacheversion": "2", (7)
"vectorlayers": [ (8)
{
"id": "states",
"fields": {
"name": "State or territory name"
}
},
{
"id": "counties",
"fields": {
"full_name": "County name including state"
}
}
],
"provider": {
"name": "compositemvt", (9)
"providers": [
{
"name": "postgismvt",
"datastore": "history",
"layer": "states", (10)
"table": "public.us_histstateterr",
"gid": "id_num",
"attributes": [
"name"
],
"filter": "to_date({layer.year}, 'yyyy') BETWEEN start_date AND end_date" (11)
},
{
"name": "postgismvt",
"datastore": "history",
"layer": "counties",
"table": "public.us_histcounties",
"gid": "id_num",
"attributes": [
"full_name"
],
"filter": "to_date({layer.year}, 'yyyy') BETWEEN start_date AND end_date",
"limit": 5000 (12)
}
]
}
}
]
}
| 1 | One connection pool serves both the vector tiles and analytics. See PostgreSQL. |
| 2 | Events are written to the same database as the data. The tile_events table must be created ahead of time as described in PostgreSQL analytics. |
| 3 | Records the placeholder values of each request, so the extra column shows which years people look at, such as {"layerparams": {"year": "1860"}}. |
| 4 | Any name matching the pattern is served by this layer, with the year available to providers as {layer.year}. See Layer Patterns. |
| 5 | Only four digit years from 1600 to 2099 are accepted. Anything else is rejected before a query runs. |
| 6 | Requests past zoom 12 are rejected rather than querying the database for tiles with no more detail. TileJSON advertises this limit, so map clients stretch zoom 12 tiles instead of requesting deeper ones. |
| 7 | Changing this value invalidates every cached tile for the layer without deleting anything. Bump it after reloading the tables. |
| 8 | Describes the sub-layers inside the tiles. It’s published through TileJSON, once for each name under examples, so a map client can style the data without inspecting a tile. |
| 9 | Merges the output of both queries into one tile. The children run in parallel and see the same {layer.year}. See Composite MVT. |
| 10 | Names the sub-layer inside the tile. Without it, both queries would use the requested layer name, such as boundaries_1860, and collide. |
| 11 | The year is sent to PostgreSQL as a prepared statement parameter rather than pasted into the SQL, so a request can’t inject SQL through it. The rest of the filter is operator input and is used as-is. See Security. |
| 12 | Caps the number of counties in a tile, protecting the database from the most expensive low zoom requests. |
Try it
export POSTGRES_PASSWORD=your-password
tilegroxy test -c historical-boundaries.yml
tilegroxy serve -c historical-boundaries.yml
The test command uses the two examples names, since a pattern layer has no single name of its own. Compare /tiles/boundaries_1790/7/36/49 with /tiles/boundaries_1870/7/36/49 to see Virginia before and after West Virginia split from it.