diff --git a/docs/endpoints.rst b/docs/endpoints.rst
index 12f1acf5e..435144acb 100644
--- a/docs/endpoints.rst
+++ b/docs/endpoints.rst
@@ -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}``
diff --git a/public/templates/data.tmpl b/public/templates/data.tmpl
index 70d3a2272..12a8d9850 100644
--- a/public/templates/data.tmpl
+++ b/public/templates/data.tmpl
@@ -9,7 +9,9 @@
+ {{^is_light}}
+ {{/is_light}}
{{/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}}
diff --git a/public/templates/index.tmpl b/public/templates/index.tmpl
index acf094f77..1d3a514ec 100644
--- a/public/templates/index.tmpl
+++ b/public/templates/index.tmpl
@@ -124,9 +124,9 @@
{{/is_vector}}
{{^is_vector}}
View
- {{#elevation_link}}
+ {{#is_terrain}}
Preview Terrain
- {{/elevation_link}}
+ {{/is_terrain}}
{{/is_vector}}
diff --git a/src/serve_data.js b/src/serve_data.js
index cd2e6bbf7..6369aa21d 100644
--- a/src/serve_data.js
+++ b/src/serve_data.js
@@ -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({
- 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)
diff --git a/src/serve_light.js b/src/serve_light.js
index 7e49c4929..13aa84c73 100644
--- a/src/serve_light.js
+++ b/src/serve_light.js
@@ -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;
+ },
};
diff --git a/src/serve_rendered.js b/src/serve_rendered.js
index af928d993..3dc3cd546 100644
--- a/src/serve_rendered.js
+++ b/src/serve_rendered.js
@@ -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;
+ }
+ });
+ },
};
diff --git a/src/server.js b/src/server.js
index 682e07e9e..468b71337 100644
--- a/src/server.js
+++ b/src/server.js
@@ -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,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]);
@@ -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,
};
});