Skip to content

Commit

Permalink
feat(config): load the min/max zoom levels of a cotar overview from t…
Browse files Browse the repository at this point in the history
…he wmtscapabilties (#2621)

* feat(config): load the min/max zoom levels of a cotar overview from the wmtscapabilties

* refactor: fixup lint
  • Loading branch information
blacha committed Dec 8, 2022
1 parent b96b3ad commit 3fe70cf
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 46 deletions.
26 changes: 26 additions & 0 deletions packages/cli/src/cli/overview/__test__/wmts.overview.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import o from 'ospec';
import { zoomLevelsFromWmts } from '@basemaps/config';
import { GoogleTms, Nztm2000QuadTms } from '@basemaps/geo';
import { createOverviewWmtsCapabilities } from '../overview.wmts.js';

// This test should really live in @basemaps/config, but all the WMTS generation logic does not live in @basemaps/config
o.spec('zoomLevelsFromWmts', () => {
o('should extract zoom levels', () => {
const wmts = createOverviewWmtsCapabilities(Nztm2000QuadTms, 10, 'Test Title');
o(zoomLevelsFromWmts(wmts, Nztm2000QuadTms)).deepEquals({ minZoom: 0, maxZoom: 10 });
});

o('should include all zoom levels', () => {
const wmts = createOverviewWmtsCapabilities(GoogleTms, GoogleTms.maxZoom, 'Test Title');
o(zoomLevelsFromWmts(wmts, GoogleTms)).deepEquals({ minZoom: 0, maxZoom: GoogleTms.maxZoom });
});
o('should not extract zoom levels for wrong projection', () => {
const wmts = createOverviewWmtsCapabilities(GoogleTms, 10, 'Test Title');
o(zoomLevelsFromWmts(wmts, Nztm2000QuadTms)).equals(null);
});

o('should not return if zooms are invalid', () => {
const wmts = createOverviewWmtsCapabilities(Nztm2000QuadTms, 0, 'Test Title');
o(zoomLevelsFromWmts(wmts, Nztm2000QuadTms)).equals(null);
});
});
2 changes: 1 addition & 1 deletion packages/config/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export { ConfigDynamoBase } from './dynamo/dynamo.config.base.js';
export { ConfigProviderMemory, ConfigBundled } from './memory/memory.config.js';
export { TileSetNameComponents, TileSetNameParser } from './tile.set.name.js';
export { parseHex, parseRgba } from './color.js';
export { ConfigJson } from './json/json.config.js';
export { ConfigJson, zoomLevelsFromWmts } from './json/json.config.js';
export { standardizeLayerName } from './json/name.convertor.js';
export { base58, isBase58 } from './base58.js';
export { sha256base58, ensureBase58 } from './base58.node.js';
64 changes: 31 additions & 33 deletions packages/config/src/json/json.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
GoogleTms,
ImageFormat,
Nztm2000QuadTms,
QuadKey,
TileMatrixSet,
TileMatrixSets,
VectorFormat,
Expand Down Expand Up @@ -277,45 +276,44 @@ export class ConfigJson {
const exists = await fsa.exists(targetOverviews);
if (!exists) return;

const smallestTiff = cfg.files.reduce((prev, current) => {
if (prev.width < current.width) return prev;
return current;
}, cfg.files[0]);

const tileMatrix = TileMatrixSets.find(cfg.tileMatrix);
if (tileMatrix == null) throw new Error('Missing tileMatrix for imagery:' + cfg.id);

// Find a maximum tile that could be used in this overview
const maxZoomLoc = tileMatrix.sourceToPixels(smallestTiff.x, smallestTiff.y, tileMatrix.maxZoom);
const maxTile = tileMatrix.pixelsToTile(maxZoomLoc.x, maxZoomLoc.y, tileMatrix.maxZoom);
let qk = QuadKey.fromTile(maxTile);

const cotar = await Cotar.fromTar(fsa.source(targetOverviews));

// Query all the quadkeys looking to see what tiles exists
const queries: Promise<string | null>[] = [];
while (qk.length > 0) {
qk = QuadKey.parent(qk);
const tile = QuadKey.toTile(qk);
queries.push(
cotar.index.find(`tiles/${tile.z}/${tile.x}/${tile.y}.webp`).then((c) => {
if (c == null) return null;
return qk;
}),
);
}
// When the cotars are made a WMTSCapabilties is added so it easy to view in something like QGIS
// We can use the WMTSCapabitities to figure out the tileMatrix and zoom levels
const wmtsRaw = await cotar.get('WMTSCapabilities.xml');
if (wmtsRaw == null) return;

const result = await Promise.all(queries);
const wmts = Buffer.from(wmtsRaw).toString();

const overview: ConfigImageryOverview = { path: 'overviews.tar.co', minZoom: tileMatrix.maxZoom, maxZoom: 0 };
for (const r of result) {
if (r == null) continue;
if (r.length < overview.minZoom) overview.minZoom = r.length;
if (r.length > overview.maxZoom) overview.maxZoom = r.length;
}
const zoomLevels = zoomLevelsFromWmts(wmts, tileMatrix);
if (zoomLevels == null) return;

if (overview.minZoom === tileMatrix.maxZoom) return;
if (overview.maxZoom === 0) return;
return overview;
return { path: 'overviews.tar.co', minZoom: zoomLevels.minZoom, maxZoom: zoomLevels.maxZoom };
}
}

