Skip to content

Commit 9acd68c

Browse files
committed
feat(feature): add tippecanoe output
1 parent 2d6c393 commit 9acd68c

File tree

2 files changed

+96
-0
lines changed

2 files changed

+96
-0
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,22 @@ wof feature ogr --docker
240240

241241
see: https://gdal.org/programs/ogr2ogr.html
242242

243+
#### tippecanoe
244+
245+
a convenience command for piping a feature stream into `tippecanoe`:
246+
247+
```bash
248+
cat jsonstream | wof feature tippecanoe --unlink example.pmtiles
249+
```
250+
251+
use a dockerized version of `tippecanoe` (to avoid installing locally):
252+
253+
```bash
254+
wof feature tippecanoe --docker
255+
```
256+
257+
see: https://github.com/mapbox/tippecanoe
258+
243259
#### Feature Properties
244260

245261
output feature properties

bin/cmd/feature/tippecanoe.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
const _ = require('lodash')
2+
const path = require('path')
3+
const Stream = require('stream')
4+
const feature = require('../../../whosonfirst/feature')
5+
const stream = {
6+
json: require('../../../stream/json'),
7+
shell: require('../../../stream/shell'),
8+
miss: require('../../../stream/miss')
9+
}
10+
11+
module.exports = {
12+
command: 'tippecanoe <dst>',
13+
describe: 'export features using tippecanoe',
14+
builder: (yargs) => {
15+
yargs.positional('dst', {
16+
type: 'string',
17+
required: true,
18+
describe: 'tippecanoe `--output` argument.'
19+
})
20+
yargs.option('docker', {
21+
type: 'boolean',
22+
default: false,
23+
describe: 'use a docker image for tippecanoe'
24+
})
25+
yargs.option('unlink', {
26+
type: 'boolean',
27+
default: false,
28+
alias: 'rm',
29+
describe: 'tippecanoe `--force` argument.'
30+
})
31+
yargs.option('image', {
32+
type: 'string',
33+
default: 'versatiles/versatiles-tippecanoe',
34+
describe: 'docker image to use'
35+
})
36+
},
37+
handler: (argv) => {
38+
const tap = new Stream.PassThrough()
39+
process.stdin.once('data', () => { // avoid empty stdin
40+
tap.pipe(argv.docker ? docker(argv) : local(argv)).pipe(process.stdout)
41+
})
42+
.pipe(stream.json.parse())
43+
.pipe(stream.miss.through.obj((feat, enc, next) => {
44+
// add tippecanoe config
45+
let layer = feature.getPlacetype(feat)
46+
if (feature.isAltGeometry(feat)) { layer = `alt-${feature.getAltLabel(feat)}` }
47+
_.set(feat, 'tippecanoe.layer', layer)
48+
next(null, feat)
49+
}))
50+
.pipe(stream.json.stringify('', '\n', '')) // add a newline between features
51+
.pipe(tap)
52+
}
53+
}
54+
55+
function flags (argv) {
56+
return [
57+
'-zg', // automatically choose maxzoom
58+
'--projection=EPSG:4326',
59+
...(argv.unlink ? ['--force'] : [])
60+
]
61+
}
62+
63+
function local (argv) {
64+
return stream.shell.duplex('tippecanoe', [
65+
...flags(argv),
66+
'--output',
67+
argv.dst
68+
])
69+
}
70+
71+
function docker (argv) {
72+
return stream.shell.duplex('docker', [
73+
'run', '-i', '--rm',
74+
'-v', `${path.dirname(path.resolve(process.cwd(), argv.dst))}:/work`,
75+
argv.image,
76+
...flags(argv),
77+
'--output',
78+
`/work/${path.basename(argv.dst)}`
79+
])
80+
}

0 commit comments

Comments
 (0)