@basemaps/lambda-tiler 6.24.2 → 6.27.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 (52) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/build/__test__/tile.cache.key.test.js +2 -3
  3. package/build/__test__/wmts.capability.test.js +36 -9
  4. package/build/__test__/xyz.test.js +51 -28
  5. package/build/__test__/xyz.util.d.ts +4 -0
  6. package/build/__test__/xyz.util.d.ts.map +1 -1
  7. package/build/__test__/xyz.util.js +8 -1
  8. package/build/cli/dump.js +2 -3
  9. package/build/index.d.ts +3 -1
  10. package/build/index.d.ts.map +1 -1
  11. package/build/index.js +2 -1
  12. package/build/routes/__test__/attribution.test.js +1 -0
  13. package/build/routes/__test__/health.test.js +1 -2
  14. package/build/routes/__test__/wmts.test.d.ts +2 -0
  15. package/build/routes/__test__/wmts.test.d.ts.map +1 -0
  16. package/build/routes/__test__/wmts.test.js +31 -0
  17. package/build/routes/health.d.ts.map +1 -1
  18. package/build/routes/health.js +3 -4
  19. package/build/routes/tile.json.d.ts +0 -7
  20. package/build/routes/tile.json.d.ts.map +1 -1
  21. package/build/routes/tile.json.js +26 -18
  22. package/build/routes/tile.wmts.d.ts +2 -0
  23. package/build/routes/tile.wmts.d.ts.map +1 -1
  24. package/build/routes/tile.wmts.js +23 -1
  25. package/build/tile.set.cache.js +2 -2
  26. package/build/tile.set.raster.d.ts +3 -1
  27. package/build/tile.set.raster.d.ts.map +1 -1
  28. package/build/tile.set.raster.js +8 -4
  29. package/build/tile.set.vector.d.ts +4 -1
  30. package/build/tile.set.vector.d.ts.map +1 -1
  31. package/build/tile.set.vector.js +11 -1
  32. package/build/wmts.capability.d.ts +15 -5
  33. package/build/wmts.capability.d.ts.map +1 -1
  34. package/build/wmts.capability.js +11 -12
  35. package/package.json +10 -10
  36. package/src/__test__/tile.cache.key.test.ts +3 -3
  37. package/src/__test__/wmts.capability.test.ts +39 -8
  38. package/src/__test__/xyz.test.ts +55 -28
  39. package/src/__test__/xyz.util.ts +10 -2
  40. package/src/cli/dump.ts +2 -3
  41. package/src/index.ts +2 -1
  42. package/src/routes/__test__/attribution.test.ts +1 -0
  43. package/src/routes/__test__/health.test.ts +1 -3
  44. package/src/routes/__test__/wmts.test.ts +40 -0
  45. package/src/routes/health.ts +3 -4
  46. package/src/routes/tile.json.ts +26 -27
  47. package/src/routes/tile.wmts.ts +23 -2
  48. package/src/tile.set.cache.ts +2 -2
  49. package/src/tile.set.raster.ts +8 -4
  50. package/src/tile.set.vector.ts +12 -2
  51. package/src/wmts.capability.ts +20 -15
  52. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,40 @@
1
+ import { ImageFormat } from '@basemaps/geo';
2
+ import { LogConfig } from '@basemaps/shared';
3
+ import { LambdaHttpRequest, LambdaUrlRequest } from '@linzjs/lambda';
4
+ import { Context } from 'aws-lambda';
5
+ import o from 'ospec';
6
+ import { getImageFormats } from '../tile.wmts.js';
7
+
8
+ o.spec('GetImageFormats', () => {
9
+ function newRequest(path: string, query: string): LambdaHttpRequest {
10
+ return new LambdaUrlRequest(
11
+ {
12
+ requestContext: { http: { method: 'GET' } },
13
+ headers: {},
14
+ rawPath: path,
15
+ rawQueryString: query,
16
+ isBase64Encoded: false,
17
+ } as any,
18
+ {} as Context,
19
+ LogConfig.get(),
20
+ );
21
+ }
22
+
23
+ o('should parse all formats', () => {
24
+ const req = newRequest('/v1/blank', 'format=png&format=jpeg');
25
+ const formats = getImageFormats(req);
26
+ o(formats).deepEquals([ImageFormat.Png, ImageFormat.Jpeg]);
27
+ });
28
+
29
+ o('should ignore bad formats', () => {
30
+ const req = newRequest('/v1/blank', 'format=fake&format=mvt');
31
+ const formats = getImageFormats(req);
32
+ o(formats).equals(undefined);
33
+ });
34
+
35
+ o('should de-dupe formats', () => {
36
+ const req = newRequest('/v1/blank', 'format=png&format=jpeg&format=png&format=jpeg&format=png&format=jpeg');
37
+ const formats = getImageFormats(req);
38
+ o(formats).deepEquals([ImageFormat.Png, ImageFormat.Jpeg]);
39
+ });
40
+ });
@@ -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
 
