diff --git a/src/serve_data.js b/src/serve_data.js index e3cb6ca..574a50c 100644 --- a/src/serve_data.js +++ b/src/serve_data.js @@ -116,6 +116,105 @@ export const serve_data = { }, ); + app.get( + '/:id/:t(\\d+)/:z(\\d+)/:x(\\d+)/:y(\\d+).:format([\\w.]+)', + (req, res, next) => { + const item = repo[req.params.id]; + if (!item) { + return res.sendStatus(404); + } + let tileJSONFormat = item.tileJSON.format; + const t = req.params.t | 0; + const z = req.params.z | 0; + const x = req.params.x | 0; + const y = req.params.y | 0; + let format = req.params.format; + if (format === options.pbfAlias) { + format = 'pbf'; + } + if ( + format !== tileJSONFormat && + !(format === 'geojson' && tileJSONFormat === 'pbf') + ) { + return res.status(404).send('Invalid format'); + } + if ( + z < item.tileJSON.minzoom || + 0 || + x < 0 || + y < 0 || + z > item.tileJSON.maxzoom || + x >= Math.pow(2, z) || + y >= Math.pow(2, z) || + t < 0 + ) { + return res.status(404).send('Out of bounds'); + } + item.source.getTileT(t, z, x, y, (err, data, headers) => { + let isGzipped; + if (err) { + if (/does not exist/.test(err.message)) { + return res.status(204).send(); + } else { + return res.status(500).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 = zlib.unzipSync(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 = zlib.unzipSync(data); + isGzipped = false; + } + + const tile = new VectorTile(new Pbf(data)); + const geojson = { + type: 'FeatureCollection', + features: [], + }; + for (let 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']; + headers['Content-Encoding'] = 'gzip'; + res.set(headers); + + if (!isGzipped) { + data = zlib.gzipSync(data); + isGzipped = true; + } + + return res.status(200).send(data); + } + } + }); + }, + ); + app.get('/:id.json', (req, res, next) => { const item = repo[req.params.id]; if (!item) { diff --git a/src/server.js b/src/server.js index 0850e5f..7d38acb 100644 --- a/src/server.js +++ b/src/server.js @@ -275,7 +275,33 @@ function start(opts) { ); } + let addData = (id, item) => { + console.log(`Add data ${id}`); + startupPromises.push( + serve_data.add(options, serving.data, item, id, opts.publicUrl), + ); + }; + if (options.serveAllStyles) { + console.log('Loading all data and styles'); + fs.readdir(options.paths.mbtiles, { withFileTypes: true }, (err, files) => { + if (err) { + return; + } + for (const file of files) { + if ( + file.isFile() && + path.extname(file.name).toLowerCase() === '.mbtiles' + ) { + let id = path.basename(file.name, '.mbtiles'); + let item = { + mbtiles: file.name, + }; + serve_data.add(options, serving.data, item, id, opts.publicUrl); + } + } + }); + fs.readdir(options.paths.styles, { withFileTypes: true }, (err, files) => { if (err) { return; @@ -286,7 +312,27 @@ function start(opts) { const item = { style: file.name, }; - addStyle(id, item, false, false); + addStyle(id, item, true, true); + } + } + }); + + const watcherData = chokidar.watch( + path.join(options.paths.mbtiles, '*.mbtiles'), + {}, + ); + watcherData.on('all', (eventType, filename) => { + if (filename) { + let id = path.basename(filename, '.mbtiles'); + + if (eventType == 'add') { + console.log(`Data "${id}" added`); + let item = { + mbtiles: filename, + }; + addData(id, item); + } else if (eventType == 'change') { + console.log(`Data "${id}" changed`); } } }); @@ -309,7 +355,7 @@ function start(opts) { const item = { style: filename, }; - addStyle(id, item, false, false); + addStyle(id, item, true, true); } } });