From a3411a356a94f86579e2b9496aaea507e1dbe5fe Mon Sep 17 00:00:00 2001 From: razbroc Date: Tue, 19 May 2026 11:33:19 +0300 Subject: [PATCH 1/3] fix: improve minimum zoom calculation in TileRanger --- src/geo/tileRanger.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/geo/tileRanger.ts b/src/geo/tileRanger.ts index 60ac31c..85ea95a 100644 --- a/src/geo/tileRanger.ts +++ b/src/geo/tileRanger.ts @@ -151,9 +151,9 @@ export class TileRanger { } const dx = boundingRange.maxX - boundingRange.minX; const dy = boundingRange.maxY - boundingRange.minY; - const minXZoom = Math.max(Math.floor(Math.log2(1 << (zoom + 1)) / dx) - 1, 0); - const minYZoom = Math.max(Math.floor(Math.log2(1 << zoom) / dy), 0); - const minZoom = Math.min(minXZoom, minYZoom); + const minXZoom = dx > 0 ? Math.max(Math.floor(Math.log2(1 << (zoom + 1)) / dx) - 1, 0) : zoom; + const minYZoom = dy > 0 ? Math.max(Math.floor(Math.log2(1 << zoom) / dy), 0) : zoom; + const minZoom = Math.min(minXZoom, minYZoom, zoom); if (verbose) { console.log(`MinZoom: ${minZoom}`); From 58aa8c56df9ce56ea687fb200bdf703a89b1242a Mon Sep 17 00:00:00 2001 From: shlomiko Date: Tue, 19 May 2026 16:28:24 +0300 Subject: [PATCH 2/3] fix: improve zoom calc in tile ranger --- src/geo/tileRanger.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/geo/tileRanger.ts b/src/geo/tileRanger.ts index 85ea95a..7bdbfa7 100644 --- a/src/geo/tileRanger.ts +++ b/src/geo/tileRanger.ts @@ -149,11 +149,12 @@ export class TileRanger { if (verbose) { console.log("find minimal zoom where the the area can be converted by area the size of single tile to skip levels that can't have full hashes"); } - const dx = boundingRange.maxX - boundingRange.minX; - const dy = boundingRange.maxY - boundingRange.minY; - const minXZoom = dx > 0 ? Math.max(Math.floor(Math.log2(1 << (zoom + 1)) / dx) - 1, 0) : zoom; - const minYZoom = dy > 0 ? Math.max(Math.floor(Math.log2(1 << zoom) / dy), 0) : zoom; - const minZoom = Math.min(minXZoom, minYZoom, zoom); + + const dx = boundingRange.maxX - boundingRange.minX + 1; + const dy = boundingRange.maxY - boundingRange.minY + 1; + const minXZoom = Math.max(Math.floor((zoom + 1) / dx) - 1, 0); + const minYZoom = Math.max(Math.floor(zoom / dy), 0); + const minZoom = Math.min(minXZoom, minYZoom); if (verbose) { console.log(`MinZoom: ${minZoom}`); From 976f2e0f1e777671326e25c5b5a3bb134a6d0266 Mon Sep 17 00:00:00 2001 From: razbroc Date: Tue, 19 May 2026 19:52:01 +0300 Subject: [PATCH 3/3] fix: refine minimum zoom calculation and add test for single-tile polygon encoding --- src/geo/tileRanger.ts | 8 ++++---- tests/unit/geo/tileRanger.spec.ts | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/geo/tileRanger.ts b/src/geo/tileRanger.ts index 7bdbfa7..8003d78 100644 --- a/src/geo/tileRanger.ts +++ b/src/geo/tileRanger.ts @@ -150,10 +150,10 @@ export class TileRanger { console.log("find minimal zoom where the the area can be converted by area the size of single tile to skip levels that can't have full hashes"); } - const dx = boundingRange.maxX - boundingRange.minX + 1; - const dy = boundingRange.maxY - boundingRange.minY + 1; - const minXZoom = Math.max(Math.floor((zoom + 1) / dx) - 1, 0); - const minYZoom = Math.max(Math.floor(zoom / dy), 0); + const xTilesCount = boundingRange.maxX - boundingRange.minX + 1; + const yTilesCount = boundingRange.maxY - boundingRange.minY + 1; + const minXZoom = Math.max(Math.floor((zoom + 1) / xTilesCount) - 1, 0); + const minYZoom = Math.max(Math.floor(zoom / yTilesCount), 0); const minZoom = Math.min(minXZoom, minYZoom); if (verbose) { diff --git a/tests/unit/geo/tileRanger.spec.ts b/tests/unit/geo/tileRanger.spec.ts index 8aba55d..dffd340 100644 --- a/tests/unit/geo/tileRanger.spec.ts +++ b/tests/unit/geo/tileRanger.spec.ts @@ -141,6 +141,28 @@ describe('TileRanger', () => { ]; expect(tileRanges).toEqual(expectedRanges); }); + + it('encodes non-bbox polygon whose bounding range is a single tile', async () => { + // Triangle fully within tile {x: 0, y: 1, zoom: 1}, which covers lon [-180, -90] lat [0, 90]. + // This exercises the single-tile edge case where xTilesCount=1, yTilesCount=1. + const poly = polygon([ + [ + [-170, 10], + [-100, 10], + [-135, 80], + [-170, 10], + ], + ]); + + const tileRanges = []; + const gen = ranger.encodeFootprint(poly, 1); + for await (const range of gen) { + tileRanges.push(range); + } + + const expectedRanges = [{ minX: 0, maxX: 0, minY: 1, maxY: 1, zoom: 1 }]; + expect(tileRanges).toEqual(expectedRanges); + }); }); describe('generateTiles', () => {