@basemaps/lambda-tiler 6.29.0 → 6.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -0
- package/build/__tests__/config.data.d.ts +11 -0
- package/build/__tests__/config.data.d.ts.map +1 -0
- package/build/__tests__/config.data.js +112 -0
- package/build/__tests__/config.data.js.map +1 -0
- package/build/__tests__/index.test.js +5 -14
- package/build/__tests__/index.test.js.map +1 -0
- package/build/__tests__/tile.style.json.test.js +1 -0
- package/build/__tests__/tile.style.json.test.js.map +1 -0
- package/build/__tests__/wmts.capability.test.d.ts +1 -1
- package/build/__tests__/wmts.capability.test.d.ts.map +1 -1
- package/build/__tests__/wmts.capability.test.js +286 -125
- package/build/__tests__/wmts.capability.test.js.map +1 -0
- package/build/__tests__/xyz.util.d.ts +7 -11
- package/build/__tests__/xyz.util.d.ts.map +1 -1
- package/build/__tests__/xyz.util.js +14 -42
- package/build/__tests__/xyz.util.js.map +1 -0
- package/build/index.d.ts +0 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +68 -41
- package/build/index.js.map +1 -0
- package/build/routes/__tests__/attribution.test.js +351 -399
- package/build/routes/__tests__/attribution.test.js.map +1 -0
- package/build/routes/__tests__/fonts.test.js +17 -3
- package/build/routes/__tests__/fonts.test.js.map +1 -0
- package/build/routes/__tests__/health.test.js +17 -13
- package/build/routes/__tests__/health.test.js.map +1 -0
- package/build/routes/__tests__/imagery.test.js +1 -0
- package/build/routes/__tests__/imagery.test.js.map +1 -0
- package/build/routes/__tests__/memory.fs.js +1 -0
- package/build/routes/__tests__/memory.fs.js.map +1 -0
- package/build/routes/__tests__/sprites.test.js +7 -0
- package/build/routes/__tests__/sprites.test.js.map +1 -0
- package/build/routes/__tests__/tile.json.test.d.ts +2 -0
- package/build/routes/__tests__/tile.json.test.d.ts.map +1 -0
- package/build/routes/__tests__/tile.json.test.js +124 -0
- package/build/routes/__tests__/tile.json.test.js.map +1 -0
- package/build/routes/__tests__/tile.style.json.test.d.ts +2 -0
- package/build/routes/__tests__/tile.style.json.test.d.ts.map +1 -0
- package/build/routes/__tests__/tile.style.json.test.js +95 -0
- package/build/routes/__tests__/tile.style.json.test.js.map +1 -0
- package/build/routes/__tests__/wmts.test.js +37 -27
- package/build/routes/__tests__/wmts.test.js.map +1 -0
- package/build/{__tests__ → routes/__tests__}/xyz.test.d.ts +0 -0
- package/build/routes/__tests__/xyz.test.d.ts.map +1 -0
- package/build/routes/__tests__/xyz.test.js +99 -0
- package/build/routes/__tests__/xyz.test.js.map +1 -0
- package/build/routes/attribution.d.ts +7 -5
- package/build/routes/attribution.d.ts.map +1 -1
- package/build/routes/attribution.js +50 -91
- package/build/routes/attribution.js.map +1 -0
- package/build/routes/fonts.d.ts +1 -1
- package/build/routes/fonts.d.ts.map +1 -1
- package/build/routes/fonts.js +33 -10
- package/build/routes/fonts.js.map +1 -0
- package/build/routes/health.d.ts +3 -3
- package/build/routes/health.d.ts.map +1 -1
- package/build/routes/health.js +16 -13
- package/build/routes/health.js.map +1 -0
- package/build/routes/imagery.d.ts +8 -1
- package/build/routes/imagery.d.ts.map +1 -1
- package/build/routes/imagery.js +17 -17
- package/build/routes/imagery.js.map +1 -0
- package/build/routes/ping.d.ts +3 -0
- package/build/routes/ping.d.ts.map +1 -0
- package/build/routes/ping.js +7 -0
- package/build/routes/ping.js.map +1 -0
- package/build/routes/sprites.d.ts.map +1 -1
- package/build/routes/sprites.js +22 -22
- package/build/routes/sprites.js.map +1 -0
- package/build/routes/tile.json.d.ts +7 -1
- package/build/routes/tile.json.d.ts.map +1 -1
- package/build/routes/tile.json.js +19 -22
- package/build/routes/tile.json.js.map +1 -0
- package/build/routes/tile.style.json.d.ts +6 -1
- package/build/routes/tile.style.json.d.ts.map +1 -1
- package/build/routes/tile.style.json.js +11 -13
- package/build/routes/tile.style.json.js.map +1 -0
- package/build/routes/tile.wmts.d.ts +9 -3
- package/build/routes/tile.wmts.d.ts.map +1 -1
- package/build/routes/tile.wmts.js +37 -50
- package/build/routes/tile.wmts.js.map +1 -0
- package/build/routes/tile.xyz.d.ts +14 -4
- package/build/routes/tile.xyz.d.ts.map +1 -1
- package/build/routes/tile.xyz.js +22 -17
- package/build/routes/tile.xyz.js.map +1 -0
- package/build/routes/tile.xyz.raster.d.ts +11 -0
- package/build/routes/tile.xyz.raster.d.ts.map +1 -0
- package/build/routes/tile.xyz.raster.js +90 -0
- package/build/routes/tile.xyz.raster.js.map +1 -0
- package/build/routes/tile.xyz.vector.d.ts +8 -0
- package/build/routes/tile.xyz.vector.d.ts.map +1 -0
- package/build/routes/tile.xyz.vector.js +46 -0
- package/build/routes/tile.xyz.vector.js.map +1 -0
- package/build/routes/version.d.ts +3 -0
- package/build/routes/version.d.ts.map +1 -0
- package/build/routes/version.js +9 -0
- package/build/routes/version.js.map +1 -0
- package/build/util/__test__/validate.test.d.ts +2 -0
- package/build/util/__test__/validate.test.d.ts.map +1 -0
- package/build/util/__test__/validate.test.js +66 -0
- package/build/util/__test__/validate.test.js.map +1 -0
- package/build/util/cotar.serve.d.ts +20 -0
- package/build/util/cotar.serve.d.ts.map +1 -0
- package/build/util/cotar.serve.js +41 -0
- package/build/util/cotar.serve.js.map +1 -0
- package/build/util/etag.d.ts +6 -0
- package/build/util/etag.d.ts.map +1 -0
- package/build/util/etag.js +20 -0
- package/build/util/etag.js.map +1 -0
- package/build/util/response.d.ts +4 -0
- package/build/util/response.d.ts.map +1 -0
- package/build/util/response.js +4 -0
- package/build/util/response.js.map +1 -0
- package/build/util/source.cache.d.ts +28 -0
- package/build/util/source.cache.d.ts.map +1 -0
- package/build/util/source.cache.js +53 -0
- package/build/util/source.cache.js.map +1 -0
- package/build/{source.tracer.d.ts → util/source.tracer.d.ts} +1 -0
- package/build/util/source.tracer.d.ts.map +1 -0
- package/build/{source.tracer.js → util/source.tracer.js} +4 -0
- package/build/util/source.tracer.js.map +1 -0
- package/build/util/swapping.lru.d.ts +21 -0
- package/build/util/swapping.lru.d.ts.map +1 -0
- package/build/util/swapping.lru.js +56 -0
- package/build/util/swapping.lru.js.map +1 -0
- package/build/util/validate.d.ts +46 -0
- package/build/util/validate.d.ts.map +1 -0
- package/build/util/validate.js +107 -0
- package/build/util/validate.js.map +1 -0
- package/build/wmts.capability.d.ts +27 -13
- package/build/wmts.capability.d.ts.map +1 -1
- package/build/wmts.capability.js +156 -55
- package/build/wmts.capability.js.map +1 -0
- package/dist/index.js +89 -73
- package/dist/node_modules/.package-lock.json +1 -1
- package/dist/package-lock.json +2 -2
- package/dist/package.json +1 -1
- package/package.json +10 -10
- package/src/__tests__/config.data.ts +120 -0
- package/src/__tests__/index.test.ts +4 -20
- package/src/__tests__/wmts.capability.test.ts +312 -139
- package/src/__tests__/xyz.util.ts +17 -45
- package/src/index.ts +75 -41
- package/src/routes/__tests__/attribution.test.ts +356 -403
- package/src/routes/__tests__/fonts.test.ts +18 -3
- package/src/routes/__tests__/health.test.ts +17 -13
- package/src/routes/__tests__/sprites.test.ts +6 -1
- package/src/routes/__tests__/tile.json.test.ts +145 -0
- package/src/routes/__tests__/tile.style.json.test.ts +105 -0
- package/src/routes/__tests__/wmts.test.ts +44 -34
- package/src/routes/__tests__/xyz.test.ts +119 -0
- package/src/routes/attribution.ts +59 -111
- package/src/routes/fonts.ts +32 -10
- package/src/routes/health.ts +17 -16
- package/src/routes/imagery.ts +18 -15
- package/src/routes/ping.ts +8 -0
- package/src/routes/sprites.ts +20 -22
- package/src/routes/tile.json.ts +24 -19
- package/src/routes/tile.style.json.ts +15 -12
- package/src/routes/tile.wmts.ts +41 -44
- package/src/routes/tile.xyz.raster.ts +106 -0
- package/src/routes/tile.xyz.ts +31 -16
- package/src/routes/tile.xyz.vector.ts +47 -0
- package/src/routes/version.ts +8 -0
- package/src/util/__test__/validate.test.ts +74 -0
- package/src/util/cotar.serve.ts +46 -0
- package/src/util/etag.ts +20 -0
- package/src/util/response.ts +4 -0
- package/src/util/source.cache.ts +71 -0
- package/src/{source.tracer.ts → util/source.tracer.ts} +4 -0
- package/src/util/swapping.lru.ts +63 -0
- package/src/util/validate.ts +126 -0
- package/src/wmts.capability.ts +170 -68
- package/tsconfig.tsbuildinfo +1 -1
- package/build/__tests__/route.test.d.ts +0 -2
- package/build/__tests__/route.test.d.ts.map +0 -1
- package/build/__tests__/route.test.js +0 -20
- package/build/__tests__/tiff.cache.test.d.ts +0 -2
- package/build/__tests__/tiff.cache.test.d.ts.map +0 -1
- package/build/__tests__/tiff.cache.test.js +0 -58
- package/build/__tests__/tile.cache.key.test.d.ts +0 -2
- package/build/__tests__/tile.cache.key.test.d.ts.map +0 -1
- package/build/__tests__/tile.cache.key.test.js +0 -48
- package/build/__tests__/tile.set.cache.test.d.ts +0 -2
- package/build/__tests__/tile.set.cache.test.d.ts.map +0 -1
- package/build/__tests__/tile.set.cache.test.js +0 -123
- package/build/__tests__/tile.set.test.d.ts +0 -2
- package/build/__tests__/tile.set.test.d.ts.map +0 -1
- package/build/__tests__/tile.set.test.js +0 -11
- package/build/__tests__/xyz.test.d.ts.map +0 -1
- package/build/__tests__/xyz.test.js +0 -306
- package/build/api.key.d.ts +0 -2
- package/build/api.key.d.ts.map +0 -1
- package/build/api.key.js +0 -23
- package/build/cli/dump.d.ts +0 -2
- package/build/cli/dump.d.ts.map +0 -1
- package/build/cli/dump.js +0 -47
- package/build/cli/tile.set.local.d.ts +0 -12
- package/build/cli/tile.set.local.d.ts.map +0 -1
- package/build/cli/tile.set.local.js +0 -39
- package/build/router.d.ts +0 -15
- package/build/router.d.ts.map +0 -1
- package/build/router.js +0 -49
- package/build/routes/api.d.ts +0 -5
- package/build/routes/api.d.ts.map +0 -1
- package/build/routes/api.js +0 -16
- package/build/routes/esri/rest.d.ts +0 -10
- package/build/routes/esri/rest.d.ts.map +0 -1
- package/build/routes/esri/rest.js +0 -87
- package/build/routes/response.d.ts +0 -4
- package/build/routes/response.d.ts.map +0 -1
- package/build/routes/response.js +0 -3
- package/build/routes/tile.d.ts +0 -3
- package/build/routes/tile.d.ts.map +0 -1
- package/build/routes/tile.etag.d.ts +0 -11
- package/build/routes/tile.etag.d.ts.map +0 -1
- package/build/routes/tile.etag.js +0 -29
- package/build/routes/tile.js +0 -27
- package/build/source.tracer.d.ts.map +0 -1
- package/build/tiff.cache.d.ts +0 -17
- package/build/tiff.cache.d.ts.map +0 -1
- package/build/tiff.cache.js +0 -45
- package/build/tile.set.cache.d.ts +0 -21
- package/build/tile.set.cache.d.ts.map +0 -1
- package/build/tile.set.cache.js +0 -100
- package/build/tile.set.d.ts +0 -4
- package/build/tile.set.d.ts.map +0 -1
- package/build/tile.set.js +0 -1
- package/build/tile.set.raster.d.ts +0 -49
- package/build/tile.set.raster.d.ts.map +0 -1
- package/build/tile.set.raster.js +0 -186
- package/build/tile.set.vector.d.ts +0 -25
- package/build/tile.set.vector.d.ts.map +0 -1
- package/build/tile.set.vector.js +0 -71
- package/build/validate.d.ts +0 -16
- package/build/validate.d.ts.map +0 -1
- package/build/validate.js +0 -31
- package/src/__tests__/route.test.ts +0 -24
- package/src/__tests__/tiff.cache.test.ts +0 -73
- package/src/__tests__/tile.cache.key.test.ts +0 -56
- package/src/__tests__/tile.set.cache.test.ts +0 -146
- package/src/__tests__/tile.set.test.ts +0 -12
- package/src/__tests__/xyz.test.ts +0 -362
- package/src/api.key.ts +0 -23
- package/src/cli/dump.ts +0 -61
- package/src/cli/tile.set.local.ts +0 -51
- package/src/router.ts +0 -58
- package/src/routes/api.ts +0 -19
- package/src/routes/esri/rest.ts +0 -90
- package/src/routes/response.ts +0 -4
- package/src/routes/tile.etag.ts +0 -36
- package/src/routes/tile.ts +0 -23
- package/src/tiff.cache.ts +0 -51
- package/src/tile.set.cache.ts +0 -111
- package/src/tile.set.raster.ts +0 -228
- package/src/tile.set.ts +0 -4
- package/src/tile.set.vector.ts +0 -79
- package/src/validate.ts +0 -32
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export class SwappingLru<T extends { size: number }> {
|
|
2
|
+
cacheA: Map<string, T> = new Map();
|
|
3
|
+
cacheB: Map<string, T> = new Map();
|
|
4
|
+
maxSize: number;
|
|
5
|
+
|
|
6
|
+
hits = 0;
|
|
7
|
+
misses = 0;
|
|
8
|
+
resets = 0;
|
|
9
|
+
|
|
10
|
+
_lastCheckedAt = -1;
|
|
11
|
+
|
|
12
|
+
constructor(maxSize: number) {
|
|
13
|
+
this.maxSize = maxSize;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
get(id: string): T | null {
|
|
17
|
+
const cacheA = this.cacheA.get(id);
|
|
18
|
+
if (cacheA) {
|
|
19
|
+
this.hits++;
|
|
20
|
+
return cacheA;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const cacheB = this.cacheB.get(id);
|
|
24
|
+
if (cacheB == null) {
|
|
25
|
+
this.misses++;
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.hits++;
|
|
30
|
+
// If a object is still useful move it into the main cache
|
|
31
|
+
this.cacheA.set(id, cacheB);
|
|
32
|
+
this.cacheB.delete(id);
|
|
33
|
+
return cacheB;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Reset the cache */
|
|
37
|
+
clear(): void {
|
|
38
|
+
this.cacheA.clear();
|
|
39
|
+
this.cacheB.clear();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
set(id: string, tiff: T): void {
|
|
43
|
+
this.cacheA.set(id, tiff);
|
|
44
|
+
this.check();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Validate the size of the cache has not exploded */
|
|
48
|
+
check(): void {
|
|
49
|
+
this._lastCheckedAt = Date.now();
|
|
50
|
+
if (this.maxSize <= 0) return;
|
|
51
|
+
if (this.currentSize <= this.maxSize) return;
|
|
52
|
+
this.resets++;
|
|
53
|
+
this.cacheB = this.cacheA;
|
|
54
|
+
this.cacheA = new Map();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Calculate the total number of bytes used by this cache */
|
|
58
|
+
get currentSize(): number {
|
|
59
|
+
let size = 0;
|
|
60
|
+
for (const value of this.cacheA.values()) size += value.size;
|
|
61
|
+
return size;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { ImageFormat, TileMatrixSet, TileMatrixSets, VectorFormat } from '@basemaps/geo';
|
|
2
|
+
import { Const, Projection } from '@basemaps/shared';
|
|
3
|
+
import { getImageFormat } from '@basemaps/tiler';
|
|
4
|
+
import { LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
|
|
5
|
+
import * as ulid from 'ulid';
|
|
6
|
+
import { TileXyzGet } from '../routes/tile.xyz';
|
|
7
|
+
|
|
8
|
+
export interface TileXyz {
|
|
9
|
+
tile: { x: number; y: number; z: number };
|
|
10
|
+
tileSet: string;
|
|
11
|
+
tileMatrix: TileMatrixSet;
|
|
12
|
+
tileType: VectorFormat | ImageFormat;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface TileMatrixRequest {
|
|
16
|
+
Params: { tileMatrix?: string };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const OneHourMs = 60 * 60 * 1000;
|
|
20
|
+
const OneDayMs = 24 * OneHourMs;
|
|
21
|
+
const MaxApiAgeMs = 91 * OneDayMs;
|
|
22
|
+
|
|
23
|
+
export interface ApiKeyStatus {
|
|
24
|
+
valid: boolean;
|
|
25
|
+
message: 'ok' | 'malformed' | 'missing' | 'expired';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function isValidApiKey(apiKey?: string | null): ApiKeyStatus {
|
|
29
|
+
if (apiKey == null) return { valid: false, message: 'missing' };
|
|
30
|
+
if (!apiKey.startsWith('c') && !apiKey.startsWith('d')) return { valid: false, message: 'malformed' };
|
|
31
|
+
const ulidId = apiKey.slice(1).toUpperCase();
|
|
32
|
+
try {
|
|
33
|
+
const ulidTime = ulid.decodeTime(ulidId);
|
|
34
|
+
if (apiKey.startsWith('d')) return { valid: true, message: 'ok' };
|
|
35
|
+
|
|
36
|
+
if (Date.now() - ulidTime > MaxApiAgeMs) return { valid: false, message: 'expired' };
|
|
37
|
+
} catch (e) {
|
|
38
|
+
return { valid: false, message: 'malformed' };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return { valid: true, message: 'ok' };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const Validate = {
|
|
45
|
+
/**
|
|
46
|
+
* Validate that the api key exists and is valid
|
|
47
|
+
* @throws if api key is not valid
|
|
48
|
+
*/
|
|
49
|
+
apiKey(req: LambdaHttpRequest): string {
|
|
50
|
+
const apiKey = req.query.get(Const.ApiKey.QueryString) ?? req.header('X-LINZ-Api-Key');
|
|
51
|
+
const valid = isValidApiKey(apiKey);
|
|
52
|
+
|
|
53
|
+
if (!valid.valid) throw new LambdaHttpResponse(400, 'API Key Invalid: ' + valid.message);
|
|
54
|
+
req.set('api', apiKey);
|
|
55
|
+
return apiKey as string;
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
getTileMatrixSet(str?: string): TileMatrixSet | null {
|
|
59
|
+
return TileMatrixSets.find(str);
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
/** Read in all image formats specified in the query parameters "format" or "tileFormat" */
|
|
63
|
+
getRequestedFormats(req: LambdaHttpRequest): ImageFormat[] | null {
|
|
64
|
+
const formats = [...req.query.getAll('format'), ...req.query.getAll('tileFormat')];
|
|
65
|
+
if (formats.length === 0) return null;
|
|
66
|
+
|
|
67
|
+
const output: Set<ImageFormat> = new Set();
|
|
68
|
+
for (const fmt of formats) {
|
|
69
|
+
const parsed = getImageFormat(fmt);
|
|
70
|
+
if (parsed == null) continue;
|
|
71
|
+
output.add(parsed);
|
|
72
|
+
}
|
|
73
|
+
if (output.size === 0) return null;
|
|
74
|
+
return [...output.values()];
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
getTileFormat(tileType: string): ImageFormat | VectorFormat | null {
|
|
78
|
+
const ext = getImageFormat(tileType);
|
|
79
|
+
if (ext) return ext;
|
|
80
|
+
if (tileType === VectorFormat.MapboxVectorTiles) return VectorFormat.MapboxVectorTiles;
|
|
81
|
+
return null;
|
|
82
|
+
},
|
|
83
|
+
/**
|
|
84
|
+
* Validate that the tile request is somewhat valid
|
|
85
|
+
* - Valid projection
|
|
86
|
+
* - Valid range
|
|
87
|
+
*
|
|
88
|
+
* @throws LambdaHttpResponse for tile requests that are not valid
|
|
89
|
+
*
|
|
90
|
+
* @param req request to validate
|
|
91
|
+
* @param xyzData
|
|
92
|
+
*/
|
|
93
|
+
xyz(req: LambdaHttpRequest<TileXyzGet>): TileXyz {
|
|
94
|
+
Validate.apiKey(req);
|
|
95
|
+
|
|
96
|
+
req.set('tileSet', req.params.tileSet);
|
|
97
|
+
|
|
98
|
+
const x = parseInt(req.params.x, 10);
|
|
99
|
+
const y = parseInt(req.params.y, 10);
|
|
100
|
+
const z = parseInt(req.params.z, 10);
|
|
101
|
+
|
|
102
|
+
const tileMatrix = Validate.getTileMatrixSet(req.params.tileMatrix);
|
|
103
|
+
if (tileMatrix == null) throw new LambdaHttpResponse(404, 'Tile Matrix not found');
|
|
104
|
+
|
|
105
|
+
req.set('tileMatrix', tileMatrix.identifier);
|
|
106
|
+
req.set('projection', tileMatrix.projection.code);
|
|
107
|
+
|
|
108
|
+
const tileType = Validate.getTileFormat(req.params.tileType);
|
|
109
|
+
if (tileType == null) throw new LambdaHttpResponse(404, 'Tile extension not found');
|
|
110
|
+
req.set('extension', tileType);
|
|
111
|
+
|
|
112
|
+
if (isNaN(z) || z > tileMatrix.maxZoom || z < 0) throw new LambdaHttpResponse(404, `Zoom not found: ${z}`);
|
|
113
|
+
|
|
114
|
+
const zoom = tileMatrix.zooms[z];
|
|
115
|
+
if (isNaN(x) || x < 0 || x > zoom.matrixWidth) throw new LambdaHttpResponse(404, `X not found: ${x}`);
|
|
116
|
+
if (isNaN(y) || y < 0 || y > zoom.matrixHeight) throw new LambdaHttpResponse(404, `Y not found: ${y}`);
|
|
117
|
+
|
|
118
|
+
const xyzData = { tile: { x, y, z }, tileSet: req.params.tileSet, tileMatrix, tileType };
|
|
119
|
+
req.set('xyz', xyzData.tile);
|
|
120
|
+
|
|
121
|
+
const latLon = Projection.tileCenterToLatLon(tileMatrix, xyzData.tile);
|
|
122
|
+
req.set('location', latLon);
|
|
123
|
+
|
|
124
|
+
return xyzData;
|
|
125
|
+
},
|
|
126
|
+
};
|
package/src/wmts.capability.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Config, ConfigImagery, ConfigLayer, ConfigTileSet, standardizeLayerName } from '@basemaps/config';
|
|
2
|
+
import { Bounds, GoogleTms, ImageFormat, TileMatrixSet, WmtsProvider } from '@basemaps/geo';
|
|
2
3
|
import { Projection, V, VNodeElement } from '@basemaps/shared';
|
|
3
4
|
import { ImageFormatOrder } from '@basemaps/tiler';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
5
|
+
import { BoundingBox } from '@cogeotiff/core';
|
|
6
|
+
import { BBox } from '@linzjs/geojson';
|
|
6
7
|
|
|
7
8
|
const CapabilitiesAttrs = {
|
|
8
9
|
xmlns: 'http://www.opengis.net/wmts/1.0',
|
|
@@ -15,77 +16,115 @@ const CapabilitiesAttrs = {
|
|
|
15
16
|
version: '1.0.0',
|
|
16
17
|
};
|
|
17
18
|
|
|
18
|
-
function wgs84Extent(
|
|
19
|
-
return Projection.get(
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Default the tile matrix id to the projection of the TileMatrixSet
|
|
24
|
-
*/
|
|
25
|
-
export function getTileMatrixId(tileMatrix: TileMatrixSet): string {
|
|
26
|
-
// TODO this should really change everything to identifier
|
|
27
|
-
if (tileMatrix.identifier === Nztm2000QuadTms.identifier) return Nztm2000QuadTms.identifier;
|
|
28
|
-
return tileMatrix.projection.toEpsgString();
|
|
19
|
+
function wgs84Extent(tileMatrix: TileMatrixSet, bbox: BoundingBox): BBox {
|
|
20
|
+
return Projection.get(tileMatrix).boundsToWgs84BoundingBox(bbox);
|
|
29
21
|
}
|
|
30
22
|
|
|
31
23
|
export interface WmtsCapabilitiesParams {
|
|
24
|
+
/** Base URL for tile server */
|
|
32
25
|
httpBase: string;
|
|
33
26
|
provider?: WmtsProvider;
|
|
34
|
-
|
|
27
|
+
/** Tileset to export into WMTS */
|
|
28
|
+
tileSet: ConfigTileSet;
|
|
29
|
+
/** List of tile matrixes to output */
|
|
30
|
+
tileMatrix: TileMatrixSet[];
|
|
31
|
+
/** Should WMTS Layers be created for each imagery set inside this tileSet */
|
|
32
|
+
isIndividualLayers: boolean;
|
|
33
|
+
/** All the imagery used by the tileSet and tileMatrixes */
|
|
34
|
+
imagery: Map<string, ConfigImagery>;
|
|
35
|
+
/** API key to append to all resource urls */
|
|
35
36
|
apiKey?: string;
|
|
36
|
-
formats
|
|
37
|
+
/** Limit the output to the following image formats other wise @see ImageFormatOrder */
|
|
38
|
+
formats?: ImageFormat[] | null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Number of decimal places to use in lat lng */
|
|
42
|
+
const LngLatPrecision = 6;
|
|
43
|
+
const MeterPrecision = 4;
|
|
44
|
+
|
|
45
|
+
function formatCoords(x: number, precision: number): string {
|
|
46
|
+
return Number(x.toFixed(precision)).toString();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Format a bounding box XY as `${x} ${y}` while restricting to precision decimal places */
|
|
50
|
+
function formatBbox(x: number, y: number, precision: number): string {
|
|
51
|
+
return `${formatCoords(x, precision)} ${formatCoords(y, precision)}`;
|
|
37
52
|
}
|
|
38
53
|
|
|
39
54
|
export class WmtsCapabilities {
|
|
40
55
|
httpBase: string;
|
|
41
56
|
provider?: WmtsProvider;
|
|
42
|
-
|
|
43
|
-
layers: Map<string, TileSetRaster[]> = new Map();
|
|
44
|
-
|
|
57
|
+
tileSet: ConfigTileSet;
|
|
45
58
|
apiKey?: string;
|
|
46
59
|
tileMatrixSets = new Map<string, TileMatrixSet>();
|
|
60
|
+
imagery: Map<string, ConfigImagery>;
|
|
47
61
|
formats: ImageFormat[];
|
|
62
|
+
isIndividualLayers = false;
|
|
48
63
|
|
|
49
64
|
constructor(params: WmtsCapabilitiesParams) {
|
|
50
65
|
this.httpBase = params.httpBase;
|
|
51
66
|
this.provider = params.provider;
|
|
67
|
+
this.tileSet = params.tileSet;
|
|
68
|
+
this.isIndividualLayers = params.isIndividualLayers;
|
|
69
|
+
for (const tms of params.tileMatrix) this.tileMatrixSets.set(tms.identifier, tms);
|
|
70
|
+
this.apiKey = params.apiKey;
|
|
71
|
+
this.formats = params.formats ?? ImageFormatOrder;
|
|
72
|
+
this.imagery = params.imagery;
|
|
73
|
+
}
|
|
52
74
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
75
|
+
async loadImagery(): Promise<void> {
|
|
76
|
+
const ids = new Set<string>();
|
|
77
|
+
for (const tms of this.tileMatrixSets.values()) {
|
|
78
|
+
for (const layer of this.tileSet.layers) {
|
|
79
|
+
const layerId = layer[tms.projection.code];
|
|
80
|
+
if (layerId != null) ids.add(layerId);
|
|
59
81
|
}
|
|
60
|
-
// TODO should a error be thrown here if the projection is invalid
|
|
61
|
-
existing.push(layer);
|
|
62
|
-
|
|
63
|
-
this.tileMatrixSets.set(layer.tileMatrix.identifier, layer.tileMatrix);
|
|
64
82
|
}
|
|
65
|
-
this.
|
|
66
|
-
this.formats = params.formats ?? ImageFormatOrder;
|
|
83
|
+
this.imagery = await Config.Imagery.getAll(ids);
|
|
67
84
|
}
|
|
68
85
|
|
|
69
|
-
buildWgs84BoundingBox(layers:
|
|
70
|
-
let bbox
|
|
71
|
-
|
|
72
|
-
|
|
86
|
+
buildWgs84BoundingBox(tms: TileMatrixSet, layers: Bounds[]): VNodeElement {
|
|
87
|
+
let bbox: BBox;
|
|
88
|
+
if (layers.length > 0) {
|
|
89
|
+
let bounds = layers[0];
|
|
90
|
+
for (let i = 1; i < layers.length; i++) {
|
|
91
|
+
bounds = bounds.union(layers[i]);
|
|
92
|
+
}
|
|
93
|
+
bbox = wgs84Extent(tms, bounds.toJson());
|
|
94
|
+
} else {
|
|
95
|
+
// No layers provided assume extent is the size of the tile matrix set :shrug: ?
|
|
96
|
+
bbox = wgs84Extent(tms, tms.extent);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// If east is less than west, then this has crossed the anti meridian, so cover the entire globe
|
|
100
|
+
if (bbox[2] < bbox[0]) {
|
|
101
|
+
bbox[0] = -180;
|
|
102
|
+
bbox[2] = 180;
|
|
73
103
|
}
|
|
74
104
|
|
|
75
|
-
return V(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
? [V('ows:LowerCorner', `-180 -90`), V('ows:UpperCorner', `180 90`)]
|
|
80
|
-
: [V('ows:LowerCorner', `${bbox[0]} ${bbox[1]}`), V('ows:UpperCorner', `${bbox[2]} ${bbox[3]}`)],
|
|
81
|
-
);
|
|
105
|
+
return V('ows:WGS84BoundingBox', { crs: 'urn:ogc:def:crs:OGC:2:84' }, [
|
|
106
|
+
V('ows:LowerCorner', formatBbox(bbox[0], bbox[1], LngLatPrecision)),
|
|
107
|
+
V('ows:UpperCorner', formatBbox(bbox[2], bbox[3], LngLatPrecision)),
|
|
108
|
+
]);
|
|
82
109
|
}
|
|
83
110
|
|
|
84
|
-
|
|
85
|
-
|
|
111
|
+
/** Combine all the bounds of the imagery inside the layers into a extent for the imagery set */
|
|
112
|
+
buildBoundingBoxFromImagery(tms: TileMatrixSet, layers: ConfigLayer[]): VNodeElement | null {
|
|
113
|
+
let bounds;
|
|
114
|
+
for (const layer of layers) {
|
|
115
|
+
const imgId = layer[tms.projection.code];
|
|
116
|
+
if (imgId == null) continue;
|
|
117
|
+
const img = this.imagery.get(imgId);
|
|
118
|
+
if (img == null) continue;
|
|
119
|
+
if (bounds == null) bounds = Bounds.fromJson(img.bounds);
|
|
120
|
+
else bounds = bounds.union(Bounds.fromJson(img.bounds));
|
|
121
|
+
}
|
|
122
|
+
if (bounds == null) return null;
|
|
123
|
+
|
|
124
|
+
const bbox = bounds.toBbox();
|
|
86
125
|
return V('ows:BoundingBox', { crs: tms.projection.toUrn() }, [
|
|
87
|
-
V('ows:LowerCorner',
|
|
88
|
-
V('ows:UpperCorner',
|
|
126
|
+
V('ows:LowerCorner', formatBbox(bbox[tms.indexX], bbox[tms.indexY], MeterPrecision)),
|
|
127
|
+
V('ows:UpperCorner', formatBbox(bbox[tms.indexX + 2], bbox[tms.indexY + 2], MeterPrecision)),
|
|
89
128
|
]);
|
|
90
129
|
}
|
|
91
130
|
|
|
@@ -124,13 +163,13 @@ export class WmtsCapabilities {
|
|
|
124
163
|
];
|
|
125
164
|
}
|
|
126
165
|
|
|
127
|
-
buildTileUrl(
|
|
166
|
+
buildTileUrl(tileSetId: string, suffix: string): string {
|
|
128
167
|
const apiSuffix = this.apiKey ? `?api=${this.apiKey}` : '';
|
|
129
168
|
return [
|
|
130
169
|
this.httpBase,
|
|
131
170
|
'v1',
|
|
132
171
|
'tiles',
|
|
133
|
-
|
|
172
|
+
tileSetId,
|
|
134
173
|
'{TileMatrixSet}',
|
|
135
174
|
'{TileMatrix}',
|
|
136
175
|
'{TileCol}',
|
|
@@ -138,37 +177,91 @@ export class WmtsCapabilities {
|
|
|
138
177
|
].join('/');
|
|
139
178
|
}
|
|
140
179
|
|
|
141
|
-
buildResourceUrl(
|
|
180
|
+
buildResourceUrl(tileSetId: string, suffix: string): VNodeElement {
|
|
142
181
|
return V('ResourceURL', {
|
|
143
182
|
format: 'image/' + suffix,
|
|
144
183
|
resourceType: 'tile',
|
|
145
|
-
template: this.buildTileUrl(
|
|
184
|
+
template: this.buildTileUrl(tileSetId, suffix),
|
|
146
185
|
});
|
|
147
186
|
}
|
|
148
187
|
|
|
149
|
-
|
|
150
|
-
const matrixSets = new Set<
|
|
188
|
+
buildLayerFromImagery(layer: ConfigLayer): VNodeElement | null {
|
|
189
|
+
const matrixSets = new Set<TileMatrixSet>();
|
|
151
190
|
const matrixSetNodes: VNodeElement[] = [];
|
|
152
|
-
for (const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
191
|
+
for (const tms of this.tileMatrixSets.values()) {
|
|
192
|
+
const imdIg = layer[tms.projection.code];
|
|
193
|
+
if (imdIg == null) continue;
|
|
194
|
+
const img = this.imagery.get(imdIg);
|
|
195
|
+
if (img == null) continue;
|
|
196
|
+
matrixSetNodes.push(V('TileMatrixSetLink', [V('TileMatrixSet', tms.identifier)]));
|
|
197
|
+
matrixSets.add(tms);
|
|
156
198
|
}
|
|
157
199
|
|
|
158
|
-
const
|
|
200
|
+
const layerNameId = standardizeLayerName(layer.name);
|
|
201
|
+
const matrixSetList = [...matrixSets.values()];
|
|
202
|
+
const firstMatrix = matrixSetList[0];
|
|
203
|
+
if (firstMatrix == null) return null;
|
|
204
|
+
const firstImg = this.imagery.get(layer[firstMatrix.projection.code] ?? '');
|
|
205
|
+
if (firstImg == null) return null;
|
|
206
|
+
|
|
159
207
|
return V('Layer', [
|
|
160
|
-
V('ows:Title',
|
|
161
|
-
V('ows:Abstract',
|
|
162
|
-
V('ows:Identifier',
|
|
163
|
-
|
|
164
|
-
|
|
208
|
+
V('ows:Title', layer.title ?? layerNameId),
|
|
209
|
+
V('ows:Abstract', ''),
|
|
210
|
+
V('ows:Identifier', layerNameId),
|
|
211
|
+
this.buildKeywords(firstImg),
|
|
212
|
+
...matrixSetList.map((tms) => {
|
|
213
|
+
return this.buildBoundingBoxFromImagery(tms, [layer]);
|
|
214
|
+
}),
|
|
215
|
+
this.buildWgs84BoundingBox(firstMatrix, [Bounds.fromJson(firstImg.bounds)]),
|
|
165
216
|
this.buildStyle(),
|
|
166
217
|
...this.formats.map((fmt) => V('Format', 'image/' + fmt)),
|
|
167
218
|
...matrixSetNodes,
|
|
168
|
-
...this.formats.map((fmt) => this.buildResourceUrl(
|
|
219
|
+
...this.formats.map((fmt) => this.buildResourceUrl(layerNameId, fmt)),
|
|
169
220
|
]);
|
|
170
221
|
}
|
|
171
222
|
|
|
223
|
+
buildLayer(layer: ConfigTileSet): VNodeElement {
|
|
224
|
+
const matrixSets = new Set<TileMatrixSet>();
|
|
225
|
+
const matrixSetNodes: VNodeElement[] = [];
|
|
226
|
+
for (const tms of this.tileMatrixSets.values()) {
|
|
227
|
+
if (layer.layers.find((f) => f[tms.projection.code] != null)) {
|
|
228
|
+
matrixSetNodes.push(V('TileMatrixSetLink', [V('TileMatrixSet', tms.identifier)]));
|
|
229
|
+
matrixSets.add(tms);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const layerNameId = standardizeLayerName(layer.name);
|
|
233
|
+
const matrixSetList = [...matrixSets.values()];
|
|
234
|
+
const firstMatrix = matrixSetList[0];
|
|
235
|
+
if (firstMatrix == null) throw new Error('No matrix sets found for layer ' + layer.name);
|
|
236
|
+
|
|
237
|
+
// Prefer using the web mercator tms for bounds
|
|
238
|
+
const webMercatorOrFirst = matrixSetList.find((f) => f.identifier === GoogleTms.identifier) ?? firstMatrix;
|
|
239
|
+
const bounds: Bounds[] = [];
|
|
240
|
+
for (const l of layer.layers) {
|
|
241
|
+
const img = this.imagery.get(l[webMercatorOrFirst.projection.code] ?? '');
|
|
242
|
+
if (img == null) continue;
|
|
243
|
+
bounds.push(Bounds.fromJson(img.bounds));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return V('Layer', [
|
|
247
|
+
V('ows:Title', layer.title ?? layerNameId),
|
|
248
|
+
V('ows:Abstract', layer.description ?? ''),
|
|
249
|
+
V('ows:Identifier', layerNameId),
|
|
250
|
+
this.buildKeywords(layer),
|
|
251
|
+
...[...matrixSets.values()].map((tms) => this.buildBoundingBoxFromImagery(tms, layer.layers)),
|
|
252
|
+
this.buildWgs84BoundingBox(webMercatorOrFirst, bounds),
|
|
253
|
+
this.buildStyle(),
|
|
254
|
+
...this.formats.map((fmt) => V('Format', 'image/' + fmt)),
|
|
255
|
+
...matrixSetNodes,
|
|
256
|
+
...this.formats.map((fmt) => this.buildResourceUrl(layerNameId, fmt)),
|
|
257
|
+
]);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
buildKeywords(tileSet: { category?: string }): VNodeElement {
|
|
261
|
+
if (tileSet.category == null) return V('ows:Keywords');
|
|
262
|
+
return V('ows:Keywords', [V('ows:Keyword', tileSet.category)]);
|
|
263
|
+
}
|
|
264
|
+
|
|
172
265
|
buildStyle(): VNodeElement {
|
|
173
266
|
return V('Style', { isDefault: 'true' }, [V('ows:Title', 'Default Style'), V('ows:Identifier', 'default')]);
|
|
174
267
|
}
|
|
@@ -177,7 +270,7 @@ export class WmtsCapabilities {
|
|
|
177
270
|
return V('TileMatrixSet', [
|
|
178
271
|
V('ows:Title', tms.def.title),
|
|
179
272
|
tms.def.abstract ? V('ows:Abstract', tms.def.abstract) : null,
|
|
180
|
-
V('ows:Identifier',
|
|
273
|
+
V('ows:Identifier', tms.identifier),
|
|
181
274
|
V('ows:SupportedCRS', tms.projection.toUrn()),
|
|
182
275
|
tms.def.wellKnownScaleSet ? V('WellKnownScaleSet', tms.def.wellKnownScaleSet) : null,
|
|
183
276
|
...tms.def.tileMatrix.map((c) => {
|
|
@@ -194,16 +287,25 @@ export class WmtsCapabilities {
|
|
|
194
287
|
]);
|
|
195
288
|
}
|
|
196
289
|
toVNode(): VNodeElement {
|
|
197
|
-
const layers: VNodeElement[] = [];
|
|
198
|
-
|
|
199
|
-
|
|
290
|
+
const layers: (VNodeElement | null)[] = [];
|
|
291
|
+
layers.push(this.buildLayer(this.tileSet));
|
|
292
|
+
|
|
293
|
+
if (this.isIndividualLayers) {
|
|
294
|
+
const layerByName = new Map<string, ConfigLayer>();
|
|
295
|
+
// Dedupe the layers by unique name
|
|
296
|
+
for (const img of this.tileSet.layers) layerByName.set(standardizeLayerName(img.name), img);
|
|
297
|
+
const orderedLayers = Array.from(layerByName.values()).sort((a, b) =>
|
|
298
|
+
(a.title ?? a.name).localeCompare(b.title ?? b.name),
|
|
299
|
+
);
|
|
300
|
+
for (const img of orderedLayers) layers.push(this.buildLayerFromImagery(img));
|
|
200
301
|
}
|
|
302
|
+
|
|
201
303
|
for (const tms of this.tileMatrixSets.values()) layers.push(this.buildTileMatrixSet(tms));
|
|
202
304
|
|
|
203
305
|
return V('Capabilities', CapabilitiesAttrs, [...this.buildProvider(), V('Contents', layers)]);
|
|
204
306
|
}
|
|
205
307
|
|
|
206
308
|
toXml(): string {
|
|
207
|
-
return '<?xml version="1.0"?>\n' + this.toVNode().toString();
|
|
309
|
+
return '<?xml version="1.0" encoding="utf-8"?>\n' + this.toVNode().toString();
|
|
208
310
|
}
|
|
209
311
|
}
|