From 2abe481fd255b6c303f317317451c35a6a54fb54 Mon Sep 17 00:00:00 2001 From: Keita Kobayashi Date: Mon, 25 Nov 2024 15:45:11 +0900 Subject: [PATCH] Add clamp so we can ignore out-of-bounds coordinates --- src/lib/tilebelt_local.test.ts | 11 +++++++ src/lib/tilebelt_local.ts | 60 ++++++++++++++++++++-------------- src/local_spatial_id.ts | 1 + 3 files changed, 47 insertions(+), 25 deletions(-) diff --git a/src/lib/tilebelt_local.test.ts b/src/lib/tilebelt_local.test.ts index b2c9ffd..9485ed8 100644 --- a/src/lib/tilebelt_local.test.ts +++ b/src/lib/tilebelt_local.test.ts @@ -77,4 +77,15 @@ describe('pointToLocalTileFraction', () => { assert.deepStrictEqual(tb.pointToLocalTileFraction(1024, 511, 511, 0, 11), [1022, 1022, 0, 11]); assert.deepStrictEqual(tb.pointToLocalTileFraction(1024, 511, 511, 0, 9), [255.5, 255.5, 0, 9]); }); + + test('works with clamp', () => { + assert.deepStrictEqual(tb.pointToLocalTileFraction(1024, 0, 0, 0, 0, true), [0, 0, 0, 0]); + assert.deepStrictEqual(tb.pointToLocalTileFraction(1024, 1100, 1100, 0, 0, true), [0, 0, 0, 0]); + }); + + test('throws an error for out-of-bounds coordinates', () => { + assert.throws(() => { + tb.pointToLocalTileFraction(1024, 1100, 1100, 0, 0, false); + }); + }); }); diff --git a/src/lib/tilebelt_local.ts b/src/lib/tilebelt_local.ts index 7aa95e2..8db6495 100644 --- a/src/lib/tilebelt_local.ts +++ b/src/lib/tilebelt_local.ts @@ -15,9 +15,9 @@ export function tile2meters(scale: number, z: number): number { /** * Get the smallest tile to cover a bbox */ -export function bboxToLocalTile(scale: number, bboxCoords: BBox3D, minZoom?: number): XYFZTile { - const min = pointToLocalTile(scale, bboxCoords[0], bboxCoords[1], bboxCoords[2], 32); - const max = pointToLocalTile(scale, bboxCoords[3], bboxCoords[4], bboxCoords[5], 32); +export function bboxToLocalTile(scale: number, bboxCoords: BBox3D, minZoom?: number, clamp?: boolean): XYFZTile { + const min = pointToLocalTile(scale, bboxCoords[0], bboxCoords[1], bboxCoords[2], 32, clamp); + const max = pointToLocalTile(scale, bboxCoords[3], bboxCoords[4], bboxCoords[5], 32, clamp); const bbox: BBox3D = [min[0], min[1], min[2], max[0], max[1], max[2]]; const z = Math.min(getBboxZoom(bbox), typeof minZoom !== 'undefined' ? minZoom : MAX_ZOOM); @@ -29,7 +29,7 @@ export function bboxToLocalTile(scale: number, bboxCoords: BBox3D, minZoom?: num } -export function calculateLocalZFXY(scale: number, input: XYPointWithAltitude | BBox3D, zoom: number): ZFXYTile { +export function calculateLocalZFXY(scale: number, input: XYPointWithAltitude | BBox3D, zoom: number, clamp?: boolean): ZFXYTile { let bbox: BBox3D; if (Array.isArray(input) && input.length === 6) { bbox = input; @@ -37,31 +37,33 @@ export function calculateLocalZFXY(scale: number, input: XYPointWithAltitude | B const i = input as XYPointWithAltitude; bbox = [i.x, i.y, i.alt, i.x, i.y, i.alt]; } - const tile = bboxToLocalTile(scale, bbox, zoom); + const tile = bboxToLocalTile(scale, bbox, zoom, clamp); return xyfzTileAryToObj(tile); } /** * Get the smallest tile to cover a bbox + * not used right now */ -export function localBboxToTile(scale: number, bboxCoords: BBox3D, minZoom?: number): XYFZTile { - const min = pointToLocalTile(scale, bboxCoords[0], bboxCoords[1], bboxCoords[2], 32); - const max = pointToLocalTile(scale, bboxCoords[3], bboxCoords[4], bboxCoords[5], 32); - const bbox: BBox3D = [min[0], min[1], min[2], max[0], max[1], max[2]]; +// export function localBboxToTile(scale: number, bboxCoords: BBox3D, minZoom?: number): XYFZTile { +// const min = pointToLocalTile(scale, bboxCoords[0], bboxCoords[1], bboxCoords[2], 32); +// const max = pointToLocalTile(scale, bboxCoords[3], bboxCoords[4], bboxCoords[5], 32); +// const bbox: BBox3D = [min[0], min[1], min[2], max[0], max[1], max[2]]; - const z = Math.min(getBboxZoom(bbox), typeof minZoom !== 'undefined' ? minZoom : MAX_ZOOM); - if (z === 0) return [0, 0, 0, 0]; - const x = bbox[0] >>> (32 - z); - const y = bbox[1] >>> (32 - z); - const f = bbox[2] >>> (32 - z); - return [x, y, f, z]; -} +// const z = Math.min(getBboxZoom(bbox), typeof minZoom !== 'undefined' ? minZoom : MAX_ZOOM); +// if (z === 0) return [0, 0, 0, 0]; +// const x = bbox[0] >>> (32 - z); +// const y = bbox[1] >>> (32 - z); +// const f = bbox[2] >>> (32 - z); +// return [x, y, f, z]; +// } /** * Get the tile for a point at a specified zoom level + * Set `clamp` to `true` to prevent out-of-bounds coordinates */ -export function pointToLocalTile(scale: number, x: number, y: number, alt: number, z: number): XYFZTile { - var tile = pointToLocalTileFraction(scale, x, y, alt, z); +export function pointToLocalTile(scale: number, x: number, y: number, alt: number, z: number, clamp?: boolean): XYFZTile { + var tile = pointToLocalTileFraction(scale, x, y, alt, z, clamp); tile[0] = Math.floor(tile[0]); tile[1] = Math.floor(tile[1]); tile[2] = Math.floor(tile[2]); @@ -70,8 +72,9 @@ export function pointToLocalTile(scale: number, x: number, y: number, alt: numbe /** * Get the precise fractional tile location for a point at a zoom level + * Set `clamp` to `true` to prevent out-of-bounds coordinates */ -export function pointToLocalTileFraction(scale: number, xMeters: number, yMeters: number, altMeters: number, z: number): XYFZTile { +export function pointToLocalTileFraction(scale: number, xMeters: number, yMeters: number, altMeters: number, z: number, clamp?: boolean): XYFZTile { const z2 = Math.pow(2, z); // const z2_1 = Math.pow(2, z - 1); @@ -80,13 +83,20 @@ export function pointToLocalTileFraction(scale: number, xMeters: number, yMeters // const x = (z2 * (xMeters / scale)) + z2_1; // const y = (z2 * (yMeters / scale)) + z2_1; - const x = z2 * (xMeters / scale); - const y = z2 * (yMeters / scale); - const f = z2 * (altMeters / scale); + let x = z2 * (xMeters / scale); + let y = z2 * (yMeters / scale); + let f = z2 * (altMeters / scale); - // Detect out-of-bounds coordinates - if (x < 0 || x >= z2 || y < 0 || y >= z2 || f < 0 || f >= z2) { - throw new Error(`Point out of bounds: (${xMeters}, ${yMeters}, ${altMeters}) with scale ${scale}m (Point would have been (x=${x}, y=${y}, f=${f}, z=${z}) and maximum for this zoom is ${z2}).`); + if (!clamp) { + // Detect out-of-bounds coordinates + if (x < 0 || x >= z2 || y < 0 || y >= z2 || f < 0 || f >= z2) { + throw new Error(`Point out of bounds: (${xMeters}, ${yMeters}, ${altMeters}) with scale ${scale}m (Point would have been (x=${x}, y=${y}, f=${f}, z=${z}) and maximum for this zoom is ${z2}).`); + } + } else { + // Clamp to bounds + x = Math.max(0, Math.min(z2 - 1, x)); + y = Math.max(0, Math.min(z2 - 1, y)); + f = Math.max(0, Math.min(z2 - 1, f)); } return [x, y, f, z]; } diff --git a/src/local_spatial_id.ts b/src/local_spatial_id.ts index cb875d6..313bcaa 100644 --- a/src/local_spatial_id.ts +++ b/src/local_spatial_id.ts @@ -52,6 +52,7 @@ export class LocalSpatialId { this.namespace.scale, input, (typeof zoom !== 'undefined') ? zoom : DEFAULT_ZOOM, + true, // clamp to valid coordinates ); }