Skip to content

Commit 62b7930

Browse files
committed
feat: add env vars to control exclusion, inclusion and aliases
1 parent 3b58d65 commit 62b7930

3 files changed

Lines changed: 110 additions & 7 deletions

File tree

README.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# @data-fair/tileserver
2+
3+
Vector tile server for the data-fair stack. Wraps [tileserver-gl-light](https://github.com/maptiler/tileserver-gl) and pulls tilesets and styles from a [data-fair/registry](https://github.com/data-fair/registry) instance at boot.
4+
5+
## Configuration
6+
7+
| Variable | Required | Default | Description |
8+
|----------|----------|---------|-------------|
9+
| `REGISTRY_URL` | yes | | Base URL of the data-fair registry |
10+
| `REGISTRY_SECRET` | yes | | Secret key for registry authentication |
11+
| `DATA_DIR` | no | `/data` | Root directory for cache and config |
12+
| `FONTS_DIR` | no | `/app/fonts` | Directory containing font files |
13+
| `PORT` | no | `8080` | HTTP server port |
14+
| `LOG_LEVEL` | no | `info` | One of `trace`, `debug`, `info`, `warn`, `error` |
15+
| `OBSERVER_ACTIVE` | no | `true` | Enable Prometheus metrics |
16+
| `OBSERVER_PORT` | no | `9090` | Prometheus metrics port |
17+
| `TILESET_INCLUDE` | no | | Comma-separated list of tileset IDs to include (if set, all others are excluded) |
18+
| `TILESET_EXCLUDE` | no | | Comma-separated list of tileset IDs to exclude |
19+
| `TILESET_ALIASES` | no | | Comma-separated `source:key` pairs to remap tileset serving keys |
20+
| `STYLE_INCLUDE` | no | | Comma-separated list of style IDs to include (if set, all others are excluded) |
21+
| `STYLE_EXCLUDE` | no | | Comma-separated list of style IDs to exclude |
22+
| `STYLE_ALIASES` | no | | Comma-separated `source:key` pairs to remap style serving names |
23+
24+
### Filtering and aliasing
25+
26+
**Include/Exclude** control which artefacts are downloaded from the registry. If `*_INCLUDE` is set, only listed IDs are kept. `*_EXCLUDE` removes listed IDs. When both are set, include is applied first, then exclude.
27+
28+
**Aliases** remap the serving key of an artefact without changing the downloaded file. The format is `source:key` where `source` is the artefact ID in the registry and `key` is the desired serving key.
29+
30+
Example: serve `france.mbtiles` under the `world` key instead of downloading `world.mbtiles`:
31+
32+
```bash
33+
TILESET_EXCLUDE=world
34+
TILESET_ALIASES=france:world
35+
```
36+
37+
Example: use a timestamped tileset as a generic data layer:
38+
39+
```bash
40+
TILESET_ALIASES=contours-2026:contours
41+
```
42+
43+
## Development
44+
45+
```bash
46+
# start dependencies (registry mock via docker)
47+
npm run dev-deps
48+
49+
# start the dev server
50+
npm run dev
51+
52+
# run tests
53+
npm test
54+
55+
# lint and type-check
56+
npm run quality
57+
```

src/build-config.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ export interface TileserverConfig {
2424
data: Record<string, { mbtiles: string }>
2525
}
2626

27+
const filterArtefacts = (artefacts: Artefact[], include: string[], exclude: string[]): Artefact[] => {
28+
let filtered = artefacts
29+
if (include.length) filtered = filtered.filter(a => include.includes(a._id))
30+
if (exclude.length) filtered = filtered.filter(a => !exclude.includes(a._id))
31+
return filtered
32+
}
33+
2734
const stylePackageName = (a: Artefact): string => {
2835
const name = a.name.replace(/^@[^/]+\//, '')
2936
return name.replace(/[^a-z0-9_-]/gi, '-')
@@ -50,9 +57,14 @@ export const buildTileserverConfig = async (): Promise<TileserverConfig> => {
5057
const styles = await listArtefacts({ category: 'maplibre-style', format: 'file' })
5158
log.info(`found ${styles.length} styles`)
5259

60+
const filteredTilesets = filterArtefacts(tilesets, config.tilesetInclude, config.tilesetExclude)
61+
if (filteredTilesets.length !== tilesets.length) {
62+
log.info(`filtered to ${filteredTilesets.length} tilesets`)
63+
}
64+
5365
const data: TileserverConfig['data'] = {}
5466
const tilesetIds = new Set<string>()
55-
for (const t of tilesets) {
67+
for (const t of filteredTilesets) {
5668
log.info(`ensuring tileset ${t._id}...`)
5769
const { downloaded } = await ensureArtefactFile({
5870
registryUrl: config.registryUrl,
@@ -62,12 +74,18 @@ export const buildTileserverConfig = async (): Promise<TileserverConfig> => {
6274
fileName: `${t._id}.mbtiles`
6375
})
6476
if (downloaded) log.info(`tileset ${t._id} downloaded`)
65-
data[t._id] = { mbtiles: `${t._id}.mbtiles` }
66-
tilesetIds.add(t._id)
77+
const dataKey = config.tilesetAliases[t._id] ?? t._id
78+
data[dataKey] = { mbtiles: `${t._id}.mbtiles` }
79+
tilesetIds.add(dataKey)
80+
}
81+
82+
const filteredStyles = filterArtefacts(styles, config.styleInclude, config.styleExclude)
83+
if (filteredStyles.length !== styles.length) {
84+
log.info(`filtered to ${filteredStyles.length} styles`)
6785
}
6886

6987
const stylesCfg: TileserverConfig['styles'] = {}
70-
for (const s of styles) {
88+
for (const s of filteredStyles) {
7189
log.info(`ensuring style ${s._id}...`)
7290
const { path: tarballPath, downloaded } = await ensureArtefactFile({
7391
registryUrl: config.registryUrl,
@@ -83,7 +101,7 @@ export const buildTileserverConfig = async (): Promise<TileserverConfig> => {
83101
await mkdir(styleDir, { recursive: true })
84102
await extractTarball(createReadStream(tarballPath), styleDir)
85103
}
86-
const styleName = stylePackageName(s)
104+
const styleName = config.styleAliases[s._id] ?? stylePackageName(s)
87105
await normalizeStyle({ styleDir, styleName, tilesetIds })
88106
const rel = relative(dirs.styles, join(styleDir, 'style.json'))
89107
stylesCfg[styleName] = { style: rel }

src/config.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,29 @@ const EnvSchema = z.object({
88
PORT: z.coerce.number().int().positive().default(8080),
99
LOG_LEVEL: z.enum(['trace', 'debug', 'info', 'warn', 'error']).default('info'),
1010
OBSERVER_ACTIVE: z.enum(['true', 'false']).default('true').transform(v => v === 'true'),
11-
OBSERVER_PORT: z.coerce.number().int().positive().default(9090)
11+
OBSERVER_PORT: z.coerce.number().int().positive().default(9090),
12+
TILESET_INCLUDE: z.string().default('').transform(v => v ? v.split(',').map(s => s.trim()).filter(Boolean) : []),
13+
TILESET_EXCLUDE: z.string().default('').transform(v => v ? v.split(',').map(s => s.trim()).filter(Boolean) : []),
14+
TILESET_ALIASES: z.string().default('').transform(v => {
15+
if (!v) return {} as Record<string, string>
16+
const map: Record<string, string> = {}
17+
for (const pair of v.split(',')) {
18+
const [source, alias] = pair.split(':').map(s => s.trim())
19+
if (source && alias) map[source] = alias
20+
}
21+
return map
22+
}),
23+
STYLE_INCLUDE: z.string().default('').transform(v => v ? v.split(',').map(s => s.trim()).filter(Boolean) : []),
24+
STYLE_EXCLUDE: z.string().default('').transform(v => v ? v.split(',').map(s => s.trim()).filter(Boolean) : []),
25+
STYLE_ALIASES: z.string().default('').transform(v => {
26+
if (!v) return {} as Record<string, string>
27+
const map: Record<string, string> = {}
28+
for (const pair of v.split(',')) {
29+
const [source, alias] = pair.split(':').map(s => s.trim())
30+
if (source && alias) map[source] = alias
31+
}
32+
return map
33+
})
1234
})
1335

1436
const parsed = EnvSchema.safeParse(process.env)
@@ -32,7 +54,13 @@ const config = {
3254
observer: {
3355
active: env.OBSERVER_ACTIVE,
3456
port: env.OBSERVER_PORT
35-
}
57+
},
58+
tilesetInclude: env.TILESET_INCLUDE,
59+
tilesetExclude: env.TILESET_EXCLUDE,
60+
tilesetAliases: env.TILESET_ALIASES,
61+
styleInclude: env.STYLE_INCLUDE,
62+
styleExclude: env.STYLE_EXCLUDE,
63+
styleAliases: env.STYLE_ALIASES
3664
} as const
3765

3866
export default config

0 commit comments

Comments
 (0)