tilegroxy Docs
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.