@basemaps/lambda-tiler 6.24.0 → 6.25.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 +35 -0
- package/build/__test__/tile.cache.key.test.js +2 -3
- package/build/__test__/tile.import.test.d.ts +2 -0
- package/build/__test__/tile.import.test.d.ts.map +1 -0
- package/build/__test__/tile.import.test.js +115 -0
- package/build/__test__/tile.set.cache.test.js +9 -0
- package/build/__test__/tile.style.json.test.js +47 -1
- package/build/__test__/xyz.test.js +55 -28
- package/build/__test__/xyz.util.d.ts +4 -0
- package/build/__test__/xyz.util.d.ts.map +1 -1
- package/build/__test__/xyz.util.js +8 -1
- package/build/cli/dump.js +2 -3
- package/build/import/imagery.find.d.ts +17 -0
- package/build/import/imagery.find.d.ts.map +1 -0
- package/build/import/imagery.find.js +38 -0
- package/build/import/make.cog.d.ts +5 -0
- package/build/import/make.cog.d.ts.map +1 -0
- package/build/import/make.cog.js +21 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -0
- package/build/routes/__test__/attribution.test.js +4 -0
- package/build/routes/__test__/health.test.js +1 -2
- package/build/routes/attribution.d.ts.map +1 -1
- package/build/routes/attribution.js +1 -0
- package/build/routes/health.d.ts.map +1 -1
- package/build/routes/health.js +3 -4
- package/build/routes/import.d.ts +9 -0
- package/build/routes/import.d.ts.map +1 -0
- package/build/routes/import.js +61 -0
- package/build/routes/tile.json.d.ts +0 -7
- package/build/routes/tile.json.d.ts.map +1 -1
- package/build/routes/tile.json.js +26 -18
- package/build/routes/tile.style.json.d.ts +8 -0
- package/build/routes/tile.style.json.d.ts.map +1 -1
- package/build/routes/tile.style.json.js +26 -20
- package/build/tile.set.cache.d.ts.map +1 -1
- package/build/tile.set.cache.js +7 -3
- package/build/tile.set.raster.d.ts +4 -2
- package/build/tile.set.raster.d.ts.map +1 -1
- package/build/tile.set.raster.js +9 -5
- package/build/tile.set.vector.d.ts +4 -1
- package/build/tile.set.vector.d.ts.map +1 -1
- package/build/tile.set.vector.js +9 -1
- package/build/wmts.capability.d.ts +4 -0
- package/build/wmts.capability.d.ts.map +1 -1
- package/build/wmts.capability.js +1 -1
- package/package.json +9 -8
- package/src/__test__/tile.cache.key.test.ts +3 -3
- package/src/__test__/tile.import.test.ts +140 -0
- package/src/__test__/tile.set.cache.test.ts +11 -0
- package/src/__test__/tile.style.json.test.ts +56 -1
- package/src/__test__/xyz.test.ts +59 -28
- package/src/__test__/xyz.util.ts +10 -2
- package/src/cli/dump.ts +2 -3
- package/src/import/imagery.find.ts +60 -0
- package/src/import/make.cog.ts +29 -0
- package/src/index.ts +2 -0
- package/src/routes/__test__/attribution.test.ts +4 -0
- package/src/routes/__test__/health.test.ts +1 -3
- package/src/routes/attribution.ts +1 -0
- package/src/routes/health.ts +3 -4
- package/src/routes/import.ts +66 -0
- package/src/routes/tile.json.ts +26 -27
- package/src/routes/tile.style.json.ts +25 -21
- package/src/tile.set.cache.ts +6 -2
- package/src/tile.set.raster.ts +9 -6
- package/src/tile.set.vector.ts +11 -2
- package/src/wmts.capability.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -74,6 +74,17 @@ o.spec('TileSetCache', () => {
|
|
|
74
74
|
if (subTileSetB == null || subTileSetB.type === TileSetType.Vector) throw new Error('null subTileSetB');
|
|
75
75
|
o(subTileSetB.title).equals('parent Tasman rural 2018-19 0.3m');
|
|
76
76
|
});
|
|
77
|
+
|
|
78
|
+
o('should not throw if child does not exist', async () => {
|
|
79
|
+
TileSets.add(new TileSetRaster('aerial@head', GoogleTms));
|
|
80
|
+
|
|
81
|
+
const parentTileSet = await TileSets.get('aerial@head', GoogleTms);
|
|
82
|
+
if (parentTileSet == null || parentTileSet.type === TileSetType.Vector) throw new Error('null parentTileSet');
|
|
83
|
+
parentTileSet.imagery = imgMap;
|
|
84
|
+
|
|
85
|
+
const subTileSet = await TileSets.get('aerial@head:fake', GoogleTms);
|
|
86
|
+
o(subTileSet).equals(null);
|
|
87
|
+
});
|
|
77
88
|
});
|
|
78
89
|
|
|
79
90
|
o.spec('loadTileSets', () => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { StyleJson } from '@basemaps/config';
|
|
1
2
|
import { Env } from '@basemaps/shared';
|
|
2
3
|
import o from 'ospec';
|
|
3
|
-
import { convertRelativeUrl } from '../routes/tile.style.json.js';
|
|
4
|
+
import { convertRelativeUrl, convertStyleJson } from '../routes/tile.style.json.js';
|
|
4
5
|
|
|
5
6
|
o.spec('TileStyleJson', () => {
|
|
6
7
|
const host = 'https://tiles.test';
|
|
@@ -37,4 +38,58 @@ o.spec('TileStyleJson', () => {
|
|
|
37
38
|
o('should not convert full urls', () => {
|
|
38
39
|
o(convertRelativeUrl('https://foo.com/foo?bar=baz', 'abc')).equals('https://foo.com/foo?bar=baz');
|
|
39
40
|
});
|
|
41
|
+
|
|
42
|
+
const baseStyleJson: StyleJson = {
|
|
43
|
+
version: 8,
|
|
44
|
+
id: 'style.id',
|
|
45
|
+
name: 'style.name',
|
|
46
|
+
sources: {
|
|
47
|
+
vector: { type: 'vector', url: '/v1/tiles/topographic/EPSG:3857/tile.json' },
|
|
48
|
+
raster: { type: 'raster', tiles: ['/v1/tiles/aerial/EPSG:3857/{z}/{x}/{y}.webp'] },
|
|
49
|
+
},
|
|
50
|
+
layers: [],
|
|
51
|
+
metadata: {},
|
|
52
|
+
glyphs: '/v1/glyphs',
|
|
53
|
+
sprite: '/v1/sprites',
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
o('should not destroy the original configuration', () => {
|
|
57
|
+
const apiKey = 'abc123';
|
|
58
|
+
const converted = convertStyleJson(baseStyleJson, apiKey);
|
|
59
|
+
|
|
60
|
+
o(converted.sources.vector).deepEquals({
|
|
61
|
+
type: 'vector',
|
|
62
|
+
url: 'https://tiles.test/v1/tiles/topographic/EPSG:3857/tile.json?api=abc123',
|
|
63
|
+
});
|
|
64
|
+
o(converted.sources.raster).deepEquals({
|
|
65
|
+
type: 'raster',
|
|
66
|
+
tiles: ['https://tiles.test/v1/tiles/aerial/EPSG:3857/{z}/{x}/{y}.webp?api=abc123'],
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
o(JSON.stringify(baseStyleJson).includes(apiKey)).equals(false);
|
|
70
|
+
|
|
71
|
+
const convertedB = convertStyleJson(baseStyleJson, '0x1234');
|
|
72
|
+
o(convertedB.sources.vector).deepEquals({
|
|
73
|
+
type: 'vector',
|
|
74
|
+
url: 'https://tiles.test/v1/tiles/topographic/EPSG:3857/tile.json?api=0x1234',
|
|
75
|
+
});
|
|
76
|
+
o(convertedB.sources.raster).deepEquals({
|
|
77
|
+
type: 'raster',
|
|
78
|
+
tiles: ['https://tiles.test/v1/tiles/aerial/EPSG:3857/{z}/{x}/{y}.webp?api=0x1234'],
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
o(JSON.stringify(baseStyleJson).includes('0x1234')).equals(false);
|
|
82
|
+
o(JSON.stringify(baseStyleJson).includes(apiKey)).equals(false);
|
|
83
|
+
o(JSON.stringify(baseStyleJson).includes('?api=')).equals(false);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
o('should convert relative glyphs and sprites', () => {
|
|
87
|
+
const apiKey = '0x9f9f';
|
|
88
|
+
const converted = convertStyleJson(baseStyleJson, apiKey);
|
|
89
|
+
o(converted.sprite).equals('https://tiles.test/v1/sprites');
|
|
90
|
+
o(converted.glyphs).equals('https://tiles.test/v1/glyphs');
|
|
91
|
+
|
|
92
|
+
o(JSON.stringify(baseStyleJson).includes(apiKey)).equals(false);
|
|
93
|
+
o(JSON.stringify(baseStyleJson).includes('?api=')).equals(false);
|
|
94
|
+
});
|
|
40
95
|
});
|
package/src/__test__/xyz.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ConfigProvider, StyleJson } from '@basemaps/config';
|
|
2
|
-
import { TileMatrixSets } from '@basemaps/geo';
|
|
2
|
+
import { GoogleTms, Nztm2000QuadTms, TileMatrixSets } from '@basemaps/geo';
|
|
3
3
|
import { Config, Env, LogConfig, VNodeParser } from '@basemaps/shared';
|
|
4
4
|
import { round } from '@basemaps/test/build/rounding.js';
|
|
5
5
|
import o from 'ospec';
|
|
@@ -8,7 +8,7 @@ import { handleRequest } from '../index.js';
|
|
|
8
8
|
import { TileEtag } from '../routes/tile.etag.js';
|
|
9
9
|
import { TileSets } from '../tile.set.cache.js';
|
|
10
10
|
import { TileComposer } from '../tile.set.raster.js';
|
|
11
|
-
import { FakeTileSet, mockRequest, Provider } from './xyz.util.js';
|
|
11
|
+
import { FakeTileSet, FakeTileSetVector, mockRequest, Provider } from './xyz.util.js';
|
|
12
12
|
|
|
13
13
|
const sandbox = sinon.createSandbox();
|
|
14
14
|
|
|
@@ -53,6 +53,8 @@ o.spec('LambdaXyz', () => {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
TileSets.add(new FakeTileSetVector('topographic', GoogleTms));
|
|
57
|
+
|
|
56
58
|
(Config.Provider as any).get = async (): Promise<ConfigProvider> => Provider;
|
|
57
59
|
});
|
|
58
60
|
|
|
@@ -186,51 +188,80 @@ o.spec('LambdaXyz', () => {
|
|
|
186
188
|
});
|
|
187
189
|
|
|
188
190
|
o.spec('tileJson', () => {
|
|
189
|
-
o('should
|
|
190
|
-
|
|
191
|
+
o('should 404 if invalid url is given', async () => {
|
|
192
|
+
const request = mockRequest('/v1/tiles/tile.json', 'get', apiKeyHeader);
|
|
191
193
|
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
+
const res = await handleRequest(request);
|
|
195
|
+
o(res.status).equals(404);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
o('should serve tile json for tile_set', async () => {
|
|
199
|
+
const request = mockRequest('/v1/tiles/aerial/NZTM2000Quad/tile.json', 'get', apiKeyHeader);
|
|
194
200
|
|
|
195
201
|
const res = await handleRequest(request);
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
202
|
+
o(res.status).equals(200);
|
|
203
|
+
o(res.header('cache-control')).equals('no-store');
|
|
200
204
|
|
|
201
|
-
|
|
202
|
-
o(
|
|
205
|
+
const body = Buffer.from(res.body ?? '', 'base64').toString();
|
|
206
|
+
o(JSON.parse(body)).deepEquals({
|
|
207
|
+
tiles: [`https://tiles.test/v1/tiles/aerial/NZTM2000Quad/{z}/{x}/{y}.webp?api=${apiKey}`],
|
|
208
|
+
vector_layers: [],
|
|
209
|
+
tilejson: '3.0.0',
|
|
210
|
+
});
|
|
211
|
+
});
|
|
203
212
|
|
|
204
|
-
|
|
213
|
+
o('should serve vector tiles', async () => {
|
|
214
|
+
const request = mockRequest('/v1/tiles/topographic/WebMercatorQuad/tile.json', 'get', apiKeyHeader);
|
|
215
|
+
|
|
216
|
+
const res = await handleRequest(request);
|
|
217
|
+
o(res.status).equals(200);
|
|
218
|
+
|
|
219
|
+
const body = Buffer.from(res.body ?? '', 'base64').toString();
|
|
220
|
+
o(JSON.parse(body)).deepEquals({
|
|
221
|
+
tiles: [`https://tiles.test/v1/tiles/topographic/EPSG:3857/{z}/{x}/{y}.pbf?api=${apiKey}`],
|
|
222
|
+
vector_layers: [],
|
|
223
|
+
tilejson: '3.0.0',
|
|
224
|
+
});
|
|
205
225
|
});
|
|
206
226
|
|
|
207
|
-
o('should
|
|
208
|
-
const
|
|
209
|
-
|
|
227
|
+
o('should serve vector tiles with min/max zoom', async () => {
|
|
228
|
+
const fakeTileSet = new FakeTileSetVector('fake-vector', GoogleTms);
|
|
229
|
+
fakeTileSet.tileSet.maxZoom = 15;
|
|
230
|
+
fakeTileSet.tileSet.minZoom = 3;
|
|
231
|
+
TileSets.add(fakeTileSet);
|
|
232
|
+
const request = mockRequest('/v1/tiles/fake-vector/WebMercatorQuad/tile.json', 'get', apiKeyHeader);
|
|
210
233
|
|
|
211
234
|
const res = await handleRequest(request);
|
|
212
235
|
o(res.status).equals(200);
|
|
213
|
-
|
|
214
|
-
const
|
|
215
|
-
o(
|
|
216
|
-
|
|
236
|
+
|
|
237
|
+
const body = Buffer.from(res.body ?? '', 'base64').toString();
|
|
238
|
+
o(JSON.parse(body)).deepEquals({
|
|
239
|
+
tiles: [`https://tiles.test/v1/tiles/fake-vector/EPSG:3857/{z}/{x}/{y}.pbf?api=${apiKey}`],
|
|
240
|
+
vector_layers: [],
|
|
241
|
+
maxzoom: 15,
|
|
242
|
+
minzoom: 3,
|
|
243
|
+
tilejson: '3.0.0',
|
|
244
|
+
});
|
|
217
245
|
});
|
|
218
246
|
|
|
219
|
-
o('should serve
|
|
220
|
-
const
|
|
247
|
+
o('should serve convert zoom to tile matrix', async () => {
|
|
248
|
+
const fakeTileSet = new FakeTileSetVector('fake-vector', Nztm2000QuadTms);
|
|
249
|
+
fakeTileSet.tileSet.maxZoom = 15;
|
|
250
|
+
fakeTileSet.tileSet.minZoom = 1;
|
|
251
|
+
TileSets.add(fakeTileSet);
|
|
252
|
+
|
|
253
|
+
const request = mockRequest('/v1/tiles/fake-vector/NZTM2000Quad/tile.json', 'get', apiKeyHeader);
|
|
221
254
|
|
|
222
255
|
const res = await handleRequest(request);
|
|
223
256
|
o(res.status).equals(200);
|
|
224
|
-
o(res.header('content-type')).equals('application/json');
|
|
225
|
-
o(res.header('cache-control')).equals('max-age=120');
|
|
226
257
|
|
|
227
258
|
const body = Buffer.from(res.body ?? '', 'base64').toString();
|
|
228
259
|
o(JSON.parse(body)).deepEquals({
|
|
229
|
-
tiles: [`https://tiles.test/v1/tiles/
|
|
260
|
+
tiles: [`https://tiles.test/v1/tiles/fake-vector/NZTM2000Quad/{z}/{x}/{y}.pbf?api=${apiKey}`],
|
|
261
|
+
vector_layers: [],
|
|
262
|
+
maxzoom: 13,
|
|
230
263
|
minzoom: 0,
|
|
231
|
-
|
|
232
|
-
format: 'pbf',
|
|
233
|
-
tilejson: '2.0.0',
|
|
264
|
+
tilejson: '3.0.0',
|
|
234
265
|
});
|
|
235
266
|
});
|
|
236
267
|
});
|
package/src/__test__/xyz.util.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { ConfigProvider } from '@basemaps/config';
|
|
2
2
|
import { TileMatrixSet } from '@basemaps/geo';
|
|
3
|
-
import { LambdaHttpRequest, LambdaAlbRequest } from '@linzjs/lambda';
|
|
4
3
|
import { LogConfig } from '@basemaps/shared';
|
|
5
|
-
import {
|
|
4
|
+
import { LambdaAlbRequest, LambdaHttpRequest } from '@linzjs/lambda';
|
|
6
5
|
import { Context } from 'aws-lambda';
|
|
6
|
+
import { TileSetRaster } from '../tile.set.raster.js';
|
|
7
|
+
import { TileSetVector } from '../tile.set.vector.js';
|
|
7
8
|
|
|
8
9
|
export function mockRequest(path: string, method = 'get', headers: Record<string, string> = {}): LambdaHttpRequest {
|
|
9
10
|
return new LambdaAlbRequest(
|
|
@@ -27,6 +28,13 @@ export class FakeTileSet extends TileSetRaster {
|
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
export class FakeTileSetVector extends TileSetVector {
|
|
32
|
+
constructor(name: string, tileMatrix: TileMatrixSet) {
|
|
33
|
+
super(name, tileMatrix);
|
|
34
|
+
this.tileSet = {} as any;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
30
38
|
export const Provider: ConfigProvider = {
|
|
31
39
|
createdAt: Date.now(),
|
|
32
40
|
name: 'main',
|
package/src/cli/dump.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { Nztm2000Tms } from '@basemaps/geo';
|
|
1
|
+
import { Nztm2000Tms, ImageFormat } from '@basemaps/geo';
|
|
2
2
|
import { LogConfig } from '@basemaps/shared';
|
|
3
|
-
import { ImageFormat } from '@basemaps/tiler';
|
|
4
3
|
import { LambdaAlbRequest } from '@linzjs/lambda';
|
|
5
4
|
import { Context } from 'aws-lambda';
|
|
6
5
|
import { promises as fs } from 'fs';
|
|
@@ -12,7 +11,7 @@ import { TileSetLocal } from './tile.set.local.js';
|
|
|
12
11
|
const xyz = { x: 0, y: 0, z: 0 };
|
|
13
12
|
const tileMatrix = Nztm2000Tms;
|
|
14
13
|
const tileSetName = 'aerial';
|
|
15
|
-
const ext = ImageFormat.
|
|
14
|
+
const ext = ImageFormat.Png;
|
|
16
15
|
|
|
17
16
|
/** Load a tileset form a file path otherwise default to the hard coded one from AWS */
|
|
18
17
|
async function getTileSet(filePath?: string): Promise<TileSet> {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { AwsCredentials } from '@chunkd/source-aws-v2';
|
|
2
|
+
import { fsa } from '@chunkd/fs';
|
|
3
|
+
import { Env } from '@basemaps/shared';
|
|
4
|
+
|
|
5
|
+
export interface RoleConfig {
|
|
6
|
+
bucket: string;
|
|
7
|
+
accountId: string;
|
|
8
|
+
roleArn: string;
|
|
9
|
+
externalId?: string;
|
|
10
|
+
roleSessionDuration?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface BucketConfig {
|
|
14
|
+
v: number;
|
|
15
|
+
buckets: RoleConfig[];
|
|
16
|
+
version: string;
|
|
17
|
+
package: string;
|
|
18
|
+
hash: string;
|
|
19
|
+
updatedAt: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class RoleRegister {
|
|
23
|
+
/** Get all imagery source aws roles */
|
|
24
|
+
static async _loadRoles(): Promise<RoleConfig[]> {
|
|
25
|
+
const configBucket = Env.get(Env.AwsRoleConfigBucket);
|
|
26
|
+
if (configBucket == null) return [];
|
|
27
|
+
const configPath = `s3://${configBucket}/config.json`;
|
|
28
|
+
const config: BucketConfig = await fsa.readJson(configPath);
|
|
29
|
+
const roles = [];
|
|
30
|
+
for (const role of config.buckets) {
|
|
31
|
+
fsa.register(
|
|
32
|
+
's3://' + role.bucket,
|
|
33
|
+
AwsCredentials.fsFromRole(role.roleArn, role.externalId, role.roleSessionDuration),
|
|
34
|
+
);
|
|
35
|
+
roles.push(role);
|
|
36
|
+
}
|
|
37
|
+
return roles;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static _loadRolesPromise: Promise<RoleConfig[]> | undefined;
|
|
41
|
+
static loadRoles(): Promise<RoleConfig[]> {
|
|
42
|
+
if (RoleRegister._loadRolesPromise == null) RoleRegister._loadRolesPromise = this._loadRoles();
|
|
43
|
+
return RoleRegister._loadRolesPromise;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static async findRole(path: string): Promise<RoleConfig | undefined> {
|
|
47
|
+
const roles = await this.loadRoles();
|
|
48
|
+
return roles.find((f) => path.startsWith(`s3://${f.bucket}`));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Search for the imagery across all of our buckets */
|
|
53
|
+
export async function findImagery(path: string): Promise<string[]> {
|
|
54
|
+
const files: string[] = [];
|
|
55
|
+
for await (const key of fsa.list(path)) {
|
|
56
|
+
const searchKey = key.toLowerCase();
|
|
57
|
+
if (searchKey.endsWith('.tif') || searchKey.endsWith('.tiff')) files.push(key);
|
|
58
|
+
}
|
|
59
|
+
return files;
|
|
60
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { JobCreationContext } from '@basemaps/cli/build/cog/cog.stac.job';
|
|
2
|
+
import { TileMatrixSet } from '@basemaps/geo';
|
|
3
|
+
import { Env } from '@basemaps/shared';
|
|
4
|
+
import { RoleConfig } from './imagery.find.js';
|
|
5
|
+
|
|
6
|
+
export async function getJobCreationContext(
|
|
7
|
+
path: string,
|
|
8
|
+
tileMatrix: TileMatrixSet,
|
|
9
|
+
role: RoleConfig,
|
|
10
|
+
files: string[],
|
|
11
|
+
): Promise<JobCreationContext> {
|
|
12
|
+
const bucket = Env.get(Env.ImportImageryBucket);
|
|
13
|
+
if (bucket == null) throw new Error('Output AWS s3 bucket Not Found.');
|
|
14
|
+
const ctx: JobCreationContext = {
|
|
15
|
+
override: {
|
|
16
|
+
projection: tileMatrix.projection,
|
|
17
|
+
resampling: {
|
|
18
|
+
warp: 'bilinear',
|
|
19
|
+
overview: 'lanczos',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
outputLocation: { type: 's3' as const, path: `s3://${bucket}` },
|
|
23
|
+
sourceLocation: { type: 's3', path, ...role, files: files },
|
|
24
|
+
batch: true,
|
|
25
|
+
tileMatrix,
|
|
26
|
+
oneCogCovering: false,
|
|
27
|
+
};
|
|
28
|
+
return ctx;
|
|
29
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { createHash } from 'crypto';
|
|
|
8
8
|
import { Imagery } from './routes/imagery.js';
|
|
9
9
|
import { Esri } from './routes/esri/rest.js';
|
|
10
10
|
import { St } from './source.tracer.js';
|
|
11
|
+
import { Import } from './routes/import.js';
|
|
11
12
|
|
|
12
13
|
const app = new Router();
|
|
13
14
|
|
|
@@ -17,6 +18,7 @@ app.get('version', Version);
|
|
|
17
18
|
app.get('tiles', Tiles);
|
|
18
19
|
app.get('imagery', Imagery);
|
|
19
20
|
app.get('esri', Esri);
|
|
21
|
+
app.get('import', Import);
|
|
20
22
|
|
|
21
23
|
let slowTimer: NodeJS.Timer | null = null;
|
|
22
24
|
export async function handleRequest(req: LambdaHttpRequest): Promise<LambdaHttpResponse> {
|
|
@@ -45,6 +45,7 @@ const ExpectedJson = {
|
|
|
45
45
|
],
|
|
46
46
|
},
|
|
47
47
|
properties: {
|
|
48
|
+
title: 'Hastings-district urban 2011-13 0.1m',
|
|
48
49
|
datetime: null,
|
|
49
50
|
start_datetime: '2011-01-01T00:00:00Z',
|
|
50
51
|
end_datetime: '2014-01-01T00:00:00Z',
|
|
@@ -73,6 +74,7 @@ const ExpectedJson = {
|
|
|
73
74
|
],
|
|
74
75
|
},
|
|
75
76
|
properties: {
|
|
77
|
+
title: 'Hastings-district urban 2013-14 0.1m',
|
|
76
78
|
datetime: null,
|
|
77
79
|
start_datetime: '2013-01-01T00:00:00Z',
|
|
78
80
|
end_datetime: '2015-01-01T00:00:00Z',
|
|
@@ -101,6 +103,7 @@ const ExpectedJson = {
|
|
|
101
103
|
],
|
|
102
104
|
},
|
|
103
105
|
properties: {
|
|
106
|
+
title: 'Hastings-district urban 2015-17 0.1m',
|
|
104
107
|
datetime: null,
|
|
105
108
|
start_datetime: '2015-01-01T00:00:00Z',
|
|
106
109
|
end_datetime: '2018-01-01T00:00:00Z',
|
|
@@ -129,6 +132,7 @@ const ExpectedJson = {
|
|
|
129
132
|
],
|
|
130
133
|
},
|
|
131
134
|
properties: {
|
|
135
|
+
title: 'Hastings-district urban 2017-18 0.1m',
|
|
132
136
|
datetime: null,
|
|
133
137
|
start_datetime: '2017-01-01T00:00:00Z',
|
|
134
138
|
end_datetime: '2019-01-01T00:00:00Z',
|
|
@@ -21,6 +21,7 @@ const ctx: LambdaHttpRequest = new LambdaAlbRequest(
|
|
|
21
21
|
);
|
|
22
22
|
|
|
23
23
|
o.spec('health', async () => {
|
|
24
|
+
o.specTimeout(1000);
|
|
24
25
|
const sandbox = sinon.createSandbox();
|
|
25
26
|
|
|
26
27
|
const tileSet = new TileSetRaster('health', GoogleTms);
|
|
@@ -57,8 +58,6 @@ o.spec('health', async () => {
|
|
|
57
58
|
// Prepare mock test tile response based on the static test tiles
|
|
58
59
|
|
|
59
60
|
o('Should give a 200 response', async () => {
|
|
60
|
-
o.timeout(500);
|
|
61
|
-
|
|
62
61
|
// Given ... a series good get tile response
|
|
63
62
|
const callback = sandbox.stub(tileSet, 'tile');
|
|
64
63
|
callback.onCall(0).resolves(Response1);
|
|
@@ -73,7 +72,6 @@ o.spec('health', async () => {
|
|
|
73
72
|
});
|
|
74
73
|
|
|
75
74
|
o('Should return mis-match tile response', async () => {
|
|
76
|
-
o.timeout(500);
|
|
77
75
|
// Given ... a bad get tile response for second get tile
|
|
78
76
|
const callback = sandbox.stub(tileSet, 'tile');
|
|
79
77
|
callback.onCall(0).resolves(Response1);
|
|
@@ -173,6 +173,7 @@ async function tileSetAttribution(tileSet: TileSetRaster): Promise<AttributionSt
|
|
|
173
173
|
coordinates: createCoordinates(bbox, im.files, proj),
|
|
174
174
|
},
|
|
175
175
|
properties: {
|
|
176
|
+
title: titleizeImageryName(im.name),
|
|
176
177
|
datetime: null,
|
|
177
178
|
start_datetime: interval[0][0],
|
|
178
179
|
end_datetime: interval[0][1],
|
package/src/routes/health.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { GoogleTms, Nztm2000QuadTms } from '@basemaps/geo';
|
|
1
|
+
import { GoogleTms, Nztm2000QuadTms, ImageFormat } from '@basemaps/geo';
|
|
2
2
|
import { TileDataXyz, TileType } from '@basemaps/shared';
|
|
3
|
-
import { ImageFormat } from '@basemaps/tiler';
|
|
4
3
|
import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
|
|
5
4
|
import * as fs from 'fs';
|
|
6
5
|
import * as path from 'path';
|
|
@@ -14,8 +13,8 @@ interface TestTile extends TileDataXyz {
|
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
export const TestTiles: TestTile[] = [
|
|
17
|
-
{ type: TileType.Tile, name: 'health', tileMatrix: GoogleTms, ext: ImageFormat.
|
|
18
|
-
{ type: TileType.Tile, name: 'health', tileMatrix: Nztm2000QuadTms, ext: ImageFormat.
|
|
16
|
+
{ type: TileType.Tile, name: 'health', tileMatrix: GoogleTms, ext: ImageFormat.Png, x: 252, y: 156, z: 8 },
|
|
17
|
+
{ type: TileType.Tile, name: 'health', tileMatrix: Nztm2000QuadTms, ext: ImageFormat.Png, x: 30, y: 33, z: 6 },
|
|
19
18
|
];
|
|
20
19
|
const TileSize = 256;
|
|
21
20
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
|
|
2
|
+
import { Config, Const, fsa } from '@basemaps/shared';
|
|
3
|
+
import { createHash } from 'crypto';
|
|
4
|
+
import { findImagery, RoleRegister } from '../import/imagery.find.js';
|
|
5
|
+
import { Nztm2000Tms, TileMatrixSets } from '@basemaps/geo';
|
|
6
|
+
import { getJobCreationContext } from '../import/make.cog.js';
|
|
7
|
+
import { ConfigProcessingJob, ConfigProviderDynamo } from '@basemaps/config';
|
|
8
|
+
import { CogJobFactory } from '@basemaps/cli';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Trigger import imagery job by this endpoint
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* - /v1/import?path=s3://linz-imagery-staging/2022-03/wellington_rural_2022_delivery_1
|
|
15
|
+
*/
|
|
16
|
+
export async function Import(req: LambdaHttpRequest): Promise<LambdaHttpResponse> {
|
|
17
|
+
const path = req.query.get('path');
|
|
18
|
+
const projection = req.query.get('p');
|
|
19
|
+
|
|
20
|
+
// Parse projection as target, default to process both NZTM2000Quad
|
|
21
|
+
let targetTms = Nztm2000Tms;
|
|
22
|
+
if (projection != null) {
|
|
23
|
+
const tileMatrix = TileMatrixSets.find(projection);
|
|
24
|
+
if (tileMatrix == null) return new LambdaHttpResponse(404, 'Target projection Not found');
|
|
25
|
+
targetTms = tileMatrix;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Find the imagery from s3
|
|
29
|
+
if (path == null || !path.startsWith('s3://')) return new LambdaHttpResponse(500, 'Invalid s3 path');
|
|
30
|
+
const role = await RoleRegister.findRole(path);
|
|
31
|
+
if (role == null) return new LambdaHttpResponse(500, 'Unable to Access the bucket');
|
|
32
|
+
const files = await findImagery(path);
|
|
33
|
+
if (files.length === 0) return new LambdaHttpResponse(404, 'Imagery Not Found');
|
|
34
|
+
|
|
35
|
+
// Prepare Cog jobs
|
|
36
|
+
const ctx = await getJobCreationContext(path, targetTms, role, files);
|
|
37
|
+
|
|
38
|
+
const id = createHash('sha256').update(JSON.stringify(ctx)).digest('base64');
|
|
39
|
+
const jobId = Config.ProcessingJob.id(id);
|
|
40
|
+
let jobConfig = await Config.ProcessingJob.get(jobId);
|
|
41
|
+
if (jobConfig == null) {
|
|
42
|
+
// Add id back to JobCreationContext
|
|
43
|
+
ctx.override!.id = id;
|
|
44
|
+
ctx.outputLocation.path = fsa.join(ctx.outputLocation.path, id);
|
|
45
|
+
|
|
46
|
+
// Start processing job
|
|
47
|
+
await CogJobFactory.create(ctx);
|
|
48
|
+
jobConfig = {
|
|
49
|
+
id: jobId,
|
|
50
|
+
name: path,
|
|
51
|
+
status: 'processing',
|
|
52
|
+
} as ConfigProcessingJob;
|
|
53
|
+
|
|
54
|
+
const config = new ConfigProviderDynamo(Const.TileMetadata.TableName);
|
|
55
|
+
await config.ProcessingJob.put(jobConfig);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const json = JSON.stringify(jobConfig);
|
|
59
|
+
const data = Buffer.from(json);
|
|
60
|
+
|
|
61
|
+
const response = new LambdaHttpResponse(200, 'ok');
|
|
62
|
+
response.header(HttpHeader.CacheControl, 'no-store');
|
|
63
|
+
response.buffer(data, 'application/json');
|
|
64
|
+
req.set('bytes', data.byteLength);
|
|
65
|
+
return response;
|
|
66
|
+
}
|
package/src/routes/tile.json.ts
CHANGED
|
@@ -1,43 +1,42 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { GoogleTms, TileJson, TileMatrixSet } from '@basemaps/geo';
|
|
2
|
+
import { Env, extractTileMatrixSet } from '@basemaps/shared';
|
|
2
3
|
import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
|
|
3
|
-
import { createHash } from 'crypto';
|
|
4
4
|
import { Router } from '../router.js';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
export interface TileJson {
|
|
9
|
-
tiles: string[];
|
|
10
|
-
minzoom: number;
|
|
11
|
-
maxzoom: number;
|
|
12
|
-
format: string;
|
|
13
|
-
tilejson: string;
|
|
14
|
-
}
|
|
5
|
+
import { TileSets } from '../tile.set.cache.js';
|
|
6
|
+
import { getTileMatrixId } from '../wmts.capability.js';
|
|
7
|
+
import { NotFound } from './response.js';
|
|
15
8
|
|
|
16
9
|
export async function tileJson(req: LambdaHttpRequest): Promise<LambdaHttpResponse> {
|
|
17
10
|
const { version, rest, name } = Router.action(req);
|
|
11
|
+
if (rest.length !== 3) return NotFound;
|
|
12
|
+
|
|
13
|
+
const tileMatrix = extractTileMatrixSet(rest[1]);
|
|
14
|
+
if (tileMatrix == null) return NotFound;
|
|
15
|
+
|
|
16
|
+
req.timer.start('tileset:load');
|
|
17
|
+
const tileSet = await TileSets.get(rest[0], tileMatrix);
|
|
18
|
+
req.timer.end('tileset:load');
|
|
19
|
+
if (tileSet == null) return NotFound;
|
|
20
|
+
|
|
18
21
|
const apiKey = Router.apiKey(req);
|
|
19
22
|
const host = Env.get(Env.PublicUrlBase) ?? '';
|
|
20
|
-
const tileUrl = `${host}/${version}/${name}/${rest[0]}/${rest[1]}/{z}/{x}/{y}.pbf?api=${apiKey}`;
|
|
21
23
|
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
maxzoom: 15,
|
|
26
|
-
format: 'pbf',
|
|
27
|
-
tilejson: '2.0.0',
|
|
28
|
-
};
|
|
24
|
+
const tileUrl =
|
|
25
|
+
[host, version, name, tileSet.fullName, getTileMatrixId(tileMatrix), '{z}', '{x}', '{y}'].join('/') +
|
|
26
|
+
`.${tileSet.format}?api=${apiKey}`;
|
|
29
27
|
|
|
30
|
-
const
|
|
28
|
+
const tileJson: TileJson = { tiles: [tileUrl], vector_layers: [], tilejson: '3.0.0' };
|
|
29
|
+
const maxZoom = TileMatrixSet.convertZoomLevel(tileSet.tileSet.maxZoom ?? 30, GoogleTms, tileMatrix, true);
|
|
30
|
+
const minZoom = TileMatrixSet.convertZoomLevel(tileSet.tileSet.minZoom ?? 0, GoogleTms, tileMatrix, true);
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const cacheKey = createHash('sha256').update(data).digest('base64');
|
|
32
|
+
if (tileSet.tileSet.maxZoom) tileJson.maxzoom = maxZoom;
|
|
33
|
+
if (tileSet.tileSet.minZoom) tileJson.minzoom = minZoom;
|
|
35
34
|
|
|
36
|
-
|
|
35
|
+
const json = JSON.stringify(tileJson);
|
|
36
|
+
const data = Buffer.from(json);
|
|
37
37
|
|
|
38
38
|
const response = new LambdaHttpResponse(200, 'ok');
|
|
39
|
-
response.header(HttpHeader.
|
|
40
|
-
response.header(HttpHeader.CacheControl, 'max-age=120');
|
|
39
|
+
response.header(HttpHeader.CacheControl, 'no-store');
|
|
41
40
|
response.buffer(data, 'application/json');
|
|
42
41
|
req.set('bytes', data.byteLength);
|
|
43
42
|
return response;
|
|
@@ -23,20 +23,15 @@ export function convertRelativeUrl(url?: string, apiKey?: string): string {
|
|
|
23
23
|
return fullUrl.toString().replace(/%7B/g, '{').replace(/%7D/g, '}');
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// Prepare sources and add linz source
|
|
37
|
-
const style = styleConfig.style;
|
|
38
|
-
const sources: Sources = {};
|
|
39
|
-
for (const [key, value] of Object.entries(style.sources)) {
|
|
26
|
+
/**
|
|
27
|
+
* Create a new style json that has absolute urls to the current host and API Keys where required
|
|
28
|
+
* @param style style to update
|
|
29
|
+
* @param apiKey api key to inject
|
|
30
|
+
* @returns new stylejson
|
|
31
|
+
*/
|
|
32
|
+
export function convertStyleJson(style: StyleJson, apiKey: string): StyleJson {
|
|
33
|
+
const sources: Sources = JSON.parse(JSON.stringify(style.sources));
|
|
34
|
+
for (const [key, value] of Object.entries(sources)) {
|
|
40
35
|
if (value.type === 'vector') {
|
|
41
36
|
value.url = convertRelativeUrl(value.url, apiKey);
|
|
42
37
|
} else if (value.type === 'raster' && Array.isArray(value.tiles)) {
|
|
@@ -47,22 +42,31 @@ export async function styleJson(req: LambdaHttpRequest, fileName: string): Promi
|
|
|
47
42
|
sources[key] = value;
|
|
48
43
|
}
|
|
49
44
|
|
|
50
|
-
|
|
51
|
-
const styleJson: StyleJson = {
|
|
52
|
-
/** Style.json version 8 */
|
|
45
|
+
return {
|
|
53
46
|
version: 8,
|
|
54
47
|
id: style.id,
|
|
55
48
|
name: style.name,
|
|
56
49
|
sources,
|
|
57
50
|
layers: style.layers,
|
|
58
|
-
metadata: style.metadata
|
|
51
|
+
metadata: style.metadata ?? {},
|
|
59
52
|
glyphs: convertRelativeUrl(style.glyphs),
|
|
60
53
|
sprite: convertRelativeUrl(style.sprite),
|
|
61
|
-
};
|
|
54
|
+
} as StyleJson;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function styleJson(req: LambdaHttpRequest, fileName: string): Promise<LambdaHttpResponse> {
|
|
58
|
+
const apiKey = Router.apiKey(req);
|
|
59
|
+
if (apiKey == null) return new LambdaHttpResponse(400, 'Invalid API Key.');
|
|
60
|
+
const styleName = fileName.split('.json')[0];
|
|
62
61
|
|
|
63
|
-
|
|
62
|
+
// Get style Config from db
|
|
63
|
+
const dbId = Config.Style.id(styleName);
|
|
64
|
+
const styleConfig = await Config.Style.get(dbId);
|
|
65
|
+
if (styleConfig == null) return NotFound;
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
// Prepare sources and add linz source
|
|
68
|
+
const style = convertStyleJson(styleConfig.style, apiKey);
|
|
69
|
+
const data = Buffer.from(JSON.stringify(style));
|
|
66
70
|
|
|
67
71
|
const cacheKey = createHash('sha256').update(data).digest('base64');
|
|
68
72
|
|