Skip to content

Commit

Permalink
feat: Handle 1:50k imagery TDE-1014 (#855)
Browse files Browse the repository at this point in the history
#### Motivation

In addition to accepting the 50k grid size, this also produces tile
names which consist of only the sheet code for 50k imagery. To do this
efficiently the code irrelevant to this simpler code path have been
rearranged to avoid unnecessary computation.

#### Checklist

- [x] Tests updated
- [ ] Docs updated (undocumented)
- [x] Issue linked in Title
  • Loading branch information
l0b0 authored Feb 11, 2024
1 parent 3b6e23f commit 8be7266
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ describe('getTileName', () => {
assert.equal(convertTileName('CH11_1000_1010', 10000), 'CH11_10000_0101');
assert.equal(convertTileName('CH11_1000_1001', 10000), 'CH11_10000_0101');
});
it('should get correct parent tile 1:50k', () => {
assert.equal(convertTileName('AT24_50000_0101', 50000), 'AT24');
assert.equal(convertTileName('AT25_50000_0101', 50000), 'AT25');
assert.equal(convertTileName('CK08_50000_0101', 50000), 'CK08');
});
});
describe('tiffLocation', () => {
it('get location from tiff', async () => {
Expand Down Expand Up @@ -230,7 +235,7 @@ describe('GridSizeFromString', () => {
it('should throw error when converting invalid grid size', async () => {
await assert.rejects(
GridSizeFromString.from('-1'),
new Error('Invalid grid size "-1"; valid values: "10000", "5000", "2000", "1000", "500"'),
new Error('Invalid grid size "-1"; valid values: "50000", "10000", "5000", "2000", "1000", "500"'),
);
});
});
34 changes: 19 additions & 15 deletions src/commands/tileindex-validate/tileindex.validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { logger } from '../../log.js';
import { isArgo } from '../../utils/argo.js';
import { FileFilter, getFiles } from '../../utils/chunk.js';
import { findBoundingBox } from '../../utils/geotiff.js';
import { GridSize, gridSizes, MapSheet, SheetRanges } from '../../utils/mapsheet.js';
import { GridSize, gridSizes, MapSheet, mapSheetTileGridSize, SheetRanges } from '../../utils/mapsheet.js';
import { config, createTiff, forceOutput, registerCli, verbose } from '../common.js';
import { CommandListArgs } from '../list/list.js';

Expand Down Expand Up @@ -310,7 +310,7 @@ export async function extractTiffLocations(
// if (shouldValidate) {
// // Is the tiff bounding box the same as the mapsheet bounding box!
// // Also need to allow for ~1.5cm of error between bounding boxes.
// // assert bbox == MapSheet.extract(tileName).bbox
// // assert bbox == MapSheet.getMapTileIndex(tileName).bbox
// }
return { bbox, source: tiff.source.url.href, tileName, epsg: tiff.images[0]?.epsg };
} catch (e) {
Expand Down Expand Up @@ -353,32 +353,36 @@ export function validateTiffAlignment(tiff: TiffLocation, allowedError = 0.015):
}

export function getTileName(x: number, y: number, gridSize: GridSize): string {
const tilesPerMapSheet = Math.floor(MapSheet.gridSizeMax / gridSize);
const tileWidth = Math.floor(MapSheet.width / tilesPerMapSheet);
const tileHeight = Math.floor(MapSheet.height / tilesPerMapSheet);
let nbDigits = 2;
if (gridSize === 500) {
nbDigits = 3;
}

if (!(SHEET_MIN_X <= x && x <= SHEET_MAX_X)) {
throw new Error(`x must be between ${SHEET_MIN_X} and ${SHEET_MAX_X}, was ${x}`);
}
if (!(SHEET_MIN_Y <= y && y <= SHEET_MAX_Y)) {
throw new Error(`y must be between ${SHEET_MIN_Y} and ${SHEET_MAX_Y}, was ${y}`);
}

// Do some maths
const offsetX = Math.round(Math.floor((x - MapSheet.origin.x) / MapSheet.width));
const offsetY = Math.round(Math.floor((MapSheet.origin.y - y) / MapSheet.height));
const maxY = MapSheet.origin.y - offsetY * MapSheet.height;
const minX = MapSheet.origin.x + offsetX * MapSheet.width;
const tileX = Math.round(Math.floor((x - minX) / tileWidth + 1));
const tileY = Math.round(Math.floor((maxY - y) / tileHeight + 1));

// Build name
const letters = Object.keys(SheetRanges)[offsetY];
const sheetCode = `${letters}${`${offsetX}`.padStart(2, '0')}`;
if (gridSize === mapSheetTileGridSize) {
// Shorter tile names for 1:50k
return sheetCode;
}

const tilesPerMapSheet = Math.floor(MapSheet.gridSizeMax / gridSize);
const tileWidth = Math.floor(MapSheet.width / tilesPerMapSheet);
const tileHeight = Math.floor(MapSheet.height / tilesPerMapSheet);

let nbDigits = 2;
if (gridSize === 500) {
nbDigits = 3;
}
const maxY = MapSheet.origin.y - offsetY * MapSheet.height;
const minX = MapSheet.origin.x + offsetX * MapSheet.width;
const tileX = Math.round(Math.floor((x - minX) / tileWidth + 1));
const tileY = Math.round(Math.floor((maxY - y) / tileHeight + 1));
const tileId = `${`${tileY}`.padStart(nbDigits, '0')}${`${tileX}`.padStart(nbDigits, '0')}`;
return `${sheetCode}_${gridSize}_${tileId}`;
}
20 changes: 19 additions & 1 deletion src/utils/__test__/mapsheet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { MapSheet, MapTileIndex } from '../mapsheet.js';
import { MapSheetData } from './mapsheet.data.js';

describe('MapSheets', () => {
it('should extract mapsheet', () => {
it('should extract MapTileIndex from 1:500 tile filename', () => {
assert.deepEqual(MapSheet.getMapTileIndex('2022_CG10_500_080037.tiff'), {
mapSheet: 'CG10',
gridSize: 500,
Expand All @@ -18,6 +18,24 @@ describe('MapSheets', () => {
bbox: [1236640, 4837200, 1236880, 4837560],
});
});
it('should extract MapTileIndex from 1:50k tile filename', () => {
assert.deepEqual(MapSheet.getMapTileIndex('AS21.tiff'), {
mapSheet: 'AS21',
gridSize: 50_000,
x: 1_492_000, // MapSheet.offset('AS21').x
y: 6_234_000, // MapSheet.offset('AS21').y
name: 'AS21',
origin: { x: 1_492_000, y: 6_234_000 }, // MapSheet.offset('AS21')
width: 24_000, // MapSheet.width
height: 36_000, // MapSheet.height
bbox: [
1_492_000, // MapSheet.offset('AS21').x
6_198_000, // MapSheet.offset('AS21').y - MapSheet.height
1_516_000, // MapSheet.offset('AS21').x + MapSheet.width
6_234_000, // MapSheet.offset('AS21').y
],
});
});

it('should calculate offsets', () => {
assert.deepEqual(MapSheet.offset('AS00'), { x: 988000, y: 6234000 });
Expand Down
41 changes: 29 additions & 12 deletions src/utils/mapsheet.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/** Parse topographic mapsheet names in the format `${mapSheet}_${gridSize}_${y}${x}` */
const MapSheetRegex = /([A-Z]{2}\d{2})_(\d+)_(\d+)/;
const MapSheetRegex = /(?<sheetCode>[A-Z]{2}\d{2})(_(?<gridSize>\d+)_(?<tileId>\d+))?/;

export interface MapTileIndex {
/**
Expand Down Expand Up @@ -56,7 +56,8 @@ export type Bounds = Point & Size;
const charA = 'A'.charCodeAt(0);
const charS = 'S'.charCodeAt(0);

export const gridSizes = [10_000, 5_000, 2_000, 1_000, 500] as const;
export const mapSheetTileGridSize = 50_000;
export const gridSizes = [mapSheetTileGridSize, 10_000, 5_000, 2_000, 1_000, 500] as const;
export type GridSize = (typeof gridSizes)[number];

/**
Expand All @@ -81,12 +82,12 @@ export const MapSheet = {
/** Width of Topo 1:50k mapsheets (meters) */
width: 24_000,
/** Base scale Topo 1:50k mapsheets (meters) */
scale: 50_000,
scale: mapSheetTileGridSize,
/** Map Sheets start at AS and end at CK */
code: { start: 'AS', end: 'CK' },
/** The top left point for where map sheets start from in NZTM2000 (EPSG:2193) */
origin: { x: 988000, y: 6234000 },
gridSizeMax: 50000,
gridSizeMax: mapSheetTileGridSize,
roundCorrection: 0.01,
/** Allowed grid sizes, these should exist in the LINZ Data service (meters) */
gridSizes: gridSizes,
Expand All @@ -96,17 +97,20 @@ export const MapSheet = {
*
* @example
* ```typescript
* MapSheet.extract("BP27_1000_4817.tiff") // { mapSheet: "BP27", gridSize: 1000, x: 17, y:48 }
* MapSheet.getMapTileIndex("BP27_1000_4817.tiff") // { mapSheet: "BP27", gridSize: 1000, x: 17, y:48 }
* ```
*/
getMapTileIndex(fileName: string): MapTileIndex | null {
const match = fileName.match(MapSheetRegex);
if (match == null) return null;
if (match[1] == null) return null;

const sheetCode = match?.groups?.['sheetCode'];
if (sheetCode == null) return null;

const gridSize = Number(match?.groups?.['gridSize'] ?? mapSheetTileGridSize);
const out: MapTileIndex = {
mapSheet: match[1],
gridSize: Number(match[2]),
mapSheet: sheetCode,
gridSize: gridSize,
x: -1,
y: -1,
name: match[0],
Expand All @@ -115,13 +119,26 @@ export const MapSheet = {
height: 0,
bbox: [0, 0, 0, 0],
};

const mapSheetOffset = MapSheet.offset(sheetCode);
if (out.gridSize === mapSheetTileGridSize) {
out.y = mapSheetOffset.y;
out.x = mapSheetOffset.x;
out.origin = mapSheetOffset;
out.width = MapSheet.width;
out.height = MapSheet.height;
// As in NZTM negative Y goes north, the minY is actually the bottom right point
out.bbox = [out.origin.x, out.origin.y - out.height, out.origin.x + out.width, out.origin.y];
return out;
}

// 1:500 has X/Y is 3 digits not 2
if (out.gridSize === 500) {
out.y = Number(match[3]?.slice(0, 3));
out.x = Number(match[3]?.slice(3));
out.y = Number(match?.groups?.['tileId']?.slice(0, 3));
out.x = Number(match?.groups?.['tileId']?.slice(3));
} else {
out.y = Number(match[3]?.slice(0, 2));
out.x = Number(match[3]?.slice(2));
out.y = Number(match?.groups?.['tileId']?.slice(0, 2));
out.x = Number(match?.groups?.['tileId']?.slice(2));
}
if (isNaN(out.gridSize) || isNaN(out.x) || isNaN(out.y)) return null;

Expand Down

0 comments on commit 8be7266

Please sign in to comment.