@basemaps/lambda-tiler 6.24.1 → 6.26.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/build/__test__/tile.cache.key.test.js +2 -3
  3. package/build/__test__/tile.import.test.d.ts +2 -0
  4. package/build/__test__/tile.import.test.d.ts.map +1 -0
  5. package/build/__test__/tile.import.test.js +115 -0
  6. package/build/__test__/tile.set.cache.test.js +9 -0
  7. package/build/__test__/xyz.test.js +51 -28
  8. package/build/__test__/xyz.util.d.ts +4 -0
  9. package/build/__test__/xyz.util.d.ts.map +1 -1
  10. package/build/__test__/xyz.util.js +8 -1
  11. package/build/cli/dump.js +2 -3
  12. package/build/import/imagery.find.d.ts +17 -0
  13. package/build/import/imagery.find.d.ts.map +1 -0
  14. package/build/import/imagery.find.js +38 -0
  15. package/build/import/make.cog.d.ts +5 -0
  16. package/build/import/make.cog.d.ts.map +1 -0
  17. package/build/import/make.cog.js +21 -0
  18. package/build/index.d.ts.map +1 -1
  19. package/build/index.js +2 -0
  20. package/build/routes/__test__/attribution.test.js +4 -0
  21. package/build/routes/__test__/health.test.js +1 -2
  22. package/build/routes/attribution.d.ts.map +1 -1
  23. package/build/routes/attribution.js +1 -0
  24. package/build/routes/health.d.ts.map +1 -1
  25. package/build/routes/health.js +3 -4
  26. package/build/routes/import.d.ts +9 -0
  27. package/build/routes/import.d.ts.map +1 -0
  28. package/build/routes/import.js +62 -0
  29. package/build/routes/tile.json.d.ts +0 -7
  30. package/build/routes/tile.json.d.ts.map +1 -1
  31. package/build/routes/tile.json.js +26 -18
  32. package/build/tile.set.cache.d.ts.map +1 -1
  33. package/build/tile.set.cache.js +7 -3
  34. package/build/tile.set.raster.d.ts +4 -2
  35. package/build/tile.set.raster.d.ts.map +1 -1
  36. package/build/tile.set.raster.js +9 -5
  37. package/build/tile.set.vector.d.ts +4 -1
  38. package/build/tile.set.vector.d.ts.map +1 -1
  39. package/build/tile.set.vector.js +9 -1
  40. package/build/wmts.capability.d.ts +4 -0
  41. package/build/wmts.capability.d.ts.map +1 -1
  42. package/build/wmts.capability.js +1 -1
  43. package/package.json +9 -8
  44. package/src/__test__/tile.cache.key.test.ts +3 -3
  45. package/src/__test__/tile.import.test.ts +140 -0
  46. package/src/__test__/tile.set.cache.test.ts +11 -0
  47. package/src/__test__/xyz.test.ts +55 -28
  48. package/src/__test__/xyz.util.ts +10 -2
  49. package/src/cli/dump.ts +2 -3
  50. package/src/import/imagery.find.ts +60 -0
  51. package/src/import/make.cog.ts +29 -0
  52. package/src/index.ts +2 -0
  53. package/src/routes/__test__/attribution.test.ts +4 -0
  54. package/src/routes/__test__/health.test.ts +1 -3
  55. package/src/routes/attribution.ts +1 -0
  56. package/src/routes/health.ts +3 -4
  57. package/src/routes/import.ts +67 -0
  58. package/src/routes/tile.json.ts +26 -27
  59. package/src/tile.set.cache.ts +6 -2
  60. package/src/tile.set.raster.ts +9 -6
  61. package/src/tile.set.vector.ts +11 -2
  62. package/src/wmts.capability.ts +1 -1
  63. package/tsconfig.tsbuildinfo +1 -1
@@ -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,76 @@ o.spec('LambdaXyz', () => {
186
188
  });
187
189
 
