Merge branch 'master' into terrain-elevation-contour
This commit is contained in:
commit
f15d1df514
13 changed files with 137 additions and 92 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
4
package-lock.json
generated
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -9,8 +9,10 @@
|
||||||
<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>
|
||||||
<script src="{{public_url}}elevation-control.js{{&key_query}}"></script>
|
|
||||||
<script src="{{public_url}}contour-control.js{{&key_query}}"></script>
|
<script src="{{public_url}}contour-control.js{{&key_query}}"></script>
|
||||||
|
{{^is_light}}
|
||||||
|
<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}}
|
||||||
|
@ -22,9 +24,10 @@
|
||||||
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;}
|
||||||
.maplibre-ctrl-elevation { padding-left: 5px; padding-right: 5px; }
|
|
||||||
.maplibre-ctrl-contour-active button { color: #33b5e5; font-weight: bold; }
|
.maplibre-ctrl-contour-active button { color: #33b5e5; font-weight: bold; }
|
||||||
|
{{^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}}
|
||||||
|
@ -180,11 +183,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}}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 { LocalDemManager } from './contour.js';
|
import { LocalDemManager } from './contour.js';
|
||||||
import {
|
import {
|
||||||
|
@ -22,6 +20,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.
|
||||||
|
@ -357,79 +369,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)
|
||||||
|
|
|
@ -6,4 +6,8 @@ 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) => {},
|
||||||
|
getTerrainElevation: (data, param) => {
|
||||||
|
param['elevation'] = 'not supported in light';
|
||||||
|
return param;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
||||||
|
@ -1458,4 +1458,76 @@ export const serve_rendered = {
|
||||||
}
|
}
|
||||||
delete repo[id];
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -772,8 +775,8 @@ 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);
|
||||||
running.server = restarted.server;
|
running.server = restarted.server;
|
||||||
running.app = restarted.app;
|
running.app = restarted.app;
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue