Merge branch 'master' into some-improvements

This commit is contained in:
Andrew Calcutt 2025-02-08 21:40:23 -05:00 committed by GitHub
commit 2ef7cd5294
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 161 additions and 91 deletions

View file

@ -1,5 +1,11 @@
# tileserver-gl changelog # tileserver-gl changelog
## 5.1.3
* Fix SIGHUP (broken since 5.1.x) (https://github.com/maptiler/tileserver-gl/pull/1452) by @okimiko
## 5.1.2
* Fix broken light (invalid use of heavy dependencies) (https://github.com/maptiler/tileserver-gl/pull/1449) by @okimiko
## 5.1.1 ## 5.1.1
* Fix wrong node version in Docker image (https://github.com/maptiler/tileserver-gl/pull/1442) by @acalcutt * Fix wrong node version in Docker image (https://github.com/maptiler/tileserver-gl/pull/1442) by @acalcutt

View file

@ -108,6 +108,8 @@ Source data
* the result will be a json object like ``{"z":7,"x":68,"y":45,"red":134,"green":66,"blue":0,"latitude":11.84069,"longitude":46.04798,"elevation":1602}`` * the result will be a json object like ``{"z":7,"x":68,"y":45,"red":134,"green":66,"blue":0,"latitude":11.84069,"longitude":46.04798,"elevation":1602}``
* The elevation api is not available in the ``tileserver-gl-light`` version.
Static files Static files
=========== ===========
* Static files are served at ``/files/{filename}`` * Static files are served at ``/files/{filename}``

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{ {
"name": "tileserver-gl", "name": "tileserver-gl",
"version": "5.1.1", "version": "5.1.3",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "tileserver-gl", "name": "tileserver-gl",
"version": "5.1.1", "version": "5.1.3",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"@jsse/pbfont": "^0.2.2", "@jsse/pbfont": "^0.2.2",

View file

@ -1,6 +1,6 @@
{ {
"name": "tileserver-gl", "name": "tileserver-gl",
"version": "5.1.1", "version": "5.1.3",
"description": "Map tile server for JSON GL styles - vector and server side generated raster tiles", "description": "Map tile server for JSON GL styles - vector and server side generated raster tiles",
"main": "src/main.js", "main": "src/main.js",
"bin": "src/main.js", "bin": "src/main.js",

View file

@ -9,7 +9,9 @@
<link rel="stylesheet" type="text/css" href="{{public_url}}maplibre-gl-inspect.css{{&key_query}}" /> <link rel="stylesheet" type="text/css" href="{{public_url}}maplibre-gl-inspect.css{{&key_query}}" />
<script src="{{public_url}}maplibre-gl.js{{&key_query}}"></script> <script src="{{public_url}}maplibre-gl.js{{&key_query}}"></script>
<script src="{{public_url}}maplibre-gl-inspect.js{{&key_query}}"></script> <script src="{{public_url}}maplibre-gl-inspect.js{{&key_query}}"></script>
{{^is_light}}
<script src="{{public_url}}elevation-control.js{{&key_query}}"></script> <script src="{{public_url}}elevation-control.js{{&key_query}}"></script>
{{/is_light}}
<style> <style>
body {background:#fff;color:#333;font-family:Arial, sans-serif;} body {background:#fff;color:#333;font-family:Arial, sans-serif;}
{{^is_terrain}} {{^is_terrain}}
@ -21,7 +23,9 @@
h1 {position:absolute;top:5px;right:0;width:240px;margin:0;line-height:20px;font-size:20px;} h1 {position:absolute;top:5px;right:0;width:240px;margin:0;line-height:20px;font-size:20px;}
#layerList {position:absolute;top:35px;right:0;bottom:0;width:240px;overflow:auto;} #layerList {position:absolute;top:35px;right:0;bottom:0;width:240px;overflow:auto;}
#layerList div div {width:15px;height:15px;display:inline-block;} #layerList div div {width:15px;height:15px;display:inline-block;}
{{^is_light}}
.maplibre-ctrl-elevation { padding-left: 5px; padding-right: 5px; } .maplibre-ctrl-elevation { padding-left: 5px; padding-right: 5px; }
{{/is_light}}
</style> </style>
{{/use_maplibre}} {{/use_maplibre}}
{{^use_maplibre}} {{^use_maplibre}}
@ -135,11 +139,13 @@
}) })
); );
{{^is_light}}
map.addControl( map.addControl(
new ElevationInfoControl({ new ElevationInfoControl({
url: "{{public_url}}data/{{id}}/elevation/{z}/{x}/{y}" url: "{{public_url}}data/{{id}}/elevation/{z}/{x}/{y}"
}) })
); );
{{/is_light}}
{{/is_terrain}} {{/is_terrain}}
{{^is_terrain}} {{^is_terrain}}

View file

@ -124,9 +124,9 @@
{{/is_vector}} {{/is_vector}}
{{^is_vector}} {{^is_vector}}
<a class="btn" href="{{public_url}}data/{{@key}}/{{&../key_query}}{{viewer_hash}}">View</a> <a class="btn" href="{{public_url}}data/{{@key}}/{{&../key_query}}{{viewer_hash}}">View</a>
{{#elevation_link}} {{#is_terrain}}
<a class="btn" href="{{public_url}}data/preview/{{@key}}/{{&../key_query}}{{viewer_hash}}">Preview Terrain</a> <a class="btn" href="{{public_url}}data/preview/{{@key}}/{{&../key_query}}{{viewer_hash}}">Preview Terrain</a>
{{/elevation_link}} {{/is_terrain}}
{{/is_vector}} {{/is_vector}}
</div> </div>
</div> </div>

View file

@ -8,8 +8,6 @@ import express from 'express';
import Pbf from 'pbf'; import Pbf from 'pbf';
import { VectorTile } from '@mapbox/vector-tile'; import { VectorTile } from '@mapbox/vector-tile';
import SphericalMercator from '@mapbox/sphericalmercator'; import SphericalMercator from '@mapbox/sphericalmercator';
import { Image, createCanvas } from 'canvas';
import sharp from 'sharp';
import { import {
fixTileJSONCenter, fixTileJSONCenter,
@ -21,6 +19,20 @@ import { getPMtilesInfo, openPMtiles } from './pmtiles_adapter.js';
import { gunzipP, gzipP } from './promises.js'; import { gunzipP, gzipP } from './promises.js';
import { openMbTilesWrapper } from './mbtiles_wrapper.js'; import { openMbTilesWrapper } from './mbtiles_wrapper.js';
import fs from 'node:fs';
import { fileURLToPath } from 'url';
const packageJson = JSON.parse(
fs.readFileSync(
path.dirname(fileURLToPath(import.meta.url)) + '/../package.json',
'utf8',
),
);
const isLight = packageJson.name.slice(-6) === '-light';
const serve_rendered = (
await import(`${!isLight ? `./serve_rendered.js` : `./serve_light.js`}`)
).serve_rendered;
export const serve_data = { export const serve_data = {
/** /**
* Initializes the serve_data module. * Initializes the serve_data module.
@ -246,79 +258,20 @@ export const serve_data = {
if (fetchTile == null) return res.status(204).send(); if (fetchTile == null) return res.status(204).send();
let data = fetchTile.data; let data = fetchTile.data;
const image = new Image(); var param = {
await new Promise(async (resolve, reject) => { long: bbox[0],
image.onload = async () => { lat: bbox[1],
const canvas = createCanvas(TILE_SIZE, TILE_SIZE); encoding,
const context = canvas.getContext('2d'); format,
context.drawImage(image, 0, 0); tile_size: TILE_SIZE,
const long = bbox[0]; z: zoom,
const lat = bbox[1]; x: xy[0],
y: xy[1],
};
// calculate pixel coordinate of tile, res
// see https://developers.google.com/maps/documentation/javascript/examples/map-coordinates .status(200)
let siny = Math.sin((lat * Math.PI) / 180); .send(await serve_rendered.getTerrainElevation(data, param));
// Truncating to 0.9999 effectively limits latitude to 89.189. This is
// about a third of a tile past the edge of the world tile.
siny = Math.min(Math.max(siny, -0.9999), 0.9999);
const xWorld = TILE_SIZE * (0.5 + long / 360);
const yWorld =
TILE_SIZE *
(0.5 - Math.log((1 + siny) / (1 - siny)) / (4 * Math.PI));
const scale = 1 << zoom;
const xTile = Math.floor((xWorld * scale) / TILE_SIZE);
const yTile = Math.floor((yWorld * scale) / TILE_SIZE);
const xPixel = Math.floor(xWorld * scale) - xTile * TILE_SIZE;
const yPixel = Math.floor(yWorld * scale) - yTile * TILE_SIZE;
if (
xPixel < 0 ||
yPixel < 0 ||
xPixel >= TILE_SIZE ||
yPixel >= TILE_SIZE
) {
return reject('Out of bounds Pixel');
}
const imgdata = context.getImageData(xPixel, yPixel, 1, 1);
const red = imgdata.data[0];
const green = imgdata.data[1];
const blue = imgdata.data[2];
let elevation;
if (encoding === 'mapbox') {
elevation = -10000 + (red * 256 * 256 + green * 256 + blue) * 0.1;
} else if (encoding === 'terrarium') {
elevation = red * 256 + green + blue / 256 - 32768;
} else {
elevation = 'invalid encoding';
}
resolve(
res.status(200).send({
z: zoom,
x: xy[0],
y: xy[1],
red,
green,
blue,
latitude: lat,
longitude: long,
elevation,
}),
);
};
image.onerror = (err) => reject(err);
if (format === 'webp') {
try {
const img = await sharp(data).toFormat('png').toBuffer();
image.src = img;
} catch (err) {
reject(err);
}
} else {
image.src = data;
}
});
} catch (err) { } catch (err) {
return res return res
.status(500) .status(500)

View file

@ -6,4 +6,9 @@ export const serve_rendered = {
init: (options, repo, programOpts) => {}, init: (options, repo, programOpts) => {},
add: (options, repo, params, id, programOpts, dataResolver) => {}, add: (options, repo, params, id, programOpts, dataResolver) => {},
remove: (repo, id) => {}, remove: (repo, id) => {},
clear: (repo) => {},
getTerrainElevation: (data, param) => {
param['elevation'] = 'not supported in light';
return param;
},
}; };

View file

@ -7,7 +7,7 @@
// This happens on ARM: // This happens on ARM:
// > terminate called after throwing an instance of 'std::runtime_error' // > terminate called after throwing an instance of 'std::runtime_error'
// > what(): Cannot read GLX extensions. // > what(): Cannot read GLX extensions.
import 'canvas'; import { Image, createCanvas } from 'canvas';
import '@maplibre/maplibre-gl-native'; import '@maplibre/maplibre-gl-native';
// //
// SECTION END // SECTION END
@ -15,7 +15,6 @@ import '@maplibre/maplibre-gl-native';
import advancedPool from 'advanced-pool'; import advancedPool from 'advanced-pool';
import path from 'path'; import path from 'path';
import url from 'url'; import url from 'url';
import util from 'util';
import sharp from 'sharp'; import sharp from 'sharp';
import clone from 'clone'; import clone from 'clone';
import Color from 'color'; import Color from 'color';
@ -1458,4 +1457,94 @@ export const serve_rendered = {
} }
delete repo[id]; delete repo[id];
}, },
/**
* Removes all items from the repository.
* @param {object} repo Repository object.
* @returns {void}
*/
clear: function (repo) {
Object.keys(repo).forEach((id) => {
const item = repo[id];
if (item) {
item.map.renderers.forEach((pool) => {
pool.close();
});
item.map.renderersStatic.forEach((pool) => {
pool.close();
});
}
delete repo[id];
});
},
/**
* Get the elevation of terrain tile data by rendering it to a canvas image
* @param {object} data The background color (or empty string for transparent).
* @param {object} param Required parameters (coordinates e.g.)
* @returns {object}
*/
getTerrainElevation: async function (data, param) {
return await new Promise(async (resolve, reject) => {
const image = new Image();
image.onload = async () => {
const canvas = createCanvas(param['tile_size'], param['tile_size']);
const context = canvas.getContext('2d');
context.drawImage(image, 0, 0);
// calculate pixel coordinate of tile,
// see https://developers.google.com/maps/documentation/javascript/examples/map-coordinates
let siny = Math.sin((param['lat'] * Math.PI) / 180);
// Truncating to 0.9999 effectively limits latitude to 89.189. This is
// about a third of a tile past the edge of the world tile.
siny = Math.min(Math.max(siny, -0.9999), 0.9999);
const xWorld = param['tile_size'] * (0.5 + param['long'] / 360);
const yWorld =
param['tile_size'] *
(0.5 - Math.log((1 + siny) / (1 - siny)) / (4 * Math.PI));
const scale = 1 << param['z'];
const xTile = Math.floor((xWorld * scale) / param['tile_size']);
const yTile = Math.floor((yWorld * scale) / param['tile_size']);
const xPixel = Math.floor(xWorld * scale) - xTile * param['tile_size'];
const yPixel = Math.floor(yWorld * scale) - yTile * param['tile_size'];
if (
xPixel < 0 ||
yPixel < 0 ||
xPixel >= param['tile_size'] ||
yPixel >= param['tile_size']
) {
return reject('Out of bounds Pixel');
}
const imgdata = context.getImageData(xPixel, yPixel, 1, 1);
const red = imgdata.data[0];
const green = imgdata.data[1];
const blue = imgdata.data[2];
let elevation;
if (param['encoding'] === 'mapbox') {
elevation = -10000 + (red * 256 * 256 + green * 256 + blue) * 0.1;
} else if (param['encoding'] === 'terrarium') {
elevation = red * 256 + green + blue / 256 - 32768;
} else {
elevation = 'invalid encoding';
}
param['elevation'] = elevation;
param['red'] = red;
param['green'] = green;
param['blue'] = blue;
resolve(param);
};
image.onerror = (err) => reject(err);
if (param['format'] === 'webp') {
try {
const img = await sharp(data).toFormat('png').toBuffer();
image.src = img;
} catch (err) {
reject(err);
}
} else {
image.src = data;
}
});
},
}; };

View file

@ -24,13 +24,12 @@ import {
} from './utils.js'; } from './utils.js';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(fileURLToPath(import.meta.url));
const __dirname = path.dirname(__filename);
const packageJson = JSON.parse( const packageJson = JSON.parse(
fs.readFileSync(__dirname + '/../package.json', 'utf8'), fs.readFileSync(__dirname + '/../package.json', 'utf8'),
); );
const isLight = packageJson.name.slice(-6) === '-light'; const isLight = packageJson.name.slice(-6) === '-light';
const serve_rendered = ( const serve_rendered = (
await import(`${!isLight ? `./serve_rendered.js` : `./serve_light.js`}`) await import(`${!isLight ? `./serve_rendered.js` : `./serve_light.js`}`)
).serve_rendered; ).serve_rendered;
@ -575,11 +574,14 @@ async function start(opts) {
tileJSON.encoding === 'terrarium' || tileJSON.encoding === 'terrarium' ||
tileJSON.encoding === 'mapbox' tileJSON.encoding === 'mapbox'
) { ) {
data.elevation_link = getTileUrls( if (!isLight) {
req, data.elevation_link = getTileUrls(
tileJSON.tiles, req,
`data/${id}/elevation`, tileJSON.tiles,
)[0]; `data/${id}/elevation`,
)[0];
}
data.is_terrain = true;
} }
if (center) { if (center) {
const centerPx = mercator.px([center[0], center[1]], center[2]); const centerPx = mercator.px([center[0], center[1]], center[2]);
@ -698,6 +700,7 @@ async function start(opts) {
is_terrain: is_terrain, is_terrain: is_terrain,
is_terrainrgb: data.tileJSON.encoding === 'mapbox', is_terrainrgb: data.tileJSON.encoding === 'mapbox',
terrain_encoding: data.tileJSON.encoding, terrain_encoding: data.tileJSON.encoding,
is_light: isLight,
}; };
}); });
@ -740,6 +743,7 @@ async function start(opts) {
app, app,
server, server,
startupPromise, startupPromise,
serving,
}; };
} }
/** /**
@ -772,10 +776,15 @@ export async function server(opts) {
console.log(`Caught signal ${signal}, refreshing`); console.log(`Caught signal ${signal}, refreshing`);
console.log('Stopping server and reloading config'); console.log('Stopping server and reloading config');
running.server.shutdown(() => { running.server.shutdown(async () => {
const restarted = start(opts); const restarted = await start(opts);
if (!isLight) {
serve_rendered.clear(running.serving.rendered);
}
running.server = restarted.server; running.server = restarted.server;
running.app = restarted.app; running.app = restarted.app;
running.startupPromise = restarted.startupPromise;
running.serving = restarted.serving;
}); });
}); });
return running; return running;