@@ -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;
@@ -1,6 +1,7 @@
1
1
  import { Config, TileSetType } from '@basemaps/config';
2
- import { TileMatrixSet } from '@basemaps/geo';
2
+ import { ImageFormat, TileMatrixSet } from '@basemaps/geo';
3
3
  import { Env, TileSetName, tileWmtsFromPath } from '@basemaps/shared';
4
+ import { getImageFormat } from '@basemaps/tiler';
4
5
  import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
5
6
  import { createHash } from 'crypto';
6
7
  import { Router } from '../router.js';
@@ -10,6 +11,20 @@ import { WmtsCapabilities } from '../wmts.capability.js';
10
11
  import { NotFound, NotModified } from './response.js';
11
12
  import { TileEtag } from './tile.etag.js';
12
13
 
14
+ export function getImageFormats(req: LambdaHttpRequest): ImageFormat[] | undefined {
15
+ const formats = req.query.getAll('format');
16
+ if (formats == null || formats.length === 0) return undefined;
17
+
18
+ const output: Set<ImageFormat> = new Set();
19
+ for (const fmt of formats) {
20
+ const parsed = getImageFormat(fmt);
21
+ if (parsed == null) continue;
22
+ output.add(parsed);
23
+ }
24
+ if (output.size === 0) return undefined;
25
+ return [...output.values()];
26
+ }
27
+
13
28
  /**
14
29
  * Serve a WMTS request
15
30
  *
@@ -32,7 +47,13 @@ export async function wmts(req: LambdaHttpRequest): Promise<LambdaHttpResponse>
32
47
  if (provider == null) return NotFound;
33
48
 
34
49
  const apiKey = Router.apiKey(req);
35
- const xml = WmtsCapabilities.toXml(host, provider, tileSets, apiKey);
50
+ const xml = new WmtsCapabilities({
51
+ httpBase: host,
52
+ provider,
53
+ layers: tileSets,
54
+ apiKey,
55
+ formats: getImageFormats(req),
56
+ }).toXml();
36
57
  if (xml == null) return NotFound;
37
58
 
38
59
  const data = Buffer.from(xml);
@@ -63,7 +63,7 @@ export class TileSetCache {
63
63
 
64
64
  // If we already have a copy and it hasn't been modified just return it
65
65
  const existing = this.tileSets.get(tileSetId);
66
- if (existing?.tileSet.updatedAt === tileSet.updatedAt) {
66
+ if (existing != null && existing?.tileSet.updatedAt === tileSet.updatedAt) {
67
67
  return existing;
68
68
  }
69
69
 
@@ -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
  }
@@ -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';
@@ -20,7 +20,7 @@ import { St } from './source.tracer.js';
20
20
  import { TiffCache } from './tiff.cache.js';
21
21
  import { TileSets } from './tile.set.cache.js';
22
22
 
23
- const LoadingQueue = pLimit(Env.getNumber(Env.TiffConcurrency, 5));
23
+ const LoadingQueue = pLimit(Env.getNumber(Env.TiffConcurrency, 25));
24
24
 
25
25
  export const TileComposer = new TileMakerSharp(256);
26
26
 
@@ -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);
@@ -160,7 +165,6 @@ export class TileSetRaster {
160
165
 
161
166
  const imagery = this.imagery.get(imgId);
162
167
  if (imagery == null) {
163
- console.log('Failed', { imagery, i: this.imagery, ts: this.tileSet.layers });
164
168
  log?.warn(
165
169
  { layer: layer.name, projection: this.tileMatrix.projection.code, imgId },
166
170
  'Failed to lookup imagery',
@@ -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 { GoogleTms, 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
  }
@@ -44,6 +53,7 @@ export class TileSetVector {
44
53
 
45
54
  async tile(req: LambdaHttpRequest, xyz: TileDataXyz): Promise<LambdaHttpResponse> {
46
55
  if (xyz.ext !== VectorFormat.MapboxVectorTiles) return NotFound;
56
+ if (xyz.tileMatrix.identifier !== GoogleTms.identifier) return NotFound;
47
57
  if (this.tileSet.layers.length > 1) return new LambdaHttpResponse(500, 'Too many layers in tileset');
48
58
  const [layer] = this.tileSet.layers;
49
59
  if (layer[3857] == null) return new LambdaHttpResponse(500, 'Layer url not found from tileset Config');
@@ -1,5 +1,4 @@
1
- import { ConfigProvider } from '@basemaps/config';
2
- import { Bounds, Nztm2000QuadTms, TileMatrixSet, WmtsProvider } from '@basemaps/geo';
1
+ import { Bounds, ImageFormat, Nztm2000QuadTms, TileMatrixSet, WmtsProvider } from '@basemaps/geo';
3
2
  import { Projection, V, VNodeElement } from '@basemaps/shared';
4
3
  import { ImageFormatOrder } from '@basemaps/tiler';
5
4
  import { BBox, Wgs84 } from '@linzjs/geojson';
@@ -23,12 +22,20 @@ function wgs84Extent(layer: TileSetRaster): BBox {
23
22
  /**
24
23
  * Default the tile matrix id to the projection of the TileMatrixSet
25
24
  */
