From 1ab22d990846b481479a05c12546b0f63fab4e48 Mon Sep 17 00:00:00 2001 From: Ivan Vazhenin Date: Mon, 18 Apr 2022 19:19:07 +0300 Subject: [PATCH] fix --- package.json | 12 ++++-- src/main.js | 2 +- src/serve_data.js | 90 ++++++++++++++++++++++++++++++++++++++++++- src/serve_rendered.js | 2 +- src/utils.js | 30 +++++++++++++++ 5 files changed, 130 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 6de09b1..cbf187e 100644 --- a/package.json +++ b/package.json @@ -20,9 +20,9 @@ "@mapbox/glyph-pbf-composite": "0.0.3", "@mapbox/mapbox-gl-native": "5.0.2", "@mapbox/mapbox-gl-style-spec": "13.12.0", - "@mapbox/mbtiles": "0.11.0", "@mapbox/sphericalmercator": "1.1.0", "@mapbox/vector-tile": "1.3.1", + "@mapbox/tiletype": "0.3.x", "advanced-pool": "0.3.3", "canvas": "2.6.1", "chokidar": "3.3.1", @@ -39,11 +39,17 @@ "proj4": "2.6.0", "request": "2.88.2", "sharp": "0.26.2", - "tileserver-gl-styles": "2.0.0" + "tileserver-gl-styles": "2.0.0", + "d3-queue": "~3.0.7", + "sqlite3": "^5.0.0" }, "devDependencies": { "mocha": "^7.1.0", "should": "^13.2.3", - "supertest": "^4.0.2" + "supertest": "^4.0.2", + "eslint": "~5.16.0", + "eslint-config-unstyled": "^1.1.0", + "sinon": "^9.0.2", + "tape": "~4.10.1" } } diff --git a/src/main.js b/src/main.js index 2fb3723..188fe4d 100644 --- a/src/main.js +++ b/src/main.js @@ -8,7 +8,7 @@ const fs = require('fs'); const path = require('path'); const request = require('request'); -const MBTiles = require('@mapbox/mbtiles'); +const MBTiles = require('./mbtiles'); const packageJson = require('../package'); diff --git a/src/serve_data.js b/src/serve_data.js index 37649bf..c08547a 100644 --- a/src/serve_data.js +++ b/src/serve_data.js @@ -6,7 +6,7 @@ const zlib = require('zlib'); const clone = require('clone'); const express = require('express'); -const MBTiles = require('@mapbox/mbtiles'); +const MBTiles = require('./mbtiles'); const Pbf = require('pbf'); const VectorTile = require('@mapbox/vector-tile').VectorTile; @@ -102,6 +102,94 @@ module.exports = { }); }); + 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']; // 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]; if (!item) { diff --git a/src/serve_rendered.js b/src/serve_rendered.js index 11b6bfb..55efbf1 100644 --- a/src/serve_rendered.js +++ b/src/serve_rendered.js @@ -18,7 +18,7 @@ const Color = require('color'); const express = require('express'); const mercator = new (require('@mapbox/sphericalmercator'))(); const mbgl = require('@mapbox/mapbox-gl-native'); -const MBTiles = require('@mapbox/mbtiles'); +const MBTiles = require('./mbtiles'); const proj4 = require('proj4'); const request = require('request'); diff --git a/src/utils.js b/src/utils.js index 6037e20..2dfbcba 100644 --- a/src/utils.js +++ b/src/utils.js @@ -7,6 +7,36 @@ const clone = require('clone'); const glyphCompose = require('@mapbox/glyph-pbf-composite'); +var EARTH_RADIUS = 6371.0088; + +function degToRad(degrees) { + return degrees * (Math.PI / 180); +} + +function tileToLon(tileX, zoom) { + return ((tileX / 2**zoom) * 360.0) - 180.0; +} + +function tileToLat(tileY, zoom) { + var n = Math.PI - 2 * Math.PI * tileY / 2**zoom; + return (180.0 / Math.PI) * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))); +} + +/** + * + * @param {Number} zoom + * @param {Number} tileX + * @param {Number} tileY + * @returns {Number} SqKM of a given tile + */ +module.exports.calculateTileArea = (zoom, tileX, tileY) => { + var left = degToRad(tileToLon(tileX, zoom)); + var top = degToRad(tileToLat(tileY, zoom)); + var right = degToRad(tileToLon(tileX + 1, zoom)); + var bottom = degToRad(tileToLat(tileY + 1, zoom)); + return (Math.PI / degToRad(180)) * EARTH_RADIUS**2 * Math.abs(Math.sin(top) - Math.sin(bottom)) * Math.abs(left - right); +}; + module.exports.getPublicUrl = (publicUrl, req) => publicUrl || `${req.protocol}://${req.headers.host}/`; module.exports.getTileUrls = (req, domains, path, format, publicUrl, aliases) => {