From d68ab38442eca2c6ed6c689b0c2e35e05cd68ae9 Mon Sep 17 00:00:00 2001 From: Andrew Calcutt Date: Thu, 25 Jan 2024 21:23:07 -0500 Subject: [PATCH] Add support for 512 sized raster tiles - v2 (#1136) * test: using 512px rendered tiles Based on https://github.com/maptiler/tileserver-gl/pull/495 Signed-off-by: Andrew Calcutt * fix: use static renderer at zoom 0 renderer is not able to change the size of tile to more than 512px in tile mode Signed-off-by: Andrew Calcutt * Add support for 512 sized raster tiles (#1) * Enable setting tilesize for raster tiles * Serve correct endpoint for raster tiles * Add 256 & 512 sized raster layers to wmts getCapabilities document * Update wmts getCapabilities tileMatrixSets * Add rendered tiles format for getTileUrls method * Update endpoints documentation * Add and fix tiles_rendered tests * fix: formatting Signed-off-by: Andrew Calcutt * fix: corrent bad merge Signed-off-by: Andrew Calcutt * fix: if tile size is undefined don't add it needed for data endpoint right now Signed-off-by: Andrew Calcutt * fix: set tile size in raster endpoints to 512 Signed-off-by: Andrew Calcutt * fix: add semicolon Signed-off-by: Andrew Calcutt * fix: lint Signed-off-by: Andrew Calcutt * fix: test z1 512px file that actually exists Signed-off-by: Andrew Calcutt * fix: make tileSize optional Signed-off-by: Andrew Calcutt * fix: cleaner if statement Signed-off-by: Andrew Calcutt * docs: update tileSize to show as optional Signed-off-by: Andrew Calcutt * fix: allow tile size in data url Signed-off-by: Andrew Calcutt * fix: remove unneeded tileSize Signed-off-by: Andrew Calcutt * fix: set data as 256 tileSize for consistency Signed-off-by: Andrew Calcutt * docs: add note about optional data tileSize Signed-off-by: Andrew Calcutt * fix: lint Signed-off-by: Andrew Calcutt * Revert "fix: remove unneeded tileSize" This reverts commit a4583161bf53653d65a4606c407ba9b5249d1061. * fix: allow tile size to be set at json endpoints Signed-off-by: Andrew Calcutt * fix: set default endpoint tilesizes Signed-off-by: Andrew Calcutt * fix: don't use tilesize one data endpoint It doesn't do anything Signed-off-by: Andrew Calcutt * fix: default style endpoint to undefined Signed-off-by: Andrew Calcutt * fix: zoom 0 workaround Signed-off-by: Andrew Calcutt * Revert "fix: zoom 0 workaround" This reverts commit 0f3bbd765c9c151016cec66768675f990676c8b3. * fix: limit min zoom to 1 in viewer Signed-off-by: Andrew Calcutt * fix: put back orig string Signed-off-by: Andrew Calcutt * docs: add optional tilesize to TileJSON Signed-off-by: Andrew Calcutt * fix: cleanup thumbnails Signed-off-by: Andrew Calcutt * fix: default undefined like other data endpoints Signed-off-by: Andrew Calcutt * fix: move data xyz_link Signed-off-by: Andrew Calcutt * fix: remove console.log Signed-off-by: Andrew Calcutt * fix: lint Signed-off-by: Andrew Calcutt * chore: update version Signed-off-by: Andrew Calcutt * fix: update path Signed-off-by: Andrew Calcutt * fix: join error Signed-off-by: Andrew Calcutt * fix: revert path change Signed-off-by: Andrew Calcutt --------- Signed-off-by: Andrew Calcutt Co-authored-by: Petteri Pesonen --- docs/endpoints.rst | 11 +- package-lock.json | 4 +- package.json | 2 +- public/templates/data.tmpl | 1 + public/templates/index.tmpl | 2 +- public/templates/viewer.tmpl | 5 +- public/templates/wmts.tmpl | 400 +++++++++++++++++++++++++++++++++-- src/serve_data.js | 2 + src/serve_rendered.js | 13 +- src/server.js | 42 ++-- src/utils.js | 19 +- test/tiles_rendered.js | 73 +++++-- 12 files changed, 500 insertions(+), 74 deletions(-) diff --git a/docs/endpoints.rst b/docs/endpoints.rst index d025e46..7562813 100644 --- a/docs/endpoints.rst +++ b/docs/endpoints.rst @@ -13,11 +13,12 @@ Styles Rendered tiles ============== -* Rendered tiles are served at ``/styles/{id}/{z}/{x}/{y}[@2x].{format}`` +* Rendered tiles are served at ``/styles/{id}[/{tileSize}]/{z}/{x}/{y}[@2x].{format}`` - * The optional ``@2x`` (or ``@3x``, ``@4x``) part can be used to render HiDPI (retina) tiles + * The optional ratio ``@2x`` (ex. ``@2x``, ``@3x``, ``@4x``) part can be used to render HiDPI (retina) tiles + * The optional tile size ``/{tileSize}`` (ex. ``/256``, ``/512``). if omitted, tileSize defaults to 256. * Available formats: ``png``, ``jpg`` (``jpeg``), ``webp`` - * TileJSON at ``/styles/{id}.json`` + * TileJSON at ``/styles[/{tileSize}]/{id}.json`` * The rendered tiles are not available in the ``tileserver-gl-light`` version. @@ -91,13 +92,13 @@ Static images Source data =========== -* Source data are served at ``/data/{mbtiles}/{z}/{x}/{y}.{format}`` +* Source data are served at ``/data/{id}/{z}/{x}/{y}.{format}`` * Format depends on the source file (usually ``png`` or ``pbf``) * ``geojson`` is also available (useful for inspecting the tiles) in case the original format is ``pbf`` - * TileJSON at ``/data/{mbtiles}.json`` + * TileJSON at ``/data/{id}.json`` TileJSON arrays =============== diff --git a/package-lock.json b/package-lock.json index d892ff9..f27b567 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "tileserver-gl", - "version": "4.8.0", + "version": "4.9.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "tileserver-gl", - "version": "4.8.0", + "version": "4.9.0", "license": "BSD-2-Clause", "dependencies": { "@mapbox/glyph-pbf-composite": "0.0.3", diff --git a/package.json b/package.json index 4c776ba..0088df7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tileserver-gl", - "version": "4.8.0", + "version": "4.9.0", "description": "Map tile server for JSON GL styles - vector and server side generated raster tiles", "main": "src/main.js", "bin": "src/main.js", diff --git a/public/templates/data.tmpl b/public/templates/data.tmpl index 7aa3ddf..06d4938 100644 --- a/public/templates/data.tmpl +++ b/public/templates/data.tmpl @@ -124,6 +124,7 @@ for (tile_url in tile_urls) { L.tileLayer(tile_urls[tile_url], { + tileSize: 256, minZoom: tile_minzoom, maxZoom: tile_maxzoom, attribution: tile_attribution diff --git a/public/templates/index.tmpl b/public/templates/index.tmpl index 250d056..0f0b6f0 100644 --- a/public/templates/index.tmpl +++ b/public/templates/index.tmpl @@ -38,7 +38,7 @@ GL Style {{/if}} {{#if serving_rendered}} - {{#if serving_data}}| {{/if}}TileJSON + {{#if serving_data}}| {{/if}}TileJSON {{/if}} {{#if serving_rendered}} | WMTS diff --git a/public/templates/viewer.tmpl b/public/templates/viewer.tmpl index 5f81eae..66abc24 100644 --- a/public/templates/viewer.tmpl +++ b/public/templates/viewer.tmpl @@ -77,11 +77,11 @@ selectThreshold: 5 })); } else { - var map = L.map('map', { zoomControl: false }); + var map = L.map('map', { minZoom: 1, zoomControl: false }); new L.Control.Zoom({ position: 'topright' }).addTo(map); var tile_urls = [], tile_attribution, tile_minzoom, tile_maxzoom; - var url = '{{public_url}}styles/{{id}}.json' + keyParam; + var url = '{{public_url}}styles/512/{{id}}.json' + keyParam; var req = new XMLHttpRequest(); req.overrideMimeType("application/json"); req.open('GET', url, true); @@ -107,6 +107,7 @@ for (tile_url in tile_urls) { L.tileLayer(tile_urls[tile_url], { + tileSize: 512, minZoom: tile_minzoom, maxZoom: tile_maxzoom, attribution: tile_attribution diff --git a/public/templates/wmts.tmpl b/public/templates/wmts.tmpl index 0db7349..7a2a5dd 100644 --- a/public/templates/wmts.tmpl +++ b/public/templates/wmts.tmpl @@ -36,8 +36,8 @@ - - {{name}} + + {{name}}-256 {{id}} -180 -85.051128779807 @@ -48,13 +48,30 @@ image/png - GoogleMapsCompatible + GoogleMapsCompatible_256 - - - GoogleMapsCompatible - GoogleMapsCompatible EPSG:3857 - GoogleMapsCompatible + + + + {{name}}-512 + {{id}} + + -180 -85.051128779807 + 180 85.051128779807 + + + image/png + + GoogleMapsCompatible_512 + + + + + GoogleMapsCompatible_256 + GoogleMapsCompatible_256 EPSG:3857 + GoogleMapsCompatible_256 urn:ogc:def:crs:EPSG::3857 0 @@ -226,10 +243,189 @@ 256 262144 262144 - - WGS84 - WGS84 EPSG:4326 - WGS84 + + + + GoogleMapsCompatible_512 + GoogleMapsCompatible_512 EPSG:3857 + GoogleMapsCompatible_512 + urn:ogc:def:crs:EPSG::3857 + + 0 + 279541132.0143589 + -20037508.34 20037508.34 + 512 + 512 + 1 + 1 + + + 1 + 139770566.0071794 + -20037508.34 20037508.34 + 512 + 512 + 2 + 2 + + + 2 + 69885283.00358972 + -20037508.34 20037508.34 + 512 + 512 + 4 + 4 + + + 3 + 34942641.501795 + -20037508.34 20037508.34 + 512 + 512 + 8 + 8 + + + 4 + 17471320.750897 + -20037508.34 20037508.34 + 512 + 512 + 16 + 16 + + + 5 + 8735660.3754487 + -20037508.34 20037508.34 + 512 + 512 + 32 + 32 + + + 6 + 4367830.1877244 + -20037508.34 20037508.34 + 512 + 512 + 64 + 64 + + + 7 + 2183915.0938622 + -20037508.34 20037508.34 + 512 + 512 + 128 + 128 + + + 8 + 1091957.5469311 + -20037508.34 20037508.34 + 512 + 512 + 256 + 256 + + + 9 + 545978.77346554 + -20037508.34 20037508.34 + 512 + 512 + 512 + 512 + + + 10 + 272989.38673277 + -20037508.34 20037508.34 + 512 + 512 + 1024 + 1024 + + + 11 + 136494.69336639 + -20037508.34 20037508.34 + 512 + 512 + 2048 + 2048 + + + 12 + 68247.346683193 + -20037508.34 20037508.34 + 512 + 512 + 4096 + 4096 + + + 13 + 34123.673341597 + -20037508.34 20037508.34 + 512 + 512 + 8192 + 8192 + + + 14 + 17061.836670798 + -20037508.34 20037508.34 + 512 + 512 + 16384 + 16384 + + + 15 + 8530.9183353991 + -20037508.34 20037508.34 + 512 + 512 + 32768 + 32768 + + + 16 + 4265.4591676996 + -20037508.34 20037508.34 + 512 + 512 + 65536 + 65536 + + + 17 + 2132.7295838498 + -20037508.34 20037508.34 + 512 + 512 + 131072 + 131072 + + + 18 + 1066.364791924892 + -20037508.34 20037508.34 + 512 + 512 + 262144 + 262144 + + + + WGS84_256 + WGS84_256 EPSG:4326 + WGS84_256 urn:ogc:def:crs:EPSG::4326 0 @@ -401,7 +597,185 @@ 256 524288 262144 - + + + + WGS84_512 + WGS84_512 EPSG:4326 + WGS84_512 + urn:ogc:def:crs:EPSG::4326 + + 0 + 139770566.00718 + 90 -180 + 512 + 512 + 2 + 1 + + + 1 + 69885283.00359 + 90 -180 + 512 + 512 + 4 + 2 + + + 2 + 34942641.501795 + 90 -180 + 512 + 512 + 8 + 4 + + + 3 + 17471320.750897 + 90 -180 + 512 + 512 + 16 + 8 + + + 4 + 8735660.3754487 + 90 -180 + 512 + 512 + 32 + 16 + + + 5 + 4367830.1877244 + 90 -180 + 512 + 512 + 64 + 32 + + + 6 + 2183915.0938622 + 90 -180 + 512 + 512 + 128 + 64 + + + 7 + 1091957.5469311 + 90 -180 + 512 + 512 + 256 + 128 + + + 8 + 545978.77346554 + 90 -180 + 512 + 512 + 512 + 256 + + + 9 + 272989.38673277 + 90 -180 + 512 + 512 + 1024 + 512 + + + 10 + 136494.69336639 + 90 -180 + 512 + 512 + 2048 + 1024 + + + 11 + 68247.346683193 + 90 -180 + 512 + 512 + 4096 + 2048 + + + 12 + 34123.673341597 + 90 -180 + 512 + 512 + 8192 + 4096 + + + 13 + 17061.836670798 + 90 -180 + 512 + 512 + 16384 + 8192 + + + 14 + 8530.9183353991 + 90 -180 + 512 + 512 + 32768 + 16384 + + + 15 + 4265.4591676996 + 90 -180 + 512 + 512 + 65536 + 32768 + + + 16 + 2132.7295838498 + 90 -180 + 512 + 512 + 131072 + 65536 + + + 17 + 1066.3647919249 + 90 -180 + 512 + 512 + 262144 + 131072 + + + 18 + 533.182 + 90 -180 + 512 + 512 + 524288 + 262144 + + diff --git a/src/serve_data.js b/src/serve_data.js index 4a95b1f..099b698 100644 --- a/src/serve_data.js +++ b/src/serve_data.js @@ -173,11 +173,13 @@ export const serve_data = { if (!item) { return res.sendStatus(404); } + const tileSize = undefined; const info = clone(item.tileJSON); info.tiles = getTileUrls( req, info.tiles, `data/${req.params.id}`, + tileSize, info.format, item.publicUrl, { diff --git a/src/serve_rendered.js b/src/serve_rendered.js index ec590b6..0068ac0 100644 --- a/src/serve_rendered.js +++ b/src/serve_rendered.js @@ -532,7 +532,7 @@ export const serve_rendered = { const app = express().disable('x-powered-by'); app.get( - `/:id/:z(\\d+)/:x(\\d+)/:y(\\d+):scale(${scalePattern})?.:format([\\w]+)`, + `/:id/(:tileSize(256|512)/)?:z(\\d+)/:x(\\d+)/:y(\\d+):scale(${scalePattern})?.:format([\\w]+)`, (req, res, next) => { const item = repo[req.params.id]; if (!item) { @@ -552,6 +552,8 @@ export const serve_rendered = { const y = req.params.y | 0; const scale = getScale(req.params.scale); const format = req.params.format; + const tileSize = parseInt(req.params.tileSize, 10) || 256; + if ( z < 0 || x < 0 || @@ -562,11 +564,10 @@ export const serve_rendered = { ) { return res.status(404).send('Out of bounds'); } - const tileSize = 256; const tileCenter = mercator.ll( [ - ((x + 0.5) / (1 << z)) * (256 << z), - ((y + 0.5) / (1 << z)) * (256 << z), + ((x + 0.5) / (1 << z)) * (tileSize << z), + ((y + 0.5) / (1 << z)) * (tileSize << z), ], z, ); @@ -821,16 +822,18 @@ export const serve_rendered = { ); } - app.get('/:id.json', (req, res, next) => { + app.get('/(:tileSize(256|512)/)?:id.json', (req, res, next) => { const item = repo[req.params.id]; if (!item) { return res.sendStatus(404); } + const tileSize = parseInt(req.params.tileSize, 10) || undefined; const info = clone(item.tileJSON); info.tiles = getTileUrls( req, info.tiles, `styles/${req.params.id}`, + tileSize, info.format, item.publicUrl, ); diff --git a/src/server.js b/src/server.js index 2b382a0..5e7019f 100644 --- a/src/server.js +++ b/src/server.js @@ -356,6 +356,7 @@ function start(opts) { const addTileJSONs = (arr, req, type) => { for (const id of Object.keys(serving[type])) { + const tileSize = 256; const info = clone(serving[type][id].tileJSON); let path = ''; if (type === 'rendered') { @@ -367,6 +368,7 @@ function start(opts) { req, info.tiles, path, + tileSize, info.format, opts.publicUrl, { @@ -454,20 +456,19 @@ function start(opts) { if (style.serving_rendered) { const { center } = style.serving_rendered.tileJSON; if (center) { - style.viewer_hash = `#${center[2]}/${center[1].toFixed( - 5, - )}/${center[0].toFixed(5)}`; + style.viewer_hash = `#${center[2]}/${center[1].toFixed(5)}/${center[0].toFixed(5)}`; const centerPx = mercator.px([center[0], center[1]], center[2]); - style.thumbnail = `${center[2]}/${Math.floor( - centerPx[0] / 256, - )}/${Math.floor(centerPx[1] / 256)}.png`; + // Set thumbnail default size to be 256px x 256px + style.thumbnail = `${center[2]}/${Math.floor(centerPx[0] / 256)}/${Math.floor(centerPx[1] / 256)}.png`; } + const tileSize = 512; style.xyz_link = getTileUrls( req, style.serving_rendered.tileJSON.tiles, `styles/${id}`, + tileSize, style.serving_rendered.tileJSON.format, opts.publicUrl, )[0]; @@ -493,22 +494,23 @@ function start(opts) { if (!data.is_vector) { if (center) { const centerPx = mercator.px([center[0], center[1]], center[2]); - data.thumbnail = `${center[2]}/${Math.floor( - centerPx[0] / 256, - )}/${Math.floor(centerPx[1] / 256)}.${tileJSON.format}`; + data.thumbnail = `${center[2]}/${Math.floor(centerPx[0] / 256)}/${Math.floor(centerPx[1] / 256)}.${tileJSON.format}`; } - - data.xyz_link = getTileUrls( - req, - tileJSON.tiles, - `data/${id}`, - tileJSON.format, - opts.publicUrl, - { - pbf: options.pbfAlias, - }, - )[0]; } + + const tileSize = undefined; + data.xyz_link = getTileUrls( + req, + tileJSON.tiles, + `data/${id}`, + tileSize, + tileJSON.format, + opts.publicUrl, + { + pbf: options.pbfAlias, + }, + )[0]; + if (data.filesize) { let suffix = 'kB'; let size = parseInt(tileJSON.filesize, 10) / 1024; diff --git a/src/utils.js b/src/utils.js index 4fdf43a..2a516bc 100644 --- a/src/utils.js +++ b/src/utils.js @@ -26,7 +26,15 @@ export const getPublicUrl = (publicUrl, req) => { return getUrlObject(req).toString(); }; -export const getTileUrls = (req, domains, path, format, publicUrl, aliases) => { +export const getTileUrls = ( + req, + domains, + path, + tileSize, + format, + publicUrl, + aliases, +) => { const urlObject = getUrlObject(req); if (domains) { if (domains.constructor === String && domains.length > 0) { @@ -67,15 +75,20 @@ export const getTileUrls = (req, domains, path, format, publicUrl, aliases) => { format = aliases[format]; } + let tileParams = `{z}/{x}/{y}`; + if (tileSize && ['png', 'jpg', 'jpeg', 'webp'].includes(format)) { + tileParams = `${tileSize}/{z}/{x}/{y}`; + } + const uris = []; if (!publicUrl) { for (const domain of domains) { uris.push( - `${req.protocol}://${domain}/${path}/{z}/{x}/{y}.${format}${query}`, + `${req.protocol}://${domain}/${path}/${tileParams}.${format}${query}`, ); } } else { - uris.push(`${publicUrl}${path}/{z}/{x}/{y}.${format}${query}`); + uris.push(`${publicUrl}${path}/${tileParams}.${format}${query}`); } return uris; diff --git a/test/tiles_rendered.js b/test/tiles_rendered.js index fb2fcc5..6f7f438 100644 --- a/test/tiles_rendered.js +++ b/test/tiles_rendered.js @@ -1,8 +1,30 @@ -const testTile = function (prefix, z, x, y, format, status, scale, type) { +var testTile = function ( + prefix, + tileSize = 256, + z, + x, + y, + format, + status, + scale, + type, +) { if (scale) y += '@' + scale + 'x'; - const path = '/styles/' + prefix + '/' + z + '/' + x + '/' + y + '.' + format; + var path = + '/styles/' + + prefix + + '/' + + tileSize + + '/' + + z + + '/' + + x + + '/' + + y + + '.' + + format; it(path + ' returns ' + status, function (done) { - const test = supertest(app).get(path); + var test = supertest(app).get(path); test.expect(status); if (type) test.expect('Content-Type', type); test.end(done); @@ -14,33 +36,40 @@ const prefix = 'test-style'; describe('Raster tiles', function () { describe('valid requests', function () { describe('various formats', function () { - testTile(prefix, 0, 0, 0, 'png', 200, undefined, /image\/png/); - testTile(prefix, 0, 0, 0, 'jpg', 200, undefined, /image\/jpeg/); - testTile(prefix, 0, 0, 0, 'jpeg', 200, undefined, /image\/jpeg/); - testTile(prefix, 0, 0, 0, 'webp', 200, undefined, /image\/webp/); + testTile(prefix, 256, 0, 0, 0, 'png', 200, undefined, /image\/png/); + testTile(prefix, 512, 0, 0, 0, 'png', 200, undefined, /image\/png/); + testTile(prefix, 256, 0, 0, 0, 'jpg', 200, undefined, /image\/jpeg/); + testTile(prefix, 512, 0, 0, 0, 'jpg', 200, undefined, /image\/jpeg/); + testTile(prefix, 256, 0, 0, 0, 'jpeg', 200, undefined, /image\/jpeg/); + testTile(prefix, 512, 0, 0, 0, 'jpeg', 200, undefined, /image\/jpeg/); + testTile(prefix, 256, 0, 0, 0, 'webp', 200, undefined, /image\/webp/); + testTile(prefix, 512, 0, 0, 0, 'webp', 200, undefined, /image\/webp/); }); describe('different coordinates and scales', function () { - testTile(prefix, 1, 1, 1, 'png', 200); - - testTile(prefix, 0, 0, 0, 'png', 200, 2); - testTile(prefix, 0, 0, 0, 'png', 200, 3); - testTile(prefix, 2, 1, 1, 'png', 200, 3); + testTile(prefix, 256, 1, 0, 0, 'png', 200); + testTile(prefix, 512, 1, 0, 0, 'png', 200); + testTile(prefix, 256, 0, 0, 0, 'png', 200, 2); + testTile(prefix, 512, 0, 0, 0, 'png', 200, 2); + testTile(prefix, 256, 0, 0, 0, 'png', 200, 3); + testTile(prefix, 512, 0, 0, 0, 'png', 200, 3); + testTile(prefix, 256, 2, 1, 1, 'png', 200, 3); + testTile(prefix, 512, 2, 1, 1, 'png', 200, 3); }); }); describe('invalid requests return 4xx', function () { - testTile('non_existent', 0, 0, 0, 'png', 404); - testTile(prefix, -1, 0, 0, 'png', 404); - testTile(prefix, 25, 0, 0, 'png', 404); - testTile(prefix, 0, 1, 0, 'png', 404); - testTile(prefix, 0, 0, 1, 'png', 404); - testTile(prefix, 0, 0, 0, 'gif', 400); - testTile(prefix, 0, 0, 0, 'pbf', 400); + testTile('non_existent', 256, 0, 0, 0, 'png', 404); + testTile(prefix, 256, -1, 0, 0, 'png', 404); + testTile(prefix, 256, 25, 0, 0, 'png', 404); + testTile(prefix, 256, 0, 1, 0, 'png', 404); + testTile(prefix, 256, 0, 0, 1, 'png', 404); + testTile(prefix, 256, 0, 0, 0, 'gif', 400); + testTile(prefix, 256, 0, 0, 0, 'pbf', 400); - testTile(prefix, 0, 0, 0, 'png', 404, 1); - testTile(prefix, 0, 0, 0, 'png', 404, 5); + testTile(prefix, 256, 0, 0, 0, 'png', 404, 1); + testTile(prefix, 256, 0, 0, 0, 'png', 404, 5); - // testTile('hybrid', 0, 0, 0, 'png', 404); //TODO: test this + testTile(prefix, 300, 0, 0, 0, 'png', 404); }); });