diff --git a/src/serve_data.js b/src/serve_data.js index 60b91b6..481b059 100644 --- a/src/serve_data.js +++ b/src/serve_data.js @@ -109,14 +109,145 @@ export const serve_data = { data = await gunzipP(data); isGzipped = false; } - data = options.dataDecoratorFunc( - req.params.id, - 'data', - data, + } else if (item.sourceType === 'mbtiles') { + item.source.getTile(z, x, y, async (err, data, headers) => { + let isGzipped; + if (err) { + if (/does not exist/.test(err.message)) { + return res.status(204).send(); + } else { + return res + .status(500) + .header('Content-Type', 'text/plain') + .send(err.message); + } + } else { + if (data == null) { + return res.status(404).send('Not found'); + } else { + if (tileJSONFormat === 'pbf') { + isGzipped = + data.slice(0, 2).indexOf(Buffer.from([0x1f, 0x8b])) === 0; + if (options.dataDecoratorFunc) { + if (isGzipped) { + data = await gunzipP(data); + isGzipped = false; + } + data = options.dataDecoratorFunc(id, 'data', data, z, x, y); + } + } + if (format === 'pbf') { + headers['Content-Type'] = 'application/x-protobuf'; + } else if (format === 'geojson') { + headers['Content-Type'] = 'application/json'; + + if (isGzipped) { + data = await gunzipP(data); + isGzipped = false; + } + + const tile = new VectorTile(new Pbf(data)); + const geojson = { + type: 'FeatureCollection', + features: [], + }; + for (const layerName in tile.layers) { + const layer = tile.layers[layerName]; + for (let i = 0; i < layer.length; i++) { + const feature = layer.feature(i); + const featureGeoJSON = feature.toGeoJSON(x, y, z); + featureGeoJSON.properties.layer = layerName; + geojson.features.push(featureGeoJSON); + } + } + data = JSON.stringify(geojson); + } + delete headers['ETag']; // do not trust the tile ETag -- regenerate + headers['Content-Encoding'] = 'gzip'; + res.set(headers); + + if (!isGzipped) { + data = await gzipP(data); + } + + return res.status(200).send(data); + } + } + }); + } + }, + ); + + app.get( + '^/:id/contour/:z([0-9]+)/:x([-.0-9]+)/:y([-.0-9]+)', + async (req, res, next) => { + try { + const item = repo?.[req.params.id]; + if (!item) return res.sendStatus(404); + if (!item.source) return res.status(404).send('Missing source'); + if (!item.tileJSON) return res.status(404).send('Missing tileJSON'); + if (!item.sourceType) + return res.status(404).send('Missing sourceType'); + + const { source, tileJSON, sourceType } = item; + + if (sourceType !== 'pmtiles' && sourceType !== 'mbtiles') { + return res + .status(400) + .send('Invalid sourceType. Must be pmtiles or mbtiles.'); + } + + const encoding = tileJSON?.encoding; + if (encoding == null) { + return res.status(400).send('Missing tileJSON.encoding'); + } else if (encoding !== 'terrarium' && encoding !== 'mapbox') { + return res + .status(400) + .send('Invalid encoding. Must be terrarium or mapbox.'); + } + + const format = tileJSON?.format; + if (format == null) { + return res.status(400).send('Missing tileJSON.format'); + } else if (format !== 'webp' && format !== 'png') { + return res.status(400).send('Invalid format. Must be webp or png.'); + } + + const maxzoom = tileJSON?.maxzoom; + if (maxzoom == null) { + return res.status(400).send('Missing tileJSON.maxzoom'); + } + + const z = parseInt(req.params.z, 10); + const x = parseFloat(req.params.x); + const y = parseFloat(req.params.y); + + const demManagerInit = new LocalDemManager( + encoding, + maxzoom, + source, + sourceType, + ); + const demManager = await demManagerInit.getManager(); + + let levels = 6; + if (z <= 5) { + levels = 1000; + } else if (z <= 8) { + levels = 500; + } else if (z <= 10) { + levels = 100; + } else if (z <= 12) { + levels = 50; + } else if (z <= 14) { + levels = 10; + } + + const { arrayBuffer } = await demManager.fetchContourTile( z, x, y, - { levels: [1000] }, + { levels: [levels] }, new AbortController(), ); }