test 1 - not working

This commit is contained in:
acalcutt 2024-12-21 14:37:17 -05:00 committed by Miko
parent 1f1fe49ec9
commit a10555e2ee
4 changed files with 214 additions and 0 deletions

6
package-lock.json generated
View file

@ -28,6 +28,7 @@
"express": "5.0.1", "express": "5.0.1",
"handlebars": "4.7.8", "handlebars": "4.7.8",
"http-shutdown": "1.2.2", "http-shutdown": "1.2.2",
"maplibre-contour": "^0.1.0",
"morgan": "1.10.0", "morgan": "1.10.0",
"pbf": "4.0.1", "pbf": "4.0.1",
"pmtiles": "3.0.7", "pmtiles": "3.0.7",
@ -5550,6 +5551,11 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/maplibre-contour": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/maplibre-contour/-/maplibre-contour-0.1.0.tgz",
"integrity": "sha512-H8muT7JWYE4oLbFv7L2RSbIM1NOu5JxjA9P/TQqhODDnRChE8ENoDkQIWOKgfcKNU77ypLk2ggGoh4/pt4UPLA=="
},
"node_modules/media-typer": { "node_modules/media-typer": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",

View file

@ -37,6 +37,7 @@
"express": "5.0.1", "express": "5.0.1",
"handlebars": "4.7.8", "handlebars": "4.7.8",
"http-shutdown": "1.2.2", "http-shutdown": "1.2.2",
"maplibre-contour": "^0.1.0",
"morgan": "1.10.0", "morgan": "1.10.0",
"pbf": "4.0.1", "pbf": "4.0.1",
"pmtiles": "3.0.7", "pmtiles": "3.0.7",

205
src/contour.js Normal file
View file

@ -0,0 +1,205 @@
import sharp from 'sharp';
import mlcontour from '../node_modules/maplibre-contour/dist/index.mjs';
import { getPMtilesTile } from './pmtiles_adapter.js';
/**
* Manages local DEM (Digital Elevation Model) data using maplibre-contour.
*/
export class LocalDemManager {
/**
* Creates a new LocalDemManager instance.
* @param {string} encoding - The encoding type for the DEM data.
* @param {number} maxzoom - The maximum zoom level for the DEM data.
* @param {object} source - The source object that contains either pmtiles or mbtiles.
* @param {'pmtiles' | 'mbtiles'} sourceType - The type of data source
* @param {Function} [extractZXYFromUrlTrimFunction] - The function to extract the zxy from the url.
* @param {Function} [GetTileFunction] - the function that returns a tile from the pmtiles object.
*/
constructor(
encoding,
maxzoom,
source,
sourceType,
GetTileFunction,
extractZXYFromUrlTrimFunction,
) {
this.encoding = encoding;
this.maxzoom = maxzoom;
this.source = source;
this.sourceType = sourceType;
this.getTile = GetTileFunction || this.GetTile.bind(this);
this.extractZXYFromUrlTrim =
extractZXYFromUrlTrimFunction || this.extractZXYFromUrlTrim.bind(this);
this.manager = new mlcontour.LocalDemManager({
demUrlPattern: '/{z}/{x}/{y}',
cacheSize: 100,
encoding: this.encoding,
maxzoom: this.maxzoom,
timeoutMs: 10000,
decodeImage: this.getImageData.bind(this),
getTile: this.getTileFunction.bind(this),
});
}
/**
* Processes image data from a blob.
* @param {Blob} blob - The image data as a Blob.
* @param {AbortController} abortController - An AbortController to cancel the image processing.
* @returns {Promise<any>} - A Promise that resolves with the processed image data, or null if aborted.
* @throws {Error} If an error occurs during image processing.
*/
async getImageData(blob, abortController) {
try {
if (Boolean(abortController?.signal?.aborted)) return null; // Check for abort signal.
const buffer = await blob.arrayBuffer();
const image = sharp(Buffer.from(buffer));
const metadata = await image.metadata();
if (Boolean(abortController?.signal?.aborted)) return null; // Check for abort signal.
const { data, info } = await image
.raw()
.toBuffer({ resolveWithObject: true });
if (Boolean(abortController?.signal?.aborted)) return null; // Check for abort signal.
const parsed = mlcontour.decodeParsedImage(
info.width,
info.height,
this.encoding,
data,
);
if (Boolean(abortController?.signal?.aborted)) return null; // Check for abort signal.
return parsed;
} catch (error) {
console.error('Error processing image:', error);
throw error; // Rethrow to handle upstream
// return null; // Or handle error gracefully
}
}
/**
* Fetches a tile using the provided url and abortController
* @param {string} url - The url that should be used to fetch the tile.
* @param {AbortController} abortController - An AbortController to cancel the request.
* @returns {Promise<{data: Blob, expires: undefined, cacheControl: undefined}>} A promise that resolves with the response data.
* @throws {Error} If an error occurs fetching or processing the tile.
*/
async GetTile(url, abortController) {
console.log(url);
const $zxy = this.extractZXYFromUrlTrim(url);
if (!$zxy) {
throw new Error(`Could not extract zxy from $`);
}
if (abortController.signal.aborted) {
return null; // Or throw an error
}
try {
let data;
if (this.sourceType === 'pmtiles') {
let zxyTile;
if (this.getPMtilesTile) {
zxyTile = await getPMtilesTile(
this.source,
$zxy.z,
$zxy.x,
$zxy.y,
abortController,
);
} else {
if (abortController.signal.aborted) {
console.log('pmtiles aborted in default');
return null;
}
zxyTile = {
data: new Uint8Array([$zxy.z, $zxy.x, $zxy.y]),
};
}
if (!zxyTile || !zxyTile.data) {
throw new Error(`No tile returned for $`);
}
data = zxyTile.data;
} else {
data = await new Promise((resolve, reject) => {
this.source.getTile($zxy.z, $zxy.x, $zxy.y, (err, tileData) => {
if (err) {
return /does not exist/.test(err.message)
? resolve(null)
: reject(err);
}
resolve(tileData);
});
});
}
if (data == null) {
return null;
}
if (!data) {
throw new Error(`No tile returned for $`);
}
const blob = new Blob([data]);
return {
data: blob,
expires: undefined,
cacheControl: undefined,
};
} catch (error) {
if (error.name === 'AbortError') {
console.log('fetch cancelled');
return null;
}
throw error; // Rethrow for handling upstream
}
}
/**
* Default implementation for extracting z,x,y from a url
* @param {string} url - The url to extract from
* @returns {{z: number, x: number, y:number} | null} Returns the z,x,y of the url, or null if can't extract
*/
extractZXYFromUrlTrim(url) {
// 1. Find the index of the last `/`
const lastSlashIndex = url.lastIndexOf('/');
if (lastSlashIndex === -1) {
return null; // URL does not have any slashes
}
const segments = url.split('/');
if (segments.length <= 3) {
return null;
}
const ySegment = segments[segments.length - 1];
const xSegment = segments[segments.length - 2];
const zSegment = segments[segments.length - 3];
const lastDotIndex = ySegment.lastIndexOf('.');
const cleanedYSegment =
lastDotIndex === -1 ? ySegment : ySegment.substring(0, lastDotIndex);
// 3. Attempt to parse segments as numbers
const z = parseInt(zSegment, 10);
const x = parseInt(xSegment, 10);
const y = parseInt(cleanedYSegment, 10);
if (isNaN(z) || isNaN(x) || isNaN(y)) {
return null; // Conversion failed, invalid URL format
}
return { z, x, y };
}
/**
* Get the underlying maplibre-contour LocalDemManager
* @returns {any} the underlying maplibre-contour LocalDemManager
*/
getManager() {
return this.manager;
}
}

View file

@ -11,6 +11,8 @@ import SphericalMercator from '@mapbox/sphericalmercator';
import { Image, createCanvas } from 'canvas'; import { Image, createCanvas } from 'canvas';
import sharp from 'sharp'; import sharp from 'sharp';
import { LocalDemManager } from './contour.js';
import { fixTileJSONCenter, getTileUrls, isValidHttpUrl } from './utils.js';
import { import {
fixTileJSONCenter, fixTileJSONCenter,
getTileUrls, getTileUrls,