diff --git a/Dockerfile b/Dockerfile index 8460f46..5ecf2a5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -76,7 +76,7 @@ RUN mkdir -p /data && chown node:node /data VOLUME /data WORKDIR /data -EXPOSE 80 +EXPOSE 8000 USER node:node diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index b4defe6..85028b0 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -20,7 +20,7 @@ trap refresh HUP if ! which -- "${1}"; then # first arg is not an executable - xvfb-run -a --server-args="-screen 0 1024x768x24" -- node /usr/src/app/ -p 80 "$@" & + xvfb-run -a --server-args="-screen 0 1024x768x24" -- node /usr/src/app/ -p 8000 "$@" & # Wait exits immediately on signals which have traps set. Store return value and wait # again for all jobs to actually complete before continuing. wait $! || RETVAL=$? diff --git a/src/serve_data.js b/src/serve_data.js index 1c8a70b..c3edfb4 100644 --- a/src/serve_data.js +++ b/src/serve_data.js @@ -10,108 +10,97 @@ import MBTiles from '@mapbox/mbtiles'; import Pbf from 'pbf'; import VectorTile from '@mapbox/vector-tile'; -import { getTileUrls, fixTileJSONCenter } from './utils.js'; +import {getTileUrls, fixTileJSONCenter} from './utils.js'; export const serve_data = { init: (options, repo) => { const app = express().disable('x-powered-by'); - app.get( - '/:id/:z(\\d+)/:x(\\d+)/:y(\\d+).:format([\\w.]+)', - (req, res, next) => { - const item = repo[req.params.id]; - if (!item) { - return res.sendStatus(404); - } - const tileJSONFormat = item.tileJSON.format; - 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 || + app.get('/:id/:z(\\d+)/:x(\\d+)/:y(\\d+).:format([\\w.]+)', (req, res, next) => { + const item = repo[req.params.id]; + if (!item) { + return res.sendStatus(404); + } + const tileJSONFormat = item.tileJSON.format; + 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) - ) { - return res.status(404).send('Out of bounds'); - } - item.source.getTile(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); - } + x >= Math.pow(2, options.wgs84 ? z + 1 : z) || y >= Math.pow(2, z)) { + return res.status(404).send('Out of bounds'); + } + item.source.getTile(z, x, y, (err, data, headers) => { + let isGzipped; + if (err) { + if (/does not exist/.test(err.message)) { + return res.status(204).send(); } 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'; - + 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; } - - 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); + data = options.dataDecoratorFunc(id, 'data', data, z, x, y); } - delete headers['ETag']; // do not trust the tile ETag -- regenerate - headers['Content-Encoding'] = 'gzip'; - res.set(headers); - - if (!isGzipped) { - data = zlib.gzipSync(data); - isGzipped = true; - } - - return res.status(200).send(data); } + 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 (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 = zlib.gzipSync(data); + isGzipped = true; + } + + return res.status(200).send(data); } - }); - }, - ); + } + }); + }); app.get('/:id.json', (req, res, next) => { const item = repo[req.params.id]; @@ -119,16 +108,10 @@ export const serve_data = { return res.sendStatus(404); } const info = clone(item.tileJSON); - info.tiles = getTileUrls( - req, - info.tiles, - `data/${req.params.id}`, - info.format, - item.publicUrl, - { - pbf: options.pbfAlias, - }, - ); + info.tiles = getTileUrls(req, info.tiles, + `data/${req.params.id}`, info.format, item.publicUrl, { + 'pbf': options.pbfAlias + }); return res.send(info); }); @@ -137,7 +120,7 @@ export const serve_data = { add: (options, repo, params, id, publicUrl) => { const mbtilesFile = path.resolve(options.paths.mbtiles, params.mbtiles); let tileJSON = { - tiles: params.domains || options.domains, + 'tiles': params.domains || options.domains }; const mbtilesFileStats = fs.statSync(mbtilesFile); @@ -146,7 +129,7 @@ export const serve_data = { } let source; const sourceInfoPromise = new Promise((resolve, reject) => { - source = new MBTiles(mbtilesFile + '?mode=ro', (err) => { + source = new MBTiles(mbtilesFile + '?mode=ro', err => { if (err) { reject(err); return; @@ -181,8 +164,8 @@ export const serve_data = { repo[id] = { tileJSON, publicUrl, - source, + source }; }); - }, + } };