Skip to content

Commit

Permalink
Fix broken light (invalid use of heavy dependencies) (#1449)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
okimiko and Miko authored Jan 17, 2025
1 parent b0a2cef commit f02c63c
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 86 deletions.
2 changes: 2 additions & 0 deletions docs/endpoints.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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}``
Expand Down
6 changes: 6 additions & 0 deletions public/templates/data.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
Expand All @@ -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}}
Expand Down Expand Up @@ -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}}

Expand Down
4 changes: 2 additions & 2 deletions public/templates/index.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
103 changes: 28 additions & 75 deletions src/serve_data.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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.
Expand Down Expand Up @@ -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({
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;
}
});
var param = {
long: bbox[0],
lat: bbox[1],
encoding,
format,
tile_size: TILE_SIZE,
z: zoom,
x: xy[0],
y: xy[1],
};

res
.status(200)
.send(await serve_rendered.getTerrainElevation(data, param));
} catch (err) {
return res
.status(500)
Expand Down
4 changes: 4 additions & 0 deletions src/serve_light.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
},
};
74 changes: 73 additions & 1 deletion src/serve_rendered.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
});
},
};
19 changes: 11 additions & 8 deletions src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -575,11 +574,14 @@ async function start(opts) {
tileJSON.encoding === 'terrarium' ||
tileJSON.encoding === 'mapbox'
) {
data.elevation_link = getTileUrls(
req,
tileJSON.tiles,
`data/${id}/elevation`,
)[0];
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]);
Expand Down Expand Up @@ -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,
};
});

Expand Down

0 comments on commit f02c63c

Please sign in to comment.