26
- function getTileMatrixId(tileMatrix: TileMatrixSet): string {
25
+ export function getTileMatrixId(tileMatrix: TileMatrixSet): string {
27
26
  // TODO this should really change everything to identifier
28
27
  if (tileMatrix.identifier === Nztm2000QuadTms.identifier) return Nztm2000QuadTms.identifier;
29
28
  return tileMatrix.projection.toEpsgString();
30
29
  }
31
30
 
31
+ export interface WmtsCapabilitiesParams {
32
+ httpBase: string;
33
+ provider: WmtsProvider;
34
+ layers: TileSetRaster[];
35
+ apiKey?: string;
36
+ formats?: ImageFormat[];
37
+ }
38
+
32
39
  export class WmtsCapabilities {
33
40
  httpBase: string;
34
41
  provider: WmtsProvider;
@@ -37,12 +44,13 @@ export class WmtsCapabilities {
37
44
 
38
45
  apiKey?: string;
39
46
  tileMatrixSets = new Map<string, TileMatrixSet>();
47
+ formats: ImageFormat[];
40
48
 
41
- constructor(httpBase: string, provider: WmtsProvider, layers: TileSetRaster[], apiKey?: string) {
42
- this.httpBase = httpBase;
43
- this.provider = provider;
49
+ constructor(params: WmtsCapabilitiesParams) {
50
+ this.httpBase = params.httpBase;
51
+ this.provider = params.provider;
44
52
 
45
- for (const layer of layers) {
53
+ for (const layer of params.layers) {
46
54
  // TODO is grouping by name the best option
47
55
  let existing = this.layers.get(layer.fullName);
48
56
  if (existing == null) {
@@ -54,7 +62,8 @@ export class WmtsCapabilities {
54
62
 
55
63
  this.tileMatrixSets.set(layer.tileMatrix.identifier, layer.tileMatrix);
56
64
  }
57
- this.apiKey = apiKey;
65
+ this.apiKey = params.apiKey;
66
+ this.formats = params.formats ?? ImageFormatOrder;
58
67
  }
59
68
 
60
69
  buildWgs84BoundingBox(layers: TileSetRaster[], tagName = 'ows:WGS84BoundingBox'): VNodeElement {
@@ -153,9 +162,9 @@ export class WmtsCapabilities {
153
162
  ...layers.map((layer) => this.buildBoundingBox(layer.tileMatrix, layer.extent)),
154
163
  this.buildWgs84BoundingBox(layers),
155
164
  this.buildStyle(),
156
- ...ImageFormatOrder.map((fmt) => V('Format', 'image/' + fmt)),
165
+ ...this.formats.map((fmt) => V('Format', 'image/' + fmt)),
157
166
  ...matrixSetNodes,
158
- ...ImageFormatOrder.map((fmt) => this.buildResourceUrl(firstLayer, fmt)),
167
+ ...this.formats.map((fmt) => this.buildResourceUrl(firstLayer, fmt)),
159
168
  ]);
160
169
  }
161
170
 
@@ -193,11 +202,7 @@ export class WmtsCapabilities {
193
202
  return V('Capabilities', CapabilitiesAttrs, [...this.buildProvider(), V('Contents', layers)]);
194
203
  }
195
204
 
196
- toString(): string {
205
+ toXml(): string {
197
206
  return '<?xml version="1.0"?>\n' + this.toVNode().toString();
198
207
  }
199
-
200
- static toXml(httpBase: string, provider: ConfigProvider, tileSet: TileSetRaster[], apiKey?: string): string {
201
- return new WmtsCapabilities(httpBase, provider, tileSet, apiKey).toString();
202
- }
203
208
  }