@basemaps/lambda-tiler 6.34.0 → 6.36.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 +40 -0
- package/build/__tests__/config.data.d.ts +2 -1
- package/build/__tests__/config.data.d.ts.map +1 -1
- package/build/__tests__/config.data.js +14 -1
- package/build/__tests__/config.data.js.map +1 -1
- package/build/__tests__/tile.style.json.test.js +17 -3
- package/build/__tests__/tile.style.json.test.js.map +1 -1
- package/build/__tests__/wmts.capability.test.js +17 -0
- package/build/__tests__/wmts.capability.test.js.map +1 -1
- package/build/arcgis/__tests__/arcgis.style.json.test.js +18 -15
- package/build/arcgis/__tests__/arcgis.style.json.test.js.map +1 -1
- package/build/arcgis/__tests__/vector.tiler.server.test.js +14 -7
- package/build/arcgis/__tests__/vector.tiler.server.test.js.map +1 -1
- package/build/arcgis/arcgis.style.json.d.ts.map +1 -1
- package/build/arcgis/arcgis.style.json.js +6 -4
- package/build/arcgis/arcgis.style.json.js.map +1 -1
- package/build/arcgis/vector.tile.server.d.ts.map +1 -1
- package/build/arcgis/vector.tile.server.js +4 -2
- package/build/arcgis/vector.tile.server.js.map +1 -1
- package/build/index.d.ts +1 -3
- package/build/index.d.ts.map +1 -1
- package/build/index.js +7 -8
- package/build/index.js.map +1 -1
- package/build/routes/__tests__/attribution.test.js +3 -2
- package/build/routes/__tests__/attribution.test.js.map +1 -1
- package/build/routes/__tests__/fonts.test.js +42 -9
- package/build/routes/__tests__/fonts.test.js.map +1 -1
- package/build/routes/__tests__/health.test.js +4 -2
- package/build/routes/__tests__/health.test.js.map +1 -1
- package/build/routes/__tests__/sprites.test.js +5 -11
- package/build/routes/__tests__/sprites.test.js.map +1 -1
- package/build/routes/__tests__/tile.json.test.js +24 -2
- package/build/routes/__tests__/tile.json.test.js.map +1 -1
- package/build/routes/__tests__/tile.style.json.test.js +58 -7
- package/build/routes/__tests__/tile.style.json.test.js.map +1 -1
- package/build/routes/__tests__/wmts.test.js +17 -14
- package/build/routes/__tests__/wmts.test.js.map +1 -1
- package/build/routes/__tests__/xyz.test.js +4 -2
- package/build/routes/__tests__/xyz.test.js.map +1 -1
- package/build/routes/attribution.d.ts.map +1 -1
- package/build/routes/attribution.js +20 -10
- package/build/routes/attribution.js.map +1 -1
- package/build/routes/config.d.ts +22 -0
- package/build/routes/config.d.ts.map +1 -0
- package/build/routes/config.js +63 -0
- package/build/routes/config.js.map +1 -0
- package/build/routes/fonts.js +1 -1
- package/build/routes/fonts.js.map +1 -1
- package/build/routes/health.d.ts.map +1 -1
- package/build/routes/health.js +3 -2
- package/build/routes/health.js.map +1 -1
- package/build/routes/imagery.d.ts.map +1 -1
- package/build/routes/imagery.js +3 -2
- package/build/routes/imagery.js.map +1 -1
- package/build/routes/tile.json.d.ts.map +1 -1
- package/build/routes/tile.json.js +7 -4
- package/build/routes/tile.json.js.map +1 -1
- package/build/routes/tile.style.json.d.ts +4 -3
- package/build/routes/tile.style.json.d.ts.map +1 -1
- package/build/routes/tile.style.json.js +53 -12
- package/build/routes/tile.style.json.js.map +1 -1
- package/build/routes/tile.wmts.d.ts.map +1 -1
- package/build/routes/tile.wmts.js +7 -4
- package/build/routes/tile.wmts.js.map +1 -1
- package/build/routes/tile.xyz.d.ts.map +1 -1
- package/build/routes/tile.xyz.js +4 -2
- package/build/routes/tile.xyz.js.map +1 -1
- package/build/routes/tile.xyz.raster.d.ts.map +1 -1
- package/build/routes/tile.xyz.raster.js +4 -2
- package/build/routes/tile.xyz.raster.js.map +1 -1
- package/build/util/__test__/config.loader.test.d.ts +2 -0
- package/build/util/__test__/config.loader.test.d.ts.map +1 -0
- package/build/util/__test__/config.loader.test.js +79 -0
- package/build/util/__test__/config.loader.test.js.map +1 -0
- package/build/util/assets.provider.d.ts +1 -4
- package/build/util/assets.provider.d.ts.map +1 -1
- package/build/util/assets.provider.js +19 -10
- package/build/util/assets.provider.js.map +1 -1
- package/build/util/config.cache.d.ts +3 -3
- package/build/util/config.cache.d.ts.map +1 -1
- package/build/util/config.cache.js +14 -15
- package/build/util/config.cache.js.map +1 -1
- package/build/util/config.loader.d.ts +10 -0
- package/build/util/config.loader.d.ts.map +1 -0
- package/build/util/config.loader.js +54 -0
- package/build/util/config.loader.js.map +1 -0
- package/build/wmts.capability.d.ts +3 -1
- package/build/wmts.capability.d.ts.map +1 -1
- package/build/wmts.capability.js +5 -15
- package/build/wmts.capability.js.map +1 -1
- package/dist/index.js +63 -63
- package/dist/node_modules/.package-lock.json +13 -10
- package/dist/node_modules/minimist/.eslintrc +54 -0
- package/dist/node_modules/minimist/.github/FUNDING.yml +12 -0
- package/dist/node_modules/minimist/.nycrc +14 -0
- package/dist/node_modules/minimist/CHANGELOG.md +212 -0
- package/dist/node_modules/minimist/{readme.markdown → README.md} +20 -1
- package/dist/node_modules/minimist/package.json +39 -9
- package/dist/node_modules/node-abi/abi_registry.json +8 -1
- package/dist/node_modules/node-abi/package.json +1 -1
- package/dist/node_modules/semver/classes/range.js +3 -0
- package/dist/node_modules/semver/index.js +81 -41
- package/dist/node_modules/semver/package.json +21 -10
- package/dist/package-lock.json +23 -20
- package/dist/package.json +1 -1
- package/package.json +8 -9
- package/src/__tests__/config.data.ts +25 -1
- package/src/__tests__/tile.style.json.test.ts +19 -3
- package/src/__tests__/wmts.capability.test.ts +21 -0
- package/src/arcgis/__tests__/arcgis.style.json.test.ts +21 -17
- package/src/arcgis/__tests__/vector.tiler.server.test.ts +17 -8
- package/src/arcgis/arcgis.style.json.ts +6 -4
- package/src/arcgis/vector.tile.server.ts +5 -2
- package/src/index.ts +9 -10
- package/src/routes/__tests__/attribution.test.ts +4 -2
- package/src/routes/__tests__/fonts.test.ts +48 -11
- package/src/routes/__tests__/health.test.ts +4 -2
- package/src/routes/__tests__/sprites.test.ts +6 -11
- package/src/routes/__tests__/tile.json.test.ts +30 -2
- package/src/routes/__tests__/tile.style.json.test.ts +68 -9
- package/src/routes/__tests__/wmts.test.ts +23 -17
- package/src/routes/__tests__/xyz.test.ts +4 -2
- package/src/routes/attribution.ts +23 -8
- package/src/routes/config.ts +83 -0
- package/src/routes/fonts.ts +1 -1
- package/src/routes/health.ts +4 -2
- package/src/routes/imagery.ts +3 -2
- package/src/routes/tile.json.ts +10 -4
- package/src/routes/tile.style.json.ts +58 -12
- package/src/routes/tile.wmts.ts +9 -4
- package/src/routes/tile.xyz.raster.ts +4 -2
- package/src/routes/tile.xyz.ts +5 -2
- package/src/util/__test__/config.loader.test.ts +116 -0
- package/src/util/assets.provider.ts +8 -12
- package/src/util/config.cache.ts +18 -18
- package/src/util/config.loader.ts +56 -0
- package/src/wmts.capability.ts +9 -15
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/node_modules/minimist/.travis.yml +0 -8
package/src/routes/tile.json.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { GoogleTms, TileJson, TileMatrixSet } from '@basemaps/geo';
|
|
2
|
-
import {
|
|
2
|
+
import { Env, toQueryString } from '@basemaps/shared';
|
|
3
3
|
import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
|
|
4
|
+
import { ConfigLoader } from '../util/config.loader.js';
|
|
4
5
|
import { NotFound } from '../util/response.js';
|
|
5
6
|
import { Validate } from '../util/validate.js';
|
|
6
7
|
|
|
@@ -17,8 +18,10 @@ export async function tileJsonGet(req: LambdaHttpRequest<TileJsonGet>): Promise<
|
|
|
17
18
|
|
|
18
19
|
const apiKey = Validate.apiKey(req);
|
|
19
20
|
|
|
21
|
+
const config = await ConfigLoader.load(req);
|
|
22
|
+
|
|
20
23
|
req.timer.start('tileset:load');
|
|
21
|
-
const tileSet = await
|
|
24
|
+
const tileSet = await config.TileSet.get(config.TileSet.id(req.params.tileSet));
|
|
22
25
|
req.timer.end('tileset:load');
|
|
23
26
|
if (tileSet == null) return NotFound();
|
|
24
27
|
|
|
@@ -26,9 +29,12 @@ export async function tileJsonGet(req: LambdaHttpRequest<TileJsonGet>): Promise<
|
|
|
26
29
|
|
|
27
30
|
const host = Env.get(Env.PublicUrlBase) ?? '';
|
|
28
31
|
|
|
32
|
+
const configLocation = ConfigLoader.extract(req);
|
|
33
|
+
|
|
34
|
+
const query = toQueryString({ api: apiKey, config: configLocation });
|
|
35
|
+
|
|
29
36
|
const tileUrl =
|
|
30
|
-
[host, 'v1', 'tiles', tileSet.name, tileMatrix.identifier, '{z}', '{x}', '{y}'].join('/') +
|
|
31
|
-
`.${format[0]}?api=${apiKey}`;
|
|
37
|
+
[host, 'v1', 'tiles', tileSet.name, tileMatrix.identifier, '{z}', '{x}', '{y}'].join('/') + `.${format[0]}${query}`;
|
|
32
38
|
|
|
33
39
|
const tileJson: TileJson = { tiles: [tileUrl], tilejson: '3.0.0' };
|
|
34
40
|
const maxZoom = TileMatrixSet.convertZoomLevel(tileSet.maxZoom ?? 30, GoogleTms, tileMatrix, true);
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { Sources, StyleJson } from '@basemaps/config';
|
|
2
|
-
import {
|
|
1
|
+
import { ConfigTileSetRaster, Sources, StyleJson, TileSetType } from '@basemaps/config';
|
|
2
|
+
import { Env, toQueryString } from '@basemaps/shared';
|
|
3
3
|
import { fsa } from '@chunkd/fs';
|
|
4
4
|
import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
|
|
5
5
|
import { URL } from 'url';
|
|
6
6
|
import { NotFound, NotModified } from '../util/response.js';
|
|
7
7
|
import { Validate } from '../util/validate.js';
|
|
8
8
|
import { Etag } from '../util/etag.js';
|
|
9
|
+
import { ConfigLoader } from '../util/config.loader.js';
|
|
10
|
+
import { GoogleTms, ImageFormat, TileMatrixSets } from '@basemaps/geo';
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Convert relative URLS into a full hostname url
|
|
@@ -13,12 +15,13 @@ import { Etag } from '../util/etag.js';
|
|
|
13
15
|
* @param apiKey ApiKey to append with ?api= if required
|
|
14
16
|
* @returns Updated Url or empty string if url is empty
|
|
15
17
|
*/
|
|
16
|
-
export function convertRelativeUrl(url?: string, apiKey?: string): string {
|
|
18
|
+
export function convertRelativeUrl(url?: string, apiKey?: string, config?: string | null): string {
|
|
17
19
|
if (url == null) return '';
|
|
18
20
|
const host = Env.get(Env.PublicUrlBase) ?? '';
|
|
19
21
|
if (!url.startsWith('/')) return url; // Not relative ignore
|
|
20
22
|
const fullUrl = new URL(fsa.join(host, url));
|
|
21
23
|
if (apiKey) fullUrl.searchParams.set('api', apiKey);
|
|
24
|
+
if (config) fullUrl.searchParams.set('config', config);
|
|
22
25
|
return fullUrl.toString().replace(/%7B/g, '{').replace(/%7D/g, '}');
|
|
23
26
|
}
|
|
24
27
|
|
|
@@ -28,14 +31,14 @@ export function convertRelativeUrl(url?: string, apiKey?: string): string {
|
|
|
28
31
|
* @param apiKey api key to inject
|
|
29
32
|
* @returns new stylejson
|
|
30
33
|
*/
|
|
31
|
-
export function convertStyleJson(style: StyleJson, apiKey: string): StyleJson {
|
|
34
|
+
export function convertStyleJson(style: StyleJson, apiKey: string, config: string | null): StyleJson {
|
|
32
35
|
const sources: Sources = JSON.parse(JSON.stringify(style.sources));
|
|
33
36
|
for (const [key, value] of Object.entries(sources)) {
|
|
34
37
|
if (value.type === 'vector') {
|
|
35
|
-
value.url = convertRelativeUrl(value.url, apiKey);
|
|
38
|
+
value.url = convertRelativeUrl(value.url, apiKey, config);
|
|
36
39
|
} else if (value.type === 'raster' && Array.isArray(value.tiles)) {
|
|
37
40
|
for (let i = 0; i < value.tiles.length; i++) {
|
|
38
|
-
value.tiles[i] = convertRelativeUrl(value.tiles[i], apiKey);
|
|
41
|
+
value.tiles[i] = convertRelativeUrl(value.tiles[i], apiKey, config);
|
|
39
42
|
}
|
|
40
43
|
}
|
|
41
44
|
sources[key] = value;
|
|
@@ -48,8 +51,8 @@ export function convertStyleJson(style: StyleJson, apiKey: string): StyleJson {
|
|
|
48
51
|
sources,
|
|
49
52
|
layers: style.layers,
|
|
50
53
|
metadata: style.metadata ?? {},
|
|
51
|
-
glyphs: convertRelativeUrl(style.glyphs),
|
|
52
|
-
sprite: convertRelativeUrl(style.sprite),
|
|
54
|
+
glyphs: convertRelativeUrl(style.glyphs, undefined, config),
|
|
55
|
+
sprite: convertRelativeUrl(style.sprite, undefined, config),
|
|
53
56
|
} as StyleJson;
|
|
54
57
|
}
|
|
55
58
|
|
|
@@ -59,17 +62,60 @@ export interface StyleGet {
|
|
|
59
62
|
};
|
|
60
63
|
}
|
|
61
64
|
|
|
65
|
+
export async function tileSetToStyle(
|
|
66
|
+
req: LambdaHttpRequest<StyleGet>,
|
|
67
|
+
tileSet: ConfigTileSetRaster,
|
|
68
|
+
apiKey: string,
|
|
69
|
+
): Promise<LambdaHttpResponse> {
|
|
70
|
+
const tileMatrix = TileMatrixSets.find(req.query.get('tileMatrix') ?? GoogleTms.identifier);
|
|
71
|
+
if (tileMatrix == null) return new LambdaHttpResponse(400, 'Invalid tile matrix');
|
|
72
|
+
const [tileFormat] = Validate.getRequestedFormats(req) ?? [ImageFormat.Webp];
|
|
73
|
+
if (tileFormat == null) return new LambdaHttpResponse(400, 'Invalid image format');
|
|
74
|
+
|
|
75
|
+
const configLocation = ConfigLoader.extract(req);
|
|
76
|
+
const query = toQueryString({ config: configLocation, api: apiKey });
|
|
77
|
+
|
|
78
|
+
const tileUrl = fsa.join(
|
|
79
|
+
Env.get(Env.PublicUrlBase) ?? '',
|
|
80
|
+
`/v1/tiles/${tileSet.name}/${tileMatrix.identifier}/{z}/{x}/{y}.${tileFormat}${query}`,
|
|
81
|
+
);
|
|
82
|
+
const styleId = `basemaps-${tileSet.name}`;
|
|
83
|
+
const style = {
|
|
84
|
+
version: 8,
|
|
85
|
+
sources: { [styleId]: { type: 'raster', tiles: [tileUrl], tileSize: 256 } },
|
|
86
|
+
layers: [{ id: styleId, type: 'raster', source: styleId }],
|
|
87
|
+
};
|
|
88
|
+
const data = Buffer.from(JSON.stringify(style));
|
|
89
|
+
|
|
90
|
+
const cacheKey = Etag.key(data);
|
|
91
|
+
if (Etag.isNotModified(req, cacheKey)) return NotModified();
|
|
92
|
+
|
|
93
|
+
const response = new LambdaHttpResponse(200, 'ok');
|
|
94
|
+
response.header(HttpHeader.ETag, cacheKey);
|
|
95
|
+
response.header(HttpHeader.CacheControl, 'no-store');
|
|
96
|
+
response.buffer(data, 'application/json');
|
|
97
|
+
req.set('bytes', data.byteLength);
|
|
98
|
+
return response;
|
|
99
|
+
}
|
|
100
|
+
|
|
62
101
|
export async function styleJsonGet(req: LambdaHttpRequest<StyleGet>): Promise<LambdaHttpResponse> {
|
|
63
102
|
const apiKey = Validate.apiKey(req);
|
|
64
103
|
const styleName = req.params.styleName;
|
|
65
104
|
|
|
66
105
|
// Get style Config from db
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
|
|
106
|
+
const config = await ConfigLoader.load(req);
|
|
107
|
+
const dbId = config.Style.id(styleName);
|
|
108
|
+
const styleConfig = await config.Style.get(dbId);
|
|
109
|
+
if (styleConfig == null) {
|
|
110
|
+
// Were we given a tileset name instead, generated
|
|
111
|
+
const tileSet = await config.TileSet.get(config.TileSet.id(styleName));
|
|
112
|
+
if (tileSet == null) return NotFound();
|
|
113
|
+
if (tileSet.type !== TileSetType.Raster) return NotFound();
|
|
114
|
+
return tileSetToStyle(req, tileSet, apiKey);
|
|
115
|
+
}
|
|
70
116
|
|
|
71
117
|
// Prepare sources and add linz source
|
|
72
|
-
const style = convertStyleJson(styleConfig.style, apiKey);
|
|
118
|
+
const style = convertStyleJson(styleConfig.style, apiKey, ConfigLoader.extract(req));
|
|
73
119
|
const data = Buffer.from(JSON.stringify(style));
|
|
74
120
|
|
|
75
121
|
const cacheKey = Etag.key(data);
|
package/src/routes/tile.wmts.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getAllImagery, TileSetType } from '@basemaps/config';
|
|
2
2
|
import { GoogleTms, Nztm2000QuadTms, TileMatrixSet } from '@basemaps/geo';
|
|
3
3
|
import { Env } from '@basemaps/shared';
|
|
4
4
|
import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
|
|
@@ -7,6 +7,7 @@ import { NotFound, NotModified } from '../util/response.js';
|
|
|
7
7
|
import { Validate } from '../util/validate.js';
|
|
8
8
|
import { WmtsCapabilities } from '../wmts.capability.js';
|
|
9
9
|
import { Etag } from '../util/etag.js';
|
|
10
|
+
import { ConfigLoader } from '../util/config.loader.js';
|
|
10
11
|
|
|
11
12
|
export interface WmtsCapabilitiesGet {
|
|
12
13
|
Params: {
|
|
@@ -36,15 +37,18 @@ export async function wmtsCapabilitiesGet(req: LambdaHttpRequest<WmtsCapabilitie
|
|
|
36
37
|
|
|
37
38
|
const host = Env.get(Env.PublicUrlBase) ?? '';
|
|
38
39
|
|
|
40
|
+
const config = await ConfigLoader.load(req);
|
|
41
|
+
|
|
39
42
|
req.timer.start('tileset:load');
|
|
40
|
-
const tileSet = await
|
|
43
|
+
const tileSet = await config.TileSet.get(config.TileSet.id(tileSetName ?? 'aerial'));
|
|
41
44
|
req.timer.end('tileset:load');
|
|
42
45
|
if (tileSet == null || tileSet.type !== TileSetType.Raster) return NotFound();
|
|
43
46
|
|
|
44
|
-
const provider = await
|
|
47
|
+
const provider = await config.Provider.get(config.Provider.id('linz'));
|
|
45
48
|
|
|
46
49
|
req.timer.start('imagery:load');
|
|
47
|
-
const imagery = await
|
|
50
|
+
const imagery = await getAllImagery(
|
|
51
|
+
config,
|
|
48
52
|
tileSet.layers,
|
|
49
53
|
tileMatrix.map((tms) => tms.projection),
|
|
50
54
|
);
|
|
@@ -58,6 +62,7 @@ export async function wmtsCapabilitiesGet(req: LambdaHttpRequest<WmtsCapabilitie
|
|
|
58
62
|
isIndividualLayers: req.params.tileMatrix == null,
|
|
59
63
|
imagery,
|
|
60
64
|
apiKey,
|
|
65
|
+
config: ConfigLoader.extract(req),
|
|
61
66
|
formats: Validate.getRequestedFormats(req),
|
|
62
67
|
}).toXml();
|
|
63
68
|
if (xml == null) return NotFound();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getAllImagery, ConfigTileSetRaster } from '@basemaps/config';
|
|
2
2
|
import { Bounds, Epsg, TileMatrixSet, TileMatrixSets, VectorFormat } from '@basemaps/geo';
|
|
3
3
|
import { Env, fsa } from '@basemaps/shared';
|
|
4
4
|
import { Tiler } from '@basemaps/tiler';
|
|
@@ -6,6 +6,7 @@ import { TileMakerSharp } from '@basemaps/tiler-sharp';
|
|
|
6
6
|
import { CogTiff } from '@cogeotiff/core';
|
|
7
7
|
import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
|
|
8
8
|
import pLimit from 'p-limit';
|
|
9
|
+
import { ConfigLoader } from '../util/config.loader.js';
|
|
9
10
|
import { Etag } from '../util/etag.js';
|
|
10
11
|
import { NotFound, NotModified } from '../util/response.js';
|
|
11
12
|
import { CoSources } from '../util/source.cache.js';
|
|
@@ -26,7 +27,8 @@ const DefaultBackground = { r: 0, g: 0, b: 0, alpha: 0 };
|
|
|
26
27
|
|
|
27
28
|
export const TileXyzRaster = {
|
|
28
29
|
async getTiffsForTile(req: LambdaHttpRequest, tileSet: ConfigTileSetRaster, xyz: TileXyz): Promise<string[]> {
|
|
29
|
-
const
|
|
30
|
+
const config = await ConfigLoader.load(req);
|
|
31
|
+
const imagery = await getAllImagery(config, tileSet.layers, [xyz.tileMatrix.projection]);
|
|
30
32
|
|
|
31
33
|
const output: string[] = [];
|
|
32
34
|
const tileBounds = xyz.tileMatrix.tileToSourceBounds(xyz.tile);
|
package/src/routes/tile.xyz.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TileSetType } from '@basemaps/config';
|
|
2
2
|
import { LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
|
|
3
|
+
import { ConfigLoader } from '../util/config.loader.js';
|
|
3
4
|
import { NotFound } from '../util/response.js';
|
|
4
5
|
import { Validate } from '../util/validate.js';
|
|
5
6
|
import { TileXyzRaster } from './tile.xyz.raster.js';
|
|
@@ -29,8 +30,10 @@ export interface TileXyzGet {
|
|
|
29
30
|
export async function tileXyzGet(req: LambdaHttpRequest<TileXyzGet>): Promise<LambdaHttpResponse> {
|
|
30
31
|
const xyzData = Validate.xyz(req);
|
|
31
32
|
|
|
33
|
+
const config = await ConfigLoader.load(req);
|
|
34
|
+
|
|
32
35
|
req.timer.start('tileset:load');
|
|
33
|
-
const tileSet = await
|
|
36
|
+
const tileSet = await config.TileSet.get(config.TileSet.id(xyzData.tileSet));
|
|
34
37
|
req.timer.end('tileset:load');
|
|
35
38
|
if (tileSet == null) return NotFound();
|
|
36
39
|
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { base58, ConfigProviderMemory } from '@basemaps/config';
|
|
2
|
+
import { fsa } from '@basemaps/shared';
|
|
3
|
+
import { LambdaHttpResponse } from '@linzjs/lambda';
|
|
4
|
+
import o from 'ospec';
|
|
5
|
+
import { createSandbox } from 'sinon';
|
|
6
|
+
import { FsMemory } from '../../routes/__tests__/memory.fs.js';
|
|
7
|
+
import { FakeData } from '../../__tests__/config.data.js';
|
|
8
|
+
import { Api, mockRequest, mockUrlRequest } from '../../__tests__/xyz.util.js';
|
|
9
|
+
import { CachedConfig } from '../config.cache.js';
|
|
10
|
+
import { ConfigLoader } from '../config.loader.js';
|
|
11
|
+
|
|
12
|
+
o.spec('ConfigLoader', () => {
|
|
13
|
+
const memory = new FsMemory();
|
|
14
|
+
const config = new ConfigProviderMemory();
|
|
15
|
+
const sandbox = createSandbox();
|
|
16
|
+
|
|
17
|
+
o.before(() => {
|
|
18
|
+
fsa.register('memory://', memory);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
o.beforeEach(() => {
|
|
22
|
+
sandbox.stub(ConfigLoader, 'getDefaultConfig').resolves(config);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
o.afterEach(() => {
|
|
26
|
+
sandbox.restore();
|
|
27
|
+
config.objects.clear();
|
|
28
|
+
memory.files.clear();
|
|
29
|
+
CachedConfig.cache.clear();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
o('should return default config', async () => {
|
|
33
|
+
const provider = await ConfigLoader.load(mockRequest('/v1/fonts.json'));
|
|
34
|
+
o(provider).deepEquals(config);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
o('should Not working with wrong config url', async () => {
|
|
38
|
+
const error = await ConfigLoader.load(
|
|
39
|
+
mockUrlRequest('/v1/tiles/🦄 🌈/NZTM2000Quad/tile.json', `?config=notapath`, Api.header),
|
|
40
|
+
)
|
|
41
|
+
.then(() => null)
|
|
42
|
+
.catch((e) => e);
|
|
43
|
+
|
|
44
|
+
o(error instanceof LambdaHttpResponse).equals(true);
|
|
45
|
+
o((error as LambdaHttpResponse).status).equals(400);
|
|
46
|
+
o((error as LambdaHttpResponse).statusDescription).equals('Invalid config location');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
o('should Not working with wrong protocol', async () => {
|
|
50
|
+
const error = await ConfigLoader.load(
|
|
51
|
+
mockUrlRequest('/v1/tiles/🦄 🌈/NZTM2000Quad/tile.json', `?config=memory1://linz-basemaps/config`, Api.header),
|
|
52
|
+
)
|
|
53
|
+
.then(() => null)
|
|
54
|
+
.catch((e) => e);
|
|
55
|
+
|
|
56
|
+
o(error instanceof LambdaHttpResponse).equals(true);
|
|
57
|
+
o((error as LambdaHttpResponse).status).equals(400);
|
|
58
|
+
o((error as LambdaHttpResponse).statusDescription).equals('Invalid configuration location protocol:memory1');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
o('should Not working with wrong s3 bucket', async () => {
|
|
62
|
+
const error = await ConfigLoader.load(
|
|
63
|
+
mockUrlRequest('/v1/tiles/🦄 🌈/NZTM2000Quad/tile.json', `?config=s3://wrong-bucket/config`, Api.header),
|
|
64
|
+
)
|
|
65
|
+
.then(() => null)
|
|
66
|
+
.catch((e) => e);
|
|
67
|
+
|
|
68
|
+
o(error instanceof LambdaHttpResponse).equals(true);
|
|
69
|
+
o((error as LambdaHttpResponse).status).equals(400);
|
|
70
|
+
o((error as LambdaHttpResponse).statusDescription).equals(
|
|
71
|
+
'Bucket: "wrong-bucket" is not a allowed bucket location',
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const location = 'memory://linz-basemaps/config.json';
|
|
76
|
+
|
|
77
|
+
o('should Not working with no file in the path', async () => {
|
|
78
|
+
const error = await ConfigLoader.load(
|
|
79
|
+
mockUrlRequest('/v1/tiles/🦄 🌈/NZTM2000Quad/tile.json', `?config=${location}`, Api.header),
|
|
80
|
+
)
|
|
81
|
+
.then(() => null)
|
|
82
|
+
.catch((e) => e);
|
|
83
|
+
|
|
84
|
+
o(error instanceof LambdaHttpResponse).equals(true);
|
|
85
|
+
o((error as LambdaHttpResponse).status).equals(404);
|
|
86
|
+
o((error as LambdaHttpResponse).statusDescription).equals(`Config not found at ${location}`);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
o('should get expected config file', async () => {
|
|
90
|
+
const expectedConfig = new ConfigProviderMemory();
|
|
91
|
+
expectedConfig.put(FakeData.tileSetRaster('aerial'));
|
|
92
|
+
await fsa.write(location, Buffer.from(JSON.stringify(expectedConfig.toJson())));
|
|
93
|
+
|
|
94
|
+
const provider = await ConfigLoader.load(
|
|
95
|
+
mockUrlRequest('/v1/tiles/🦄 🌈/NZTM2000Quad/tile.json', `?config=${location}`, Api.header),
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
o(await provider.Imagery.get('aerial')).deepEquals(await expectedConfig.Imagery.get('aerial'));
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
o('should get expected config file with base58 location', async () => {
|
|
102
|
+
const expectedConfig = new ConfigProviderMemory();
|
|
103
|
+
expectedConfig.put(FakeData.tileSetVector('topographic'));
|
|
104
|
+
await fsa.write(location, Buffer.from(JSON.stringify(expectedConfig.toJson())));
|
|
105
|
+
|
|
106
|
+
const provider = await ConfigLoader.load(
|
|
107
|
+
mockUrlRequest(
|
|
108
|
+
'/v1/tiles/🦄 🌈/NZTM2000Quad/tile.json',
|
|
109
|
+
`?config=${base58.encode(Buffer.from(location))}`,
|
|
110
|
+
Api.header,
|
|
111
|
+
),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
o(await provider.Imagery.get('topographic')).deepEquals(await expectedConfig.Imagery.get('topographic'));
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { fsa } from '@chunkd/fs';
|
|
2
2
|
import { LambdaHttpResponse, LambdaHttpRequest, HttpHeader } from '@linzjs/lambda';
|
|
3
|
+
import { ConfigLoader } from './config.loader.js';
|
|
3
4
|
import { isGzip } from './cotar.serve.js';
|
|
4
5
|
import { Etag } from './etag.js';
|
|
5
6
|
import { NotFound, NotModified } from './response.js';
|
|
@@ -15,21 +16,13 @@ export class AssetProvider {
|
|
|
15
16
|
* s3://linz-basemaps/assets/assets-b4ff211a.tar.co # Remote Cotar
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
path: string | undefined;
|
|
20
|
-
|
|
21
|
-
set(path?: string): void {
|
|
22
|
-
this.path = path;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async get(fileName: string): Promise<Buffer | null> {
|
|
26
|
-
if (this.path == null) return null;
|
|
19
|
+
async get(path: string, fileName: string): Promise<Buffer | null> {
|
|
27
20
|
// get assets file from cotar
|
|
28
|
-
if (
|
|
21
|
+
if (path.endsWith('.tar.co')) return await this.getFromCotar(path, fileName);
|
|
29
22
|
|
|
30
23
|
// get assets file for directory
|
|
31
24
|
try {
|
|
32
|
-
const filePath = fsa.join(
|
|
25
|
+
const filePath = fsa.join(path, fileName);
|
|
33
26
|
return await fsa.read(filePath);
|
|
34
27
|
} catch (e: any) {
|
|
35
28
|
if (e.code === 404) return null;
|
|
@@ -51,7 +44,10 @@ export class AssetProvider {
|
|
|
51
44
|
* - Content-Type from the parameter contentType
|
|
52
45
|
*/
|
|
53
46
|
async serve(req: LambdaHttpRequest, file: string, contentType: string): Promise<LambdaHttpResponse> {
|
|
54
|
-
const
|
|
47
|
+
const config = await ConfigLoader.load(req);
|
|
48
|
+
if (config == null) return NotFound();
|
|
49
|
+
if (config.assets == null) return NotFound();
|
|
50
|
+
const buf = await assetProvider.get(config.assets, file);
|
|
55
51
|
if (buf == null) return NotFound();
|
|
56
52
|
const cacheKey = Etag.key(buf);
|
|
57
53
|
if (Etag.isNotModified(req, cacheKey)) return NotModified();
|
package/src/util/config.cache.ts
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { ConfigBundled, ConfigProviderMemory } from '@basemaps/config';
|
|
2
|
-
import { fsa } from '@
|
|
2
|
+
import { fsa } from '@basemaps/shared';
|
|
3
3
|
import { SwappingLru } from './swapping.lru.js';
|
|
4
4
|
|
|
5
5
|
class LruConfig {
|
|
6
|
-
configProvider: Promise<ConfigProviderMemory>;
|
|
6
|
+
configProvider: Promise<ConfigProviderMemory | null>;
|
|
7
7
|
|
|
8
8
|
constructor(config: Promise<ConfigBundled>) {
|
|
9
|
-
this.configProvider = config
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
this.configProvider = config
|
|
10
|
+
.then((c) => {
|
|
11
|
+
const configProvider = ConfigProviderMemory.fromJson(c);
|
|
12
|
+
configProvider.createVirtualTileSets();
|
|
13
|
+
return configProvider;
|
|
14
|
+
})
|
|
15
|
+
.catch((e) => {
|
|
16
|
+
if (e.code === 404) return null;
|
|
17
|
+
throw e;
|
|
18
|
+
});
|
|
14
19
|
}
|
|
15
20
|
|
|
16
21
|
get size(): number {
|
|
@@ -25,20 +30,15 @@ export class ConfigCache {
|
|
|
25
30
|
this.cache = new SwappingLru<LruConfig>(maxSize);
|
|
26
31
|
}
|
|
27
32
|
|
|
28
|
-
|
|
33
|
+
get(location: string): Promise<ConfigProviderMemory | null> {
|
|
29
34
|
const existing = this.cache.get(location)?.configProvider;
|
|
30
35
|
if (existing != null) return existing;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return config.configProvider;
|
|
36
|
-
} catch (e: any) {
|
|
37
|
-
if (e.code === 404) return Promise.resolve(null);
|
|
38
|
-
throw e;
|
|
39
|
-
}
|
|
36
|
+
const configJson = fsa.readJson<ConfigBundled>(location);
|
|
37
|
+
const config = new LruConfig(configJson);
|
|
38
|
+
this.cache.set(location, config);
|
|
39
|
+
return config.configProvider;
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
/** Cache 20 configs(Around
|
|
43
|
+
/** Cache 20 configs (Around <30KB -> 5MB each)*/
|
|
44
44
|
export const CachedConfig = new ConfigCache(20);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { base58, BasemapsConfigProvider, isBase58 } from '@basemaps/config';
|
|
2
|
+
import { LambdaHttpResponse } from '@linzjs/lambda';
|
|
3
|
+
import { parseUri } from '@chunkd/core';
|
|
4
|
+
import { LambdaHttpRequest } from '@linzjs/lambda';
|
|
5
|
+
import { CachedConfig } from './config.cache.js';
|
|
6
|
+
import { getDefaultConfig } from '@basemaps/shared';
|
|
7
|
+
|
|
8
|
+
// FIXME load this from process.env COG BUCKETS?
|
|
9
|
+
const SafeBuckets = new Set(['linz-workflow-artifacts', 'linz-basemaps']);
|
|
10
|
+
const SafeProtocols = new Set(['s3', 'memory']);
|
|
11
|
+
|
|
12
|
+
export class ConfigLoader {
|
|
13
|
+
/** Exposed for testing */
|
|
14
|
+
static async getDefaultConfig(): Promise<BasemapsConfigProvider> {
|
|
15
|
+
const config = getDefaultConfig();
|
|
16
|
+
if (config.assets == null) {
|
|
17
|
+
const cb = await config.ConfigBundle.get(config.ConfigBundle.id('latest'));
|
|
18
|
+
if (cb == null) throw new LambdaHttpResponse(400, 'Unable to get lastest config bundle for asset.');
|
|
19
|
+
config.assets = cb.assets;
|
|
20
|
+
}
|
|
21
|
+
return config;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Lookup the config path from a request and return a standardized location */
|
|
25
|
+
static extract(req: LambdaHttpRequest): string | null {
|
|
26
|
+
const rawLocation = req.query.get('config');
|
|
27
|
+
if (rawLocation == null) return null;
|
|
28
|
+
if (rawLocation.includes('/')) return base58.encode(Buffer.from(rawLocation));
|
|
29
|
+
return rawLocation;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static async load(req: LambdaHttpRequest): Promise<BasemapsConfigProvider> {
|
|
33
|
+
const rawLocation = req.query.get('config');
|
|
34
|
+
if (rawLocation == null) return this.getDefaultConfig();
|
|
35
|
+
|
|
36
|
+
const configLocation = isBase58(rawLocation) ? Buffer.from(base58.decode(rawLocation)).toString() : rawLocation;
|
|
37
|
+
|
|
38
|
+
const r = parseUri(configLocation);
|
|
39
|
+
|
|
40
|
+
if (r == null) throw new LambdaHttpResponse(400, 'Invalid config location');
|
|
41
|
+
if (!SafeProtocols.has(r.protocol)) {
|
|
42
|
+
throw new LambdaHttpResponse(400, `Invalid configuration location protocol:${r.protocol}`);
|
|
43
|
+
}
|
|
44
|
+
if (!SafeBuckets.has(r.bucket)) {
|
|
45
|
+
throw new LambdaHttpResponse(400, `Bucket: "${r.bucket}" is not a allowed bucket location`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
req.set('config', configLocation);
|
|
49
|
+
req.timer.start('config:load');
|
|
50
|
+
return CachedConfig.get(configLocation).then((f) => {
|
|
51
|
+
req.timer.end('config:load');
|
|
52
|
+
if (f == null) throw new LambdaHttpResponse(404, `Config not found at ${configLocation}`);
|
|
53
|
+
return f;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
package/src/wmts.capability.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ConfigImagery, ConfigLayer, ConfigTileSet, standardizeLayerName } from '@basemaps/config';
|
|
2
2
|
import { Bounds, GoogleTms, ImageFormat, TileMatrixSet, WmtsProvider } from '@basemaps/geo';
|
|
3
|
-
import { Projection, V, VNodeElement } from '@basemaps/shared';
|
|
3
|
+
import { Projection, toQueryString, V, VNodeElement } from '@basemaps/shared';
|
|
4
4
|
import { ImageFormatOrder } from '@basemaps/tiler';
|
|
5
5
|
import { BoundingBox } from '@cogeotiff/core';
|
|
6
6
|
import { BBox } from '@linzjs/geojson';
|
|
@@ -36,6 +36,8 @@ export interface WmtsCapabilitiesParams {
|
|
|
36
36
|
apiKey?: string;
|
|
37
37
|
/** Limit the output to the following image formats other wise @see ImageFormatOrder */
|
|
38
38
|
formats?: ImageFormat[] | null;
|
|
39
|
+
/** Config location */
|
|
40
|
+
config?: string | null;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
/** Number of decimal places to use in lat lng */
|
|
@@ -56,6 +58,7 @@ export class WmtsCapabilities {
|
|
|
56
58
|
provider?: WmtsProvider;
|
|
57
59
|
tileSet: ConfigTileSet;
|
|
58
60
|
apiKey?: string;
|
|
61
|
+
config?: string | null;
|
|
59
62
|
tileMatrixSets = new Map<string, TileMatrixSet>();
|
|
60
63
|
imagery: Map<string, ConfigImagery>;
|
|
61
64
|
formats: ImageFormat[];
|
|
@@ -65,6 +68,7 @@ export class WmtsCapabilities {
|
|
|
65
68
|
this.httpBase = params.httpBase;
|
|
66
69
|
this.provider = params.provider;
|
|
67
70
|
this.tileSet = params.tileSet;
|
|
71
|
+
this.config = params.config;
|
|
68
72
|
this.isIndividualLayers = params.isIndividualLayers;
|
|
69
73
|
for (const tms of params.tileMatrix) this.tileMatrixSets.set(tms.identifier, tms);
|
|
70
74
|
this.apiKey = params.apiKey;
|
|
@@ -72,17 +76,6 @@ export class WmtsCapabilities {
|
|
|
72
76
|
this.imagery = params.imagery;
|
|
73
77
|
}
|
|
74
78
|
|
|
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);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
this.imagery = await Config.Imagery.getAll(ids);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
79
|
buildWgs84BoundingBox(tms: TileMatrixSet, layers: Bounds[]): VNodeElement {
|
|
87
80
|
let bbox: BBox;
|
|
88
81
|
if (layers.length > 0) {
|
|
@@ -164,7 +157,8 @@ export class WmtsCapabilities {
|
|
|
164
157
|
}
|
|
165
158
|
|
|
166
159
|
buildTileUrl(tileSetId: string, suffix: string): string {
|
|
167
|
-
const
|
|
160
|
+
const query = toQueryString({ api: this.apiKey, config: this.config });
|
|
161
|
+
|
|
168
162
|
return [
|
|
169
163
|
this.httpBase,
|
|
170
164
|
'v1',
|
|
@@ -173,7 +167,7 @@ export class WmtsCapabilities {
|
|
|
173
167
|
'{TileMatrixSet}',
|
|
174
168
|
'{TileMatrix}',
|
|
175
169
|
'{TileCol}',
|
|
176
|
-
`{TileRow}.${suffix}${
|
|
170
|
+
`{TileRow}.${suffix}${query}`,
|
|
177
171
|
].join('/');
|
|
178
172
|
}
|
|
179
173
|
|