Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
schmidtnz committed Dec 16, 2024
1 parent 641c472 commit a25a774
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ describe('tiffLocation', () => {
TiffAy29.images[0].origin[0] = 1684000;
TiffAy29.images[0].origin[1] = 6018000;
const location = await extractTiffLocations([TiffAs21, TiffAy29], 1000);
assert.equal(location[0]?.tileNames[0], 'AS21_1000_0101');
assert.equal(location[1]?.tileNames[0], 'AY29_1000_0101');
assert.deepEqual(location[0]?.tileNames, ['AS21_1000_0101']);
assert.deepEqual(location[1]?.tileNames, ['AY29_1000_0101']);
});

it('should find duplicates', async () => {
Expand Down Expand Up @@ -118,7 +118,7 @@ describe('tiffLocation', () => {
TiffAy29.images[0].origin[0] = 19128043.69337794;
TiffAy29.images[0].origin[1] = -4032710.6009459053;
const location = await extractTiffLocations([TiffAy29], 1000);
assert.equal(location[0]?.tileNames[0], 'AS21_1000_0101');
assert.deepEqual(location[0]?.tileNames, ['AS21_1000_0101']);
});

it('should fail if one location is not extracted', async () => {
Expand Down Expand Up @@ -204,7 +204,7 @@ describe('validate', () => {

for (const offset of [0.05, -0.05]) {
it(`should fail if input tiff origin X is offset by ${offset}m`, async (t) => {
const fakeTiff = FakeCogTiff.fromTileName('AS21_1000_0101');
const fakeTiff = FakeCogTiff.fromTileName('AU25_1000_0101');
fakeTiff.images[0].origin[0] = fakeTiff.images[0].origin[0] + offset;
t.mock.method(TiffLoader, 'load', () => Promise.resolve([fakeTiff]));
try {
Expand All @@ -222,7 +222,7 @@ describe('validate', () => {
}
});
it(`should fail if input tiff origin Y is offset by ${offset}m`, async (t) => {
const fakeTiff = FakeCogTiff.fromTileName('AS21_1000_0101');
const fakeTiff = FakeCogTiff.fromTileName('AU25_1000_0101');
fakeTiff.images[0].origin[1] = fakeTiff.images[0].origin[1] + offset;
t.mock.method(TiffLoader, 'load', () => Promise.resolve([fakeTiff]));
try {
Expand All @@ -246,7 +246,7 @@ describe('validate', () => {
// 720x481 => 720x481
// 721x481 => 721x481
it(`should fail if input tiff width is off by ${offset}m`, async (t) => {
const fakeTiff = FakeCogTiff.fromTileName('AS21_1000_0101');
const fakeTiff = FakeCogTiff.fromTileName('AU25_1000_0101');
fakeTiff.images[0].size.width = fakeTiff.images[0].size.width + offset;
t.mock.method(TiffLoader, 'load', () => Promise.resolve([fakeTiff]));
try {
Expand All @@ -264,7 +264,7 @@ describe('validate', () => {
}
});
it(`should fail if input tiff height is off by ${offset}m`, async (t) => {
const fakeTiff = FakeCogTiff.fromTileName('AS21_1000_0101');
const fakeTiff = FakeCogTiff.fromTileName('AU25_1000_0101');
fakeTiff.images[0].size.height = fakeTiff.images[0].size.height + offset;
t.mock.method(TiffLoader, 'load', () => Promise.resolve([fakeTiff]));
try {
Expand Down Expand Up @@ -309,3 +309,20 @@ describe('is8BitsTiff', () => {
});
});
});

describe('TiffFromMisalignedTiff', () => {
it('should properly identify all tiles under a tiff not aligned to our grid', async () => {
const fakeTiffCover4 = FakeCogTiff.fromTileName('CJ09');
fakeTiffCover4.images[0].origin[0] -= 10;
fakeTiffCover4.images[0].origin[1] += 10;
const fakeTiffCover9 = FakeCogTiff.fromTileName('BA33');
fakeTiffCover9.images[0].origin[0] -= 10;
fakeTiffCover9.images[0].origin[1] += 10;
fakeTiffCover9.images[0].size.width += 100;
fakeTiffCover9.images[0].size.height += 100;
const locations = await extractTiffLocations([fakeTiffCover4, fakeTiffCover9], 50000);

assert.deepEqual(locations[0]?.tileNames, ['CH08', 'CH09', 'CJ08', 'CJ09']);
assert.deepEqual(locations[1]?.tileNames, ['AZ32', 'AZ33', 'AZ34', 'BA32', 'BA33', 'BA34', 'BB32', 'BB33', 'BB34']);
});
});
86 changes: 60 additions & 26 deletions src/commands/tileindex-validate/tileindex.validate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import assert from 'node:assert';

import { Bounds, Projection } from '@basemaps/geo';
import { fsa } from '@chunkd/fs';
import { Size, Tiff, TiffTag } from '@cogeotiff/core';
Expand Down Expand Up @@ -362,6 +360,43 @@ export interface TiffLocation {
bands: string[];
}

/**
* Calculate the number grid tiles touched by a TIFF with min being the lowest width/height, and max the largest.
* @param inputMin lowest extent of misaligned tile on one dimension (left or bottom)
* @param inputMax largest extent of misaligned tile on same dimension (right or top)
* @param gridOrigin grid origin (x or y corresponding to min/max dimension)
* @param tileSize height or width of the target grid tile (corresponding to min/max dimension)
*/
const calculateStep = (inputMin: number, inputMax: number, gridOrigin: number, tileSize: number): number => {
const startStep = Math.floor((inputMin - gridOrigin) / tileSize);
const endStep = Math.floor((inputMax - gridOrigin - 1) / tileSize);

return endStep - startStep + 1;
};

/**
* Reproject the bounding box if the source and target projections are different.
* @param bbox input bounding box
* @param sourceProjection CRS of the input bounding box
* @param targetProjection target CRS
*/
function reprojectIfNeeded(
bbox: [number, number, number, number],
sourceProjection: Projection,
targetProjection: Projection,
): [number | undefined, number | undefined, number | undefined, number | undefined] {
{
if (targetProjection !== sourceProjection) {
const [ulX, ulY] = targetProjection.fromWgs84(sourceProjection.toWgs84([bbox[0], bbox[3]]));
const [lrX, lrY] = targetProjection.fromWgs84(sourceProjection.toWgs84([bbox[2], bbox[1]]));

return [ulX, lrY, lrX, ulY];
}

return bbox;
}
}

/**
* Create a list of `TiffLocation` from a list of TIFFs (`CogTiff`) by extracting their bounding box and generated their tile name from their origin based on a provided `GridSize`.
*
Expand All @@ -386,26 +421,10 @@ export async function extractTiffLocations(
return null;
}

const centerX = (bbox[0] + bbox[2]) / 2;
const centerY = (bbox[1] + bbox[3]) / 2;
// bbox is not epsg:2193
const targetProjection = Projection.get(2193);
const sourceProjection = Projection.get(sourceEpsg);

const [x, y] = targetProjection.fromWgs84(sourceProjection.toWgs84([centerX, centerY])).map(Math.round);
if (x == null || y == null) {
logger.error(
{ reason: 'Failed to reproject point', source: tiff.source },
'Reprojection:ExtracTiffLocations:Failed',
);
return null;
}

// Tilename from center
const tileName = getTileName(x, y, gridSize);

const [ulX, ulY] = targetProjection.fromWgs84(sourceProjection.toWgs84([bbox[0], bbox[3]])).map(Math.round);
const [lrX, lrY] = targetProjection.fromWgs84(sourceProjection.toWgs84([bbox[2], bbox[1]])).map(Math.round);
const [ulX, lrY, lrX, ulY] = reprojectIfNeeded(bbox, sourceProjection, targetProjection);

if (ulX == null || ulY == null || lrX == null || lrY == null) {
logger.error(
Expand All @@ -416,7 +435,6 @@ export async function extractTiffLocations(
}

const covering = [...iterateMapSheets([ulX, ulY, lrX, lrY], gridSize)] as string[];
assert.ok(covering.includes(tileName));

// if (shouldValidate) {
// // Is the tiff bounding box the same as the map sheet bounding box!
Expand Down Expand Up @@ -501,10 +519,8 @@ export function validateTiffAlignment(tiff: TiffLocation, allowedError = 0.015):

export function getTileName(x: number, y: number, gridSize: GridSize): string {
const sheetCode = MapSheet.sheetCode(x, y);
// TODO: re-enable this check when validation logic
if (!MapSheet.isKnown(sheetCode)) {
logger.warn('Map sheet outside known range: ' + sheetCode);
return '';
throw new RangeError(`Map sheet (${sheetCode}) at coordinates (${x}, ${y}) is outside the known range.`);
}

// Shorter tile names for 1:50k
Expand Down Expand Up @@ -555,9 +571,27 @@ export function* iterateMapSheets(bounds: BBox, gridSize: GridSize): Generator<s

const tileWidth = Math.floor(MapSheet.width / tilesPerMapSheet);
const tileHeight = Math.floor(MapSheet.height / tilesPerMapSheet);
for (let x = minX; x < maxX; x += tileWidth) {
for (let y = maxY; y > minY; y -= tileHeight) {
yield getTileName(x, y, gridSize);

let tile: string = '';
const stepsX = calculateStep(minX, maxX, MapSheet.origin.x, tileWidth);
const stepsY = calculateStep(minY, maxY, MapSheet.origin.y, tileHeight);

for (let stepY = 0; stepY < stepsY; stepY += 1) {
const y = maxY - stepY * tileHeight;
for (let stepX = 0; stepX < stepsX; stepX += 1) {
const x = minX + stepX * tileWidth;
try {
tile = getTileName(x, y, gridSize);
} catch (e) {
if (e instanceof RangeError) {
logger.warn(e['message']);
continue;
} else throw e;
}
yield tile;
}
}
if (tile === '') {
throw new Error(`No valid MapSheets found in the provided bounds ${bounds}`);

Check failure on line 595 in src/commands/tileindex-validate/tileindex.validate.ts

View workflow job for this annotation

GitHub Actions / build

Invalid type "BBox" of template literal expression
}
}

0 comments on commit a25a774

Please sign in to comment.