188
190
  o.spec('tileJson', () => {
189
- o('should 304 if a json is not modified', async () => {
190
- // delete process.env[Env.PublicUrlBase];
191
+ o('should 404 if invalid url is given', async () => {
192
+ const request = mockRequest('/v1/tiles/tile.json', 'get', apiKeyHeader);
191
193
 
192
- const key = 'BBfQpNXA3Q90jlFrLSOZhxbvfOh7OpN1OEE+BylpbHw=';
193
- const request = mockRequest('/v1/tiles/tile.json', 'get', { 'if-none-match': key, ...apiKeyHeader });
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
- if (res.status === 200) {
197
- o(res.header('eTaG')).equals(key); // this line is useful for discovering the new etag
198
- return;
199
- }
202
+ o(res.status).equals(200);
203
+ o(res.header('cache-control')).equals('no-store');
200
204
 
201
- o(res.status).equals(304);
202
- o(rasterMock.calls.length).equals(0);
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
+ tilejson: '3.0.0',
209
+ });
210
+ });
203
211
 
204
- o(request.logContext['cache']).deepEquals({ key, match: key, hit: true });
212
+ o('should serve vector tiles', async () => {
213
+ const request = mockRequest('/v1/tiles/topographic/WebMercatorQuad/tile.json', 'get', apiKeyHeader);
214
+
215
+ const res = await handleRequest(request);
216
+ o(res.status).equals(200);
217
+
218
+ const body = Buffer.from(res.body ?? '', 'base64').toString();
219
+ o(JSON.parse(body)).deepEquals({
220
+ tiles: [`https://tiles.test/v1/tiles/topographic/EPSG:3857/{z}/{x}/{y}.pbf?api=${apiKey}`],
221
+ tilejson: '3.0.0',
222
+ });
205
223
  });
206
224
 
207
- o('should 200 if a invalid etag is given', async () => {
208
- const key = 'ABCXecTdbcdjCyzB1MHOOQbrOkD2TTJ0ORh4JuXqhxXEE0=';
209
- const request = mockRequest('/v1/tiles/tile.json', 'get', { 'if-none-match': key, ...apiKeyHeader });
225
+ o('should serve vector tiles with min/max zoom', async () => {
226
+ const fakeTileSet = new FakeTileSetVector('fake-vector', GoogleTms);
227
+ fakeTileSet.tileSet.maxZoom = 15;
228
+ fakeTileSet.tileSet.minZoom = 3;
229
+ TileSets.add(fakeTileSet);
230
+ const request = mockRequest('/v1/tiles/fake-vector/WebMercatorQuad/tile.json', 'get', apiKeyHeader);
210
231
 
211
232
  const res = await handleRequest(request);
212
233
  o(res.status).equals(200);
213
- o(res.header('etag')).equals('BBfQpNXA3Q90jlFrLSOZhxbvfOh7OpN1OEE+BylpbHw=');
214
- const out = JSON.parse(Buffer.from(res.body ?? '', 'base64').toString());
215
- o(out.tiles[0].startsWith('https://tiles.test/v1/tiles/tile.json/undefined/{z}/{x}/{y}.pbf?api=')).equals(true);
216
- o(request.logContext['cache']).deepEquals(undefined);
234
+
235
+ const body = Buffer.from(res.body ?? '', 'base64').toString();
236
+ o(JSON.parse(body)).deepEquals({
237
+ tiles: [`https://tiles.test/v1/tiles/fake-vector/EPSG:3857/{z}/{x}/{y}.pbf?api=${apiKey}`],
238
+ maxzoom: 15,
239
+ minzoom: 3,
240
+ tilejson: '3.0.0',
241
+ });
217
242
  });
218
243
 
219
- o('should serve tile json for tile_set', async () => {
220
- const request = mockRequest('/v1/tiles/topographic/Google/tile.json', 'get', apiKeyHeader);
244
+ o('should serve convert zoom to tile matrix', async () => {
245
+ const fakeTileSet = new FakeTileSetVector('fake-vector', Nztm2000QuadTms);
246
+ fakeTileSet.tileSet.maxZoom = 15;
247
+ fakeTileSet.tileSet.minZoom = 1;
248
+ TileSets.add(fakeTileSet);
249
+
250
+ const request = mockRequest('/v1/tiles/fake-vector/NZTM2000Quad/tile.json', 'get', apiKeyHeader);
221
251
 
222
252
  const res = await handleRequest(request);
223
253
  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
254
 
227
255
  const body = Buffer.from(res.body ?? '', 'base64').toString();
228
256
  o(JSON.parse(body)).deepEquals({
229
- tiles: [`https://tiles.test/v1/tiles/topographic/Google/{z}/{x}/{y}.pbf?api=${apiKey}`],
257
+ tiles: [`https://tiles.test/v1/tiles/fake-vector/NZTM2000Quad/{z}/{x}/{y}.pbf?api=${apiKey}`],
258
+ maxzoom: 13,
230
259
  minzoom: 0,
231
- maxzoom: 15,
232
- format: 'pbf',
233
- tilejson: '2.0.0',
260
+ tilejson: '3.0.0',
234
261
  });
235
262
  });
236
263
  });
@@ -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 { TileSetRaster } from '../tile.set.raster.js';
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.PNG;
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],
@@ -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.PNG, x: 252, y: 156, z: 8 },
18
- { type: TileType.Tile, name: 'health', tileMatrix: Nztm2000QuadTms, ext: ImageFormat.PNG, x: 30, y: 33, z: 6 },
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,67 @@
1
+ import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
2
+ import { Config, 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 } 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(400, `Invalid s3 path: ${path}`);
30
+ const role = await RoleRegister.findRole(path);
31
+ if (role == null) return new LambdaHttpResponse(403, 'Unable to Access the s3 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.outputLocation.path = fsa.join(ctx.outputLocation.path, id);
44
+
45
+ // Insert Processing job config
46
+ jobConfig = {
47
+ id: jobId,
48
+ name: path,
49
+ status: 'processing',
50
+ } as ConfigProcessingJob;
51
+
52
+ if (Config.ProcessingJob.isWriteable()) await Config.ProcessingJob.put(jobConfig);
53
+ else return new LambdaHttpResponse(403, 'Unable to insert the Processing Job config');
54
+
55
+ // Start processing job
56
+ await CogJobFactory.create(ctx);
57
+ }
58
+
59
+ const json = JSON.stringify(jobConfig);
60
+ const data = Buffer.from(json);
61
+
62
+ const response = new LambdaHttpResponse(200, 'ok');
63
+ response.header(HttpHeader.CacheControl, 'no-store');
64
+ response.buffer(data, 'application/json');
65
+ req.set('bytes', data.byteLength);
66
+ return response;
67
+ }
@@ -1,43 +1,42 @@
1
- import { Env } from '@basemaps/shared';
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 { NotModified } from './response.js';
6
- import { TileEtag } from './tile.etag.js';
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 tileJson: TileJson = {
23
- tiles: [tileUrl],
24
- minzoom: 0,
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 json = JSON.stringify(tileJson);
28
+ const tileJson: TileJson = { tiles: [tileUrl], 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
- const data = Buffer.from(json);
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
- if (TileEtag.isNotModified(req, cacheKey)) return NotModified;
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.ETag, cacheKey);
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;
@@ -75,7 +75,7 @@ export class TileSetCache {
75
75
  }
76
76
 
77
77
  const ts = new TileSetVector(name, tileMatrix);
78
- ts.tileSet = tileSet;
78
+ await ts.init(tileSet);
79
79
  this.tileSets.set(tileSetId, ts);
80
80
  return ts;
81
81
  }
@@ -97,7 +97,11 @@ export class TileSetCache {
97
97
  if (nameComp.layer != null) {
98
98
  parent.components.name = nameComp.name;
99
99
  } else if (parent.imagery != null && parent.imagery.size > 1) {
100
- for (const imageId of parent.imagery.keys()) tileSets.push(parent.child(imageId));
100
+ for (const imageId of parent.imagery.keys()) {
101
+ const childImg = parent.child(imageId);
102
+ if (childImg == null) continue;
103
+ tileSets.push(childImg);
104
+ }
101
105
  }
102
106
  }
103
107
  return tileSets.sort((a, b) => a.title.localeCompare(b.title));
@@ -6,8 +6,8 @@ import {
6
6
  TileSetNameParser,
7
7
  TileSetType,
8
8
  } from '@basemaps/config';
9
- import { Bounds, Epsg, Tile, TileMatrixSet, TileMatrixSets } from '@basemaps/geo';
10
- import { Config, Env, fsa, LogType, TileDataXyz, titleizeImageryName, VectorFormat } from '@basemaps/shared';
9
+ import { Bounds, Epsg, ImageFormat, Tile, TileMatrixSet, TileMatrixSets, VectorFormat } from '@basemaps/geo';
10
+ import { Config, Env, fsa, LogType, TileDataXyz, titleizeImageryName } from '@basemaps/shared';
11
11
  import { Tiler } from '@basemaps/tiler';
12
12
  import { TileMakerSharp } from '@basemaps/tiler-sharp';
13
13
  import { CogTiff } from '@cogeotiff/core';
@@ -83,6 +83,11 @@ export class TileSetRaster {
83
83
  return this.extentOverride ?? this.tileMatrix.extent;
84
84
  }
85
85
 
86
+ /** Preferred default imagery format */
87
+ get format(): ImageFormat {
88
+ return this.tileSet.format ?? ImageFormat.Webp;
89
+ }
90
+
86
91
  async init(record: ConfigTileSetRaster): Promise<void> {
87
92
  this.tileSet = record;
88
93
  this.imagery = await Config.getAllImagery(this.tileSet.layers, this.tileMatrix.projection);
@@ -208,11 +213,9 @@ export class TileSetRaster {
208
213
  return null;
209
214
  }
210
215
 
211
- child(imgId: string): TileSetRaster {
216
+ child(imgId: string): TileSetRaster | null {
212
217
  const image = this.findImagery(imgId);
213
- if (image == null) {
214
- throw new Error('Failed to create child tile set ' + this.fullName + ' Missing imagery ' + imgId);
215
- }
218
+ if (image == null) return null;
216
219
  const childName = TileSetNameParser.componentsToName({ ...this.components, layer: image.name });
217
220
  const child = new TileSetRaster(childName, this.tileMatrix);
218
221
  // use parent data as prototype for child;
@@ -1,6 +1,6 @@
1
1
  import { ConfigTileSetVector, TileSetNameComponents, TileSetNameParser, TileSetType } from '@basemaps/config';
2
- import { TileMatrixSet } from '@basemaps/geo';
3
- import { fsa, TileDataXyz, VectorFormat } from '@basemaps/shared';
2
+ import { TileMatrixSet, VectorFormat } from '@basemaps/geo';
3
+ import { fsa, TileDataXyz } from '@basemaps/shared';
4
4
  import { Cotar } from '@cotar/core';
5
5
  import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
6
6
  import { NotFound } from './routes/response.js';
@@ -34,6 +34,15 @@ export class TileSetVector {
34
34
  this.tileMatrix = tileMatrix;
35
35
  }
36
36
 
37
+ async init(record: ConfigTileSetVector): Promise<void> {
38
+ this.tileSet = record;
39
+ }
40
+
41
+ /** What format does tile set use */
42
+ get format(): VectorFormat {
43
+ return VectorFormat.MapboxVectorTiles;
44
+ }
45
+
37
46
  get id(): string {
38
47
  return TileSets.id(this.fullName, this.tileMatrix);
39
48
  }
@@ -23,7 +23,7 @@ function wgs84Extent(layer: TileSetRaster): BBox {
23
23
  /**
24
24
  * Default the tile matrix id to the projection of the TileMatrixSet
25
25
  */
26
- function getTileMatrixId(tileMatrix: TileMatrixSet): string {
26
+ export function getTileMatrixId(tileMatrix: TileMatrixSet): string {
27
27
  // TODO this should really change everything to identifier
28
28
  if (tileMatrix.identifier === Nztm2000QuadTms.identifier) return Nztm2000QuadTms.identifier;
29
29
  return tileMatrix.projection.toEpsgString();