/** Attempt to parse a cotar WMTSCapabilties to figure out what zoom levels are applicable */
export function zoomLevelsFromWmts(
wmts: string,
tileMatrix: TileMatrixSet,
): { minZoom: number; maxZoom: number } | null {
const owsIds = wmts
.split('\n')
.filter((f) => f.includes('ows:Identifier'))
.map((c) => c.trim().replace('<ows:Identifier>', '').replace('</ows:Identifier>', ''));

const tileMatrixOffset = owsIds.indexOf(tileMatrix.identifier);
if (tileMatrixOffset === -1) return null;

const minZoom = Number(owsIds[tileMatrixOffset + 1]);
const maxZoom = Number(owsIds[owsIds.length - 1]);

if (isNaN(minZoom)) return null;
if (isNaN(maxZoom)) return null;
if (maxZoom < minZoom) return null;
if (maxZoom === 0) return null;
return { minZoom, maxZoom };
}
2 changes: 1 addition & 1 deletion packages/lambda-tiler/src/wmts.capability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ export class WmtsCapabilities {
V('ows:Identifier', tms.identifier),
V('ows:SupportedCRS', tms.projection.toUrn()),
tms.def.wellKnownScaleSet ? V('WellKnownScaleSet', tms.def.wellKnownScaleSet) : null,
...tms.def.tileMatrix.slice(this.minZoom, this.maxZoom).map((c) => {
...tms.def.tileMatrix.slice(this.minZoom, this.maxZoom + 1).map((c) => {
return V('TileMatrix', [
V('ows:Identifier', c.identifier),
V('ScaleDenominator', c.scaleDenominator),
Expand Down
4 changes: 1 addition & 3 deletions packages/shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,12 @@
"entities": "^4.3.0",
"pino": "^8.6.1",
"proj4": "^2.8.0",
"sax": "^1.2.4",
"source-map-support": "^0.5.19",
"ulid": "^2.3.0"
},
"devDependencies": {
"@types/aws-lambda": "^8.10.75",
"@types/proj4": "^2.5.2",
"@types/sax": "^1.2.1"
"@types/proj4": "^2.5.2"
},
"publishConfig": {
"access": "public"
Expand Down
9 changes: 1 addition & 8 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1977,13 +1977,6 @@
"@types/scheduler" "*"
csstype "^3.0.2"

"@types/sax@^1.2.1":
version "1.2.2"
resolved "https://registry.yarnpkg.com/@types/sax/-/sax-1.2.2.tgz#0b8996ffb9ca0b0491e791a09b550d77fa852f5d"
integrity sha512-nfnUx5UQ1R+/riXydS0UyYJiqtxgOeObr9Hw8xSTtpB4LNoHa1z31rsvGXN0JKz/7/kDXw0bT2Q/VQkXAbCYoA==
dependencies:
"@types/node" "*"

"@types/scheduler@*":
version "0.16.2"
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"
Expand Down Expand Up @@ -7295,7 +7288,7 @@ sax@1.2.1:
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a"
integrity sha1-e45lYZCyKOgaZq6nSEgNgozS03o=

sax@>=0.6.0, sax@^1.2.4:
sax@>=0.6.0:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
Expand Down

0 comments on commit 3fe70cf

Please sign in to comment.