Fix broken light (invalid use of heavy dependencies) (#1449)
* fix broken light (invalid use of heavy dependencies) - move elevation calculation to serve_rendered and stub in serve_light due to use of canvas and sharp - elevation api is not available for light * add elevatppy lint:js:fix * remove not working isLight function * apply lint:js:fix * add isLight to data.tmpl * add note of not available elevation api in light version * hide elevation link in light version * cleanup terrain and elevation template flags --------- Co-authored-by: Miko <miko@none>
This commit is contained in:
parent
b0a2cefb0e
commit
f02c63c94c
7 changed files with 125 additions and 85 deletions
|
@ -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 elevation api is not available in the ``tileserver-gl-light`` version.
|
||||
|
||||
Static files
|
||||
===========
|
||||
* Static files are served at ``/files/{filename}``
|
||||
|
|
|
@ -9,7 +9,9 @@
|
|||
<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-inspect.js{{&key_query}}"></script>
|
||||
{{^is_light}}
|
||||
<script src="{{public_url}}elevation-control.js{{&key_query}}"></script>
|
||||
{{/is_light}}
|
||||
<style>
|
||||
body {background:#fff;color:#333;font-family:Arial, sans-serif;}
|
||||
{{^is_terrain}}
|
||||
|
@ -21,7 +23,9 @@
|
|||
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 div div {width:15px;height:15px;display:inline-block;}
|
||||
{{^is_light}}
|
||||
.maplibre-ctrl-elevation { padding-left: 5px; padding-right: 5px; }
|
||||
{{/is_light}}
|
||||
</style>
|
||||
{{/use_maplibre}}
|
||||
{{^use_maplibre}}
|
||||
|
@ -135,11 +139,13 @@
|
|||
})
|
||||
);
|
||||
|
||||
{{^is_light}}
|
||||
map.addControl(
|
||||
new ElevationInfoControl({
|
||||
url: "{{public_url}}data/{{id}}/elevation/{z}/{x}/{y}"
|
||||
})
|
||||
);
|
||||
{{/is_light}}
|
||||
{{/is_terrain}}
|
||||
{{^is_terrain}}
|
||||
|
||||
|
|
|
@ -124,9 +124,9 @@
|
|||
{{/is_vector}}
|
||||
{{^is_vector}}
|
||||
<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>
|
||||
{{/elevation_link}}
|
||||
{{/is_terrain}}
|
||||
{{/is_vector}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -8,8 +8,6 @@ import express from 'express';
|
|||
import Pbf from 'pbf';
|
||||
import { VectorTile } from '@mapbox/vector-tile';
|
||||
import SphericalMercator from '@mapbox/sphericalmercator';
|
||||
import { Image, createCanvas } from 'canvas';
|
||||
import sharp from 'sharp';
|
||||
|
||||
import {
|
||||
fixTileJSONCenter,
|
||||
|
@ -21,6 +19,20 @@ import { getPMtilesInfo, openPMtiles } from './pmtiles_adapter.js';
|
|||
import { gunzipP, gzipP } from './promises.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 = {
|
||||
/**
|
||||
* Initializes the serve_data module.
|
||||
|
@ -246,79 +258,20 @@ export const serve_data = {
|
|||
if (fetchTile == null) return res.status(204).send();
|
||||
|
||||
let data = fetchTile.data;
|
||||
const image = new Image();
|
||||
await new Promise(async (resolve, reject) => {
|
||||
image.onload = async () => {
|
||||
const canvas = createCanvas(TILE_SIZE, TILE_SIZE);
|
||||
const context = canvas.getContext('2d');
|
||||
context.drawImage(image, 0, 0);
|
||||
const long = bbox[0];
|
||||
const lat = bbox[1];
|
||||
|
||||
// calculate pixel coordinate of tile,
|
||||
// see https://developers.google.com/maps/documentation/javascript/examples/map-coordinates
|
||||
let siny = Math.sin((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 = 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({
|
||||
var param = {
|
||||
long: bbox[0],
|
||||
lat: bbox[1],
|
||||
encoding,
|
||||
format,
|
||||
tile_size: TILE_SIZE,
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
res
|
||||
.status(200)
|
||||
.send(await serve_rendered.getTerrainElevation(data, param));
|
||||
} catch (err) {
|
||||
return res
|
||||
.status(500)
|
||||
|
|
|
@ -6,4 +6,8 @@ export const serve_rendered = {
|
|||
init: (options, repo, programOpts) => {},
|
||||
add: (options, repo, params, id, programOpts, dataResolver) => {},
|
||||
remove: (repo, id) => {},
|
||||
getTerrainElevation: (data, param) => {
|
||||
param['elevation'] = 'not supported in light';
|
||||
return param;
|
||||
},
|
||||
};
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
// This happens on ARM:
|
||||
// > terminate called after throwing an instance of 'std::runtime_error'
|
||||
// > what(): Cannot read GLX extensions.
|
||||
import 'canvas';
|
||||
import { Image, createCanvas } from 'canvas';
|
||||
import '@maplibre/maplibre-gl-native';
|
||||
//
|
||||
// SECTION END
|
||||
|
@ -1458,4 +1458,76 @@ export const serve_rendered = {
|
|||
}
|
||||
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';
|
||||
|
||||
import { fileURLToPath } from 'url';
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const packageJson = JSON.parse(
|
||||
fs.readFileSync(__dirname + '/../package.json', 'utf8'),
|
||||
);
|
||||
|
||||
const isLight = packageJson.name.slice(-6) === '-light';
|
||||
|
||||
const serve_rendered = (
|
||||
await import(`${!isLight ? `./serve_rendered.js` : `./serve_light.js`}`)
|
||||
).serve_rendered;
|
||||
|
@ -575,12 +574,15 @@ async function start(opts) {
|
|||
tileJSON.encoding === 'terrarium' ||
|
||||
tileJSON.encoding === 'mapbox'
|
||||
) {
|
||||
if (!isLight) {
|
||||
data.elevation_link = getTileUrls(
|
||||
req,
|
||||
tileJSON.tiles,
|
||||
`data/${id}/elevation`,
|
||||
)[0];
|
||||
}
|
||||
data.is_terrain = true;
|
||||
}
|
||||
if (center) {
|
||||
const centerPx = mercator.px([center[0], center[1]], center[2]);
|
||||
data.thumbnail = `${Math.floor(center[2])}/${Math.floor(
|
||||
|
@ -698,6 +700,7 @@ async function start(opts) {
|
|||
is_terrain: is_terrain,
|
||||
is_terrainrgb: data.tileJSON.encoding === 'mapbox',
|
||||
terrain_encoding: data.tileJSON.encoding,
|
||||
is_light: isLight,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue