Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/modules.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</div></div></li><li><h5>maxTile: <a href="interfaces/ITile.html" class="tsd-signature-type" data-tsd-kind="Interface">ITile</a></h5><div class="tsd-comment tsd-typography"><div class="lead">
<p>corner tile for bbox with maximal x,y values</p>
</div></div></li></ul><h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">BBox2d</span></h4><div></div></li></ul></section><section class="tsd-panel tsd-member tsd-kind-function"><a id="bboxToTileRange" class="tsd-anchor"></a><h3 class="tsd-anchor-link">bbox<wbr/>To<wbr/>Tile<wbr/>Range<a href="#bboxToTileRange" aria-label="Permalink" class="tsd-anchor-icon"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-link" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 14a3.5 3.5 0 0 0 5 0l4 -4a3.5 3.5 0 0 0 -5 -5l-.5 .5"></path><path d="M14 10a3.5 3.5 0 0 0 -5 0l-4 4a3.5 3.5 0 0 0 5 5l.5 -.5"></path></svg></a></h3><ul class="tsd-signatures tsd-kind-function"><li class="tsd-signature tsd-kind-icon">bbox<wbr/>To<wbr/>Tile<wbr/>Range<span class="tsd-signature-symbol">(</span>bbox<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">BBox2d</span>, zoom<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">number</span><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">: </span><a href="interfaces/ITileRange.html" class="tsd-signature-type" data-tsd-kind="Interface">ITileRange</a></li></ul><ul class="tsd-descriptions"><li class="tsd-description"><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/MapColonies/mc-utils/blob/c3d42d0/src/geo/bboxUtils.ts#L74">src/geo/bboxUtils.ts:74</a></li></ul></aside><div class="tsd-comment tsd-typography"><div class="lead">
<p>coverts bbox to covering tile range of specified zoom level</p>
<p>converts bbox to covering tile range of specified zoom level</p>
</div></div><h4 class="tsd-parameters-title">Parameters</h4><ul class="tsd-parameters"><li><h5>bbox: <span class="tsd-signature-type">BBox2d</span></h5></li><li><h5>zoom: <span class="tsd-signature-type">number</span></h5><div class="tsd-comment tsd-typography"><div class="lead">
<p>target zoom level</p>
</div></div></li></ul><h4 class="tsd-returns-title">Returns <a href="interfaces/ITileRange.html" class="tsd-signature-type" data-tsd-kind="Interface">ITileRange</a></h4><div><p>covering tile range</p>
Expand Down Expand Up @@ -86,7 +86,7 @@
<p>optional - default is 0 - if minResolutionDeg property was provided, the param will be ignored</p>
</div></div></li></ul><h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">number</span></h4><div><p>tile count included on provided feature and zooms ranges</p>
</div></li></ul></section><section class="tsd-panel tsd-member tsd-kind-function"><a id="flipYAxis" class="tsd-anchor"></a><h3 class="tsd-anchor-link">flipYAxis<a href="#flipYAxis" aria-label="Permalink" class="tsd-anchor-icon"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-link" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10 14a3.5 3.5 0 0 0 5 0l4 -4a3.5 3.5 0 0 0 -5 -5l-.5 .5"></path><path d="M14 10a3.5 3.5 0 0 0 -5 0l-4 4a3.5 3.5 0 0 0 5 5l.5 -.5"></path></svg></a></h3><ul class="tsd-signatures tsd-kind-function"><li class="tsd-signature tsd-kind-icon">flipYAxis<span class="tsd-signature-symbol">(</span>tile<span class="tsd-signature-symbol">: </span><a href="interfaces/ITile.html" class="tsd-signature-type" data-tsd-kind="Interface">ITile</a><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">: </span><a href="interfaces/ITile.html" class="tsd-signature-type" data-tsd-kind="Interface">ITile</a></li></ul><ul class="tsd-descriptions"><li class="tsd-description"><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/MapColonies/mc-utils/blob/c3d42d0/src/geo/tiles.ts#L90">src/geo/tiles.ts:90</a></li></ul></aside><div class="tsd-comment tsd-typography"><div class="lead">
<p>coverts tile coordinates between ll and ul</p>
<p>converts tile coordinates between ll and ul</p>
</div></div><h4 class="tsd-parameters-title">Parameters</h4><ul class="tsd-parameters"><li><h5>tile: <a href="interfaces/ITile.html" class="tsd-signature-type" data-tsd-kind="Interface">ITile</a></h5><div class="tsd-comment tsd-typography"><div class="lead">
<p>source tile</p>
</div></div></li></ul><h4 class="tsd-returns-title">Returns <a href="interfaces/ITile.html" class="tsd-signature-type" data-tsd-kind="Interface">ITile</a></h4><div><p>converted tile</p>
Expand Down
6 changes: 3 additions & 3 deletions src/geo/bboxUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export const bboxFromTiles = (minTile: ITile, maxTile: ITile): BBox2d => {
};

/**
* coverts bbox to covering tile range of specified zoom level
* converts bbox to covering tile range of specified zoom level
* @param bbox
* @param zoom target zoom level
* @returns covering tile range
Expand All @@ -90,8 +90,8 @@ export const bboxToTileRange = (bbox: BBox2d, zoom: number): ITileRange => {
return {
minX: minTile.x,
minY: minTile.y,
maxX: maxTile.x,
maxY: maxTile.y,
maxX: maxTile.x - 1, // exclusive upper bound: maxTile is the first tile outside the bbox (right/above)
maxY: maxTile.y - 1, // exclusive upper bound: maxTile is the first tile outside the bbox (right/above)
zoom,
};
Comment thread
vitaligi marked this conversation as resolved.
};
44 changes: 22 additions & 22 deletions src/geo/tileBatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,24 @@ async function* tileBatchGenerator(batchSize: number, ranges: AsyncGenerator<ITi
let targetRanges: ITileRange[] = [];
let requiredForFullBatch = batchSize;
for await (const range of ranges) {
const dx = range.maxX - range.minX;
let dy = range.maxY - range.minY;
if (dx === 0 || dy === 0) {
const dx = range.maxX - range.minX + 1;
let dy = range.maxY - range.minY + 1;
if (range.maxX < range.minX || range.maxY < range.minY) {
// guard: degenerate/empty range
continue;
}
Comment thread
vitaligi marked this conversation as resolved.
let reminderX = range.maxX;
while (range.minY < range.maxY) {
//remaining tiles in batch row row
if (reminderX < range.maxX) {
const remaining = range.maxX - reminderX;
let reminderX = range.maxX + 1; // no partial row (one past inclusive end)
while (range.minY <= range.maxY) {
//remaining tiles in partial row from a previous cut
if (reminderX <= range.maxX) {
// reminderX > maxX means (no partial row)
const remaining = range.maxX - reminderX + 1;
if (remaining > requiredForFullBatch) {
targetRanges.push({
minX: reminderX,
maxX: reminderX + requiredForFullBatch,
maxX: reminderX + requiredForFullBatch - 1,
minY: range.minY,
maxY: range.minY + 1,
maxY: range.minY,
zoom: range.zoom,
});
yield await Promise.resolve(targetRanges);
Expand All @@ -36,9 +38,9 @@ async function* tileBatchGenerator(batchSize: number, ranges: AsyncGenerator<ITi
} else {
targetRanges.push({
minX: reminderX,
maxX: reminderX + remaining,
maxX: range.maxX,
minY: range.minY,
maxY: range.minY + 1,
maxY: range.minY,
zoom: range.zoom,
});
range.minY++;
Expand All @@ -47,35 +49,33 @@ async function* tileBatchGenerator(batchSize: number, ranges: AsyncGenerator<ITi
dy--;
}
}
//add max full lines
const requiredLines = Math.floor(requiredForFullBatch / dx);
const yRange = Math.min(requiredLines, dy);
if (yRange > 0) {
targetRanges.push({
minX: range.minX,
maxX: range.maxX,
minY: range.minY,
maxY: range.minY + yRange,
maxY: range.minY + yRange - 1,
zoom: range.zoom,
});
range.minY += yRange;
dy -= yRange;
requiredForFullBatch -= yRange * dx;
}
//add partial lines beginning
if (requiredForFullBatch > 0 && range.minY < range.maxY) {
const endX = Math.min(range.minX + requiredForFullBatch, range.maxX);
//add partial line at row beginning
if (requiredForFullBatch > 0 && range.minY <= range.maxY) {
const endX = Math.min(range.minX + requiredForFullBatch - 1, range.maxX);
targetRanges.push({
minX: range.minX,
maxX: endX,
minY: range.minY,
maxY: range.minY + 1,
maxY: range.minY,
zoom: range.zoom,
});
requiredForFullBatch -= endX - range.minX;
if (endX < range.maxX) {
reminderX = endX;
} else {
requiredForFullBatch -= endX - range.minX + 1;
reminderX = endX + 1;
if (endX >= range.maxX) {
range.minY++;
}
}
Expand Down
16 changes: 8 additions & 8 deletions src/geo/tileRanger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,21 @@ export class TileRanger {
public tileToRange(tile: ITile, zoom: number): ITileRange {
let minX: number, minY: number, maxX: number, maxY: number;
minX = tile.x;
maxX = tile.x + 1;
maxX = tile.x;
minY = tile.y;
maxY = tile.y + 1;
maxY = tile.y;
if (tile.zoom < zoom) {
const dz = zoom - tile.zoom;
minX = minX << dz;
maxX = maxX << dz;
maxX = ((tile.x + 1) << dz) - 1; // (tile.x+1) is the exclusive next tile; shift then -1 gives the inclusive last sub-tile
minY = minY << dz;
maxY = maxY << dz;
maxY = ((tile.y + 1) << dz) - 1; // same logic for Y axis
} else if (tile.zoom > zoom) {
const dz = tile.zoom - zoom;
minX = minX >> dz;
minY = minY >> dz;
maxX = minX + 1;
maxY = minY + 1;
maxX = minX; // tile maps to a single parent tile; inclusive range collapses to one tile
maxY = minY;
}
return {
minX,
Expand Down Expand Up @@ -163,8 +163,8 @@ export class TileRanger {
/////////////////////////////////////////////////////////////////////////////////////////////////
//find base hashes
const minimalRange = bboxToTileRange(bbox, minZoom);
for (let x = minimalRange.minX; x < minimalRange.maxX; x++) {
for (let y = minimalRange.minY; y < minimalRange.maxY; y++) {
for (let x = minimalRange.minX; x <= minimalRange.maxX; x++) {
for (let y = minimalRange.minY; y <= minimalRange.maxY; y++) {
/////////////////////////////////////////////////////////////////////////////////////////////////
/// Step 6: for every tile in the current range:
/// Step 7: check the tile intersection with the footprint
Expand Down
6 changes: 3 additions & 3 deletions src/geo/tiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export function degreesPerPixel(zoomLevel: number): number {
}

/**
* coverts tile coordinates between ll and ul
* converts tile coordinates between ll and ul
* @param tile source tile
* @returns converted tile
*/
Expand Down Expand Up @@ -142,8 +142,8 @@ export function tileToBbox(tile: ITile): BBox2d {
* @param ITileRange
* @returns
*/
export function tileRangeToTilesCount(batch: ITileRange): number {
return (batch.maxX - batch.minX) * (batch.maxY - batch.minY);
export function tileRangeToTilesCount(tileRange: ITileRange): number {
return (tileRange.maxX - tileRange.minX + 1) * (tileRange.maxY - tileRange.minY + 1);
}

/**
Expand Down
4 changes: 2 additions & 2 deletions src/geo/tilesGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { ITile, ITileRange } from '../models/interfaces/geo/iTile';

export async function* tilesGenerator(rangeGen: AsyncIterable<ITileRange>): AsyncGenerator<ITile> {
for await (const range of rangeGen) {
for (let x = range.minX; x < range.maxX; x++) {
for (let y = range.minY; y < range.maxY; y++) {
for (let x = range.minX; x <= range.maxX; x++) {
for (let y = range.minY; y <= range.maxY; y++) {
yield await Promise.resolve({
x,
y,
Expand Down
24 changes: 12 additions & 12 deletions tests/unit/geo/bboxUtils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,61 +33,61 @@ describe('bboxUtils', () => {
});

describe('bboxToTileRange', () => {
it('coverts bbox to expected tile range (no rounding, single tile)', () => {
it('converts bbox to expected tile range (no rounding, single tile)', () => {
const bbox = [0, 0, 45, 45] as BBox2d;

const range = bboxToTileRange(bbox, 2);

const expectedRange = {
Comment thread
vitaligi marked this conversation as resolved.
minX: 4,
minY: 2,
maxX: 5,
maxY: 3,
maxX: 4,
maxY: 2,
zoom: 2,
};
Comment thread
vitaligi marked this conversation as resolved.
expect(range).toEqual(expectedRange);
});

it('coverts bbox to expected tile range (no rounding)', () => {
it('converts bbox to expected tile range (no rounding)', () => {
const bbox = [0, 0, 90, 45] as BBox2d;

const range = bboxToTileRange(bbox, 2);

const expectedRange = {
minX: 4,
minY: 2,
maxX: 6,
maxY: 3,
maxX: 5,
maxY: 2,
zoom: 2,
};
Comment thread
vitaligi marked this conversation as resolved.
expect(range).toEqual(expectedRange);
});

it('coverts bbox to expected tile range (rounding down)', () => {
it('converts bbox to expected tile range (rounding down)', () => {
const bbox = [0, 0, 45, 45] as BBox2d;

const range = bboxToTileRange(bbox, 1);

const expectedRange = {
minX: 2,
minY: 1,
maxX: 3,
maxY: 2,
maxX: 2,
maxY: 1,
zoom: 1,
};
expect(range).toEqual(expectedRange);
});

it('coverts bbox to expected tile range (rounding up)', () => {
it('converts bbox to expected tile range (rounding up)', () => {
const bbox = [0, 0, 45, 45.1] as BBox2d;

const range = bboxToTileRange(bbox, 3);

const expectedRange = {
minX: 8,
minY: 4,
maxX: 10,
maxY: 7,
maxX: 9,
maxY: 6,
zoom: 3,
};
Comment thread
vitaligi marked this conversation as resolved.
expect(range).toEqual(expectedRange);
Expand Down
42 changes: 21 additions & 21 deletions tests/unit/geo/tileBatcher.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ describe('GeoHashBatcher', () => {
describe('#getResource', () => {
it('return expected batches for complex data', async function () {
const ranges = [
{ minX: 0, minY: 2, maxX: 5, maxY: 4, zoom: 8 },
{ minX: 0, minY: 6, maxX: 2, maxY: 8, zoom: 8 },
{ minX: 0, minY: 8, maxX: 1, maxY: 11, zoom: 8 },
{ minX: 0, minY: 2, maxX: 4, maxY: 3, zoom: 8 },
{ minX: 0, minY: 6, maxX: 1, maxY: 7, zoom: 8 },
{ minX: 0, minY: 8, maxX: 0, maxY: 10, zoom: 8 },
];

const rangeAsyncGen = (async function* () {
Expand All @@ -25,27 +25,27 @@ describe('GeoHashBatcher', () => {

// expectation
const expectedBatches = [
[{ minX: 0, maxX: 3, minY: 2, maxY: 3, zoom: 8 }],
[{ minX: 0, maxX: 2, minY: 2, maxY: 2, zoom: 8 }],
[
{ minX: 3, maxX: 5, minY: 2, maxY: 3, zoom: 8 },
{ minX: 0, maxX: 1, minY: 3, maxY: 4, zoom: 8 },
{ minX: 3, maxX: 4, minY: 2, maxY: 2, zoom: 8 },
{ minX: 0, maxX: 0, minY: 3, maxY: 3, zoom: 8 },
],
[{ minX: 1, maxX: 4, minY: 3, maxY: 4, zoom: 8 }],
[{ minX: 1, maxX: 3, minY: 3, maxY: 3, zoom: 8 }],
[
{ minX: 4, maxX: 5, minY: 3, maxY: 4, zoom: 8 },
{ minX: 0, maxX: 2, minY: 6, maxY: 7, zoom: 8 },
{ minX: 4, maxX: 4, minY: 3, maxY: 3, zoom: 8 },
{ minX: 0, maxX: 1, minY: 6, maxY: 6, zoom: 8 },
],
[
{ minX: 0, maxX: 2, minY: 7, maxY: 8, zoom: 8 },
{ minX: 0, maxX: 1, minY: 8, maxY: 9, zoom: 8 },
{ minX: 0, maxX: 1, minY: 7, maxY: 7, zoom: 8 },
{ minX: 0, maxX: 0, minY: 8, maxY: 8, zoom: 8 },
],
[{ minX: 0, maxX: 1, minY: 9, maxY: 11, zoom: 8 }],
[{ minX: 0, maxX: 0, minY: 9, maxY: 10, zoom: 8 }],
];
expect(batches).toEqual(expectedBatches);
});

it('return expected batch for single tile', async function () {
const ranges = [{ minX: 0, minY: 2, maxX: 1, maxY: 3, zoom: 8 }];
const ranges = [{ minX: 0, minY: 2, maxX: 0, maxY: 2, zoom: 8 }];
const rangeAsyncGen = (async function* () {
yield await Promise.resolve(ranges[0]);
})();
Expand All @@ -58,12 +58,12 @@ describe('GeoHashBatcher', () => {
}

// expectation
const expectedBatches = [[{ minX: 0, maxX: 1, minY: 2, maxY: 3, zoom: 8 }]];
const expectedBatches = [[{ minX: 0, maxX: 0, minY: 2, maxY: 2, zoom: 8 }]];
expect(batches).toEqual(expectedBatches);
});

it('return empty batch on invalid empty x', async function () {
const ranges = [{ minX: 0, minY: 2, maxX: 0, maxY: 3, zoom: 8 }];
const ranges = [{ minX: 1, minY: 2, maxX: 0, maxY: 3, zoom: 8 }];
const rangeAsyncGen = (async function* () {
yield await Promise.resolve(ranges[0]);
})();
Expand All @@ -81,7 +81,7 @@ describe('GeoHashBatcher', () => {
});

it('return empty batch on invalid empty y', async function () {
const ranges = [{ minX: 0, minY: 2, maxX: 4, maxY: 2, zoom: 8 }];
const ranges = [{ minX: 0, minY: 3, maxX: 4, maxY: 2, zoom: 8 }];
const rangeAsyncGen = (async function* () {
yield await Promise.resolve(ranges[0]);
})();
Expand All @@ -98,8 +98,8 @@ describe('GeoHashBatcher', () => {
expect(batches).toEqual(expectedBatches);
});

it('return proper tiles for batch size that is power of 2', async function () {
const ranges = [{ minX: 0, minY: 16, maxX: 16, maxY: 32, zoom: 5 }];
it('yields each tile individually when batch size is 1', async function () {
const ranges = [{ minX: 0, minY: 16, maxX: 15, maxY: 31, zoom: 5 }];
const rangeAsyncGen = (async function* () {
yield await Promise.resolve(ranges[0]);
})();
Expand All @@ -113,9 +113,9 @@ describe('GeoHashBatcher', () => {

// expectation
const expectedBatches: ITileRange[][] = [];
for (let y = 16; y < 32; y++) {
for (let x = 0; x < 16; x++) {
expectedBatches.push([{ minX: x, maxX: x + 1, minY: y, maxY: y + 1, zoom: 5 }]);
for (let y = 16; y <= 31; y++) {
for (let x = 0; x <= 15; x++) {
expectedBatches.push([{ minX: x, maxX: x, minY: y, maxY: y, zoom: 5 }]);
}
}
expect(batches).toEqual(expectedBatches);
Expand Down
Loading
Loading