@basemaps/lambda-tiler 7.7.0 → 7.10.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 (51) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/build/__tests__/config.data.js +3 -3
  3. package/build/__tests__/config.data.js.map +1 -1
  4. package/build/__tests__/tile.style.json.test.js +13 -12
  5. package/build/__tests__/tile.style.json.test.js.map +1 -1
  6. package/build/routes/__tests__/health.test.js +40 -20
  7. package/build/routes/__tests__/health.test.js.map +1 -1
  8. package/build/routes/__tests__/tile.style.json.test.js +81 -0
  9. package/build/routes/__tests__/tile.style.json.test.js.map +1 -1
  10. package/build/routes/__tests__/xyz.test.js +13 -0
  11. package/build/routes/__tests__/xyz.test.js.map +1 -1
  12. package/build/routes/health.d.ts +17 -0
  13. package/build/routes/health.js +119 -21
  14. package/build/routes/health.js.map +1 -1
  15. package/build/routes/tile.style.json.d.ts +36 -8
  16. package/build/routes/tile.style.json.js +144 -128
  17. package/build/routes/tile.style.json.js.map +1 -1
  18. package/build/routes/tile.xyz.raster.js.map +1 -1
  19. package/build/routes/tile.xyz.vector.js +9 -9
  20. package/build/routes/tile.xyz.vector.js.map +1 -1
  21. package/build/util/__test__/cache.test.d.ts +1 -0
  22. package/build/util/__test__/cache.test.js +29 -0
  23. package/build/util/__test__/cache.test.js.map +1 -0
  24. package/build/util/__test__/nztm.style.test.d.ts +1 -0
  25. package/build/util/__test__/nztm.style.test.js +87 -0
  26. package/build/util/__test__/nztm.style.test.js.map +1 -0
  27. package/build/util/nztm.style.d.ts +12 -0
  28. package/build/util/nztm.style.js +45 -0
  29. package/build/util/nztm.style.js.map +1 -0
  30. package/build/util/source.cache.d.ts +2 -8
  31. package/build/util/source.cache.js +6 -24
  32. package/build/util/source.cache.js.map +1 -1
  33. package/build/util/swapping.lru.d.ts +1 -0
  34. package/build/util/swapping.lru.js +4 -0
  35. package/build/util/swapping.lru.js.map +1 -1
  36. package/package.json +7 -6
  37. package/src/__tests__/config.data.ts +3 -3
  38. package/src/__tests__/tile.style.json.test.ts +16 -14
  39. package/src/routes/__tests__/health.test.ts +46 -22
  40. package/src/routes/__tests__/tile.style.json.test.ts +91 -0
  41. package/src/routes/__tests__/xyz.test.ts +18 -0
  42. package/src/routes/health.ts +129 -21
  43. package/src/routes/tile.style.json.ts +172 -149
  44. package/src/routes/tile.xyz.raster.ts +0 -1
  45. package/src/routes/tile.xyz.vector.ts +10 -6
  46. package/src/util/__test__/cache.test.ts +36 -0
  47. package/src/util/__test__/nztm.style.test.ts +100 -0
  48. package/src/util/nztm.style.ts +44 -0
  49. package/src/util/source.cache.ts +10 -20
  50. package/src/util/swapping.lru.ts +5 -0
  51. package/tsconfig.tsbuildinfo +1 -1
@@ -1,13 +1,47 @@
1
1
  import * as fs from 'node:fs';
2
+ import { TileSetType } from '@basemaps/config';
2
3
  import { GoogleTms, Nztm2000QuadTms } from '@basemaps/geo';
3
4
  import { HttpHeader, LambdaHttpResponse } from '@linzjs/lambda';
5
+ import { VectorTile } from '@mapbox/vector-tile';
6
+ import Protobuf from 'pbf';
4
7
  import PixelMatch from 'pixelmatch';
5
8
  import Sharp from 'sharp';
9
+ import { gunzipSync } from 'zlib';
6
10
  import { ConfigLoader } from '../util/config.loader.js';
11
+ import { isGzip } from '../util/cotar.serve.js';
7
12
  import { TileXyzRaster } from './tile.xyz.raster.js';
13
+ import { tileXyzVector } from './tile.xyz.vector.js';
8
14
  export const TestTiles = [
9
15
  { tileSet: 'health', tileMatrix: GoogleTms, tileType: 'png', tile: { x: 252, y: 156, z: 8 } },
10
16
  { tileSet: 'health', tileMatrix: Nztm2000QuadTms, tileType: 'png', tile: { x: 30, y: 33, z: 6 } },
17
+ {
18
+ tileSet: 'topographic',
19
+ tileMatrix: GoogleTms,
20
+ tileType: 'pbf',
21
+ tile: { x: 1009, y: 641, z: 10 },
22
+ testFeatures: [
23
+ { layer: 'aeroway', key: 'name', value: 'Wellington Airport' },
24
+ { layer: 'place', key: 'name', value: 'Wellington' },
25
+ { layer: 'coastline', key: 'class', value: 'coastline' },
26
+ { layer: 'landcover', key: 'class', value: 'grass' },
27
+ { layer: 'poi', key: 'name', value: 'Seatoun Wharf' },
28
+ { layer: 'transportation', key: 'name', value: 'Mt Victoria Tunnel' },
29
+ ],
30
+ },
31
+ {
32
+ tileSet: 'topographic',
33
+ tileMatrix: GoogleTms,
34
+ tileType: 'pbf',
35
+ tile: { x: 62, y: 40, z: 6 },
36
+ testFeatures: [
37
+ { layer: 'landuse', key: 'name', value: 'Queenstown' },
38
+ { layer: 'place', key: 'name', value: 'Christchurch' },
39
+ { layer: 'water', key: 'name', value: 'Tasman Lake' },
40
+ { layer: 'coastline', key: 'class', value: 'coastline' },
41
+ { layer: 'landcover', key: 'class', value: 'wood' },
42
+ { layer: 'transportation', key: 'name', value: 'STATE HIGHWAY 6' },
43
+ ],
44
+ },
11
45
  ];
12
46
  const TileSize = 256;
13
47
  export async function getTestBuffer(test) {
@@ -27,6 +61,80 @@ export async function updateExpectedTile(test, newTileData, difference) {
27
61
  .toBuffer();
28
62
  await fs.promises.writeFile(`${expectedFileName}.diff.png`, imgPng);
29
63
  }
64
+ /**
65
+ * Compare and validate the raster test tile from server with pixel match
66
+ */
67
+ async function validateRasterTile(tileSet, test, req) {
68
+ // Get the parse response tile to raw buffer
69
+ const response = await TileXyzRaster.tile(req, tileSet, test);
70
+ if (response.status !== 200)
71
+ throw new LambdaHttpResponse(500, response.statusDescription);
72
+ if (!Buffer.isBuffer(response._body))
73
+ throw new LambdaHttpResponse(500, 'Not a Buffer response content.');
74
+ const resImgBuffer = await Sharp(response._body).raw().toBuffer();
75
+ // Get test tile to compare
76
+ const testBuffer = await getTestBuffer(test);
77
+ test.buf = testBuffer;
78
+ const testImgBuffer = await Sharp(testBuffer).raw().toBuffer();
79
+ const outputBuffer = Buffer.alloc(testImgBuffer.length);
80
+ const missMatchedPixels = PixelMatch(testImgBuffer, resImgBuffer, outputBuffer, TileSize, TileSize);
81
+ if (missMatchedPixels) {
82
+ /** Uncomment this to overwite the expected files */
83
+ // await updateExpectedTile(test, response._body as Buffer, outputBuffer);
84
+ req.log.error({ missMatchedPixels, projection: test.tileMatrix.identifier, xyz: test.tile }, 'Health:MissMatch');
85
+ throw new LambdaHttpResponse(500, 'TileSet does not match.');
86
+ }
87
+ }
88
+ function checkFeatureExists(tile, testFeature) {
89
+ const layer = tile.layers[testFeature.layer];
90
+ for (let i = 0; i < layer.length; i++) {
91
+ const feature = layer.feature(i);
92
+ if (feature.properties[testFeature.key] === testFeature.value)
93
+ return true;
94
+ }
95
+ return false;
96
+ }
97
+ /**
98
+ * Fetch vector tile and decode into mapbox VectorTile
99
+ */
100
+ export const VectorTileProvider = {
101
+ async getVectorTile(tileSet, test, req) {
102
+ // Get the parse response tile to raw buffer
103
+ const response = await tileXyzVector.tile(req, tileSet, test);
104
+ if (response.status !== 200)
105
+ throw new LambdaHttpResponse(500, response.statusDescription);
106
+ if (!Buffer.isBuffer(response._body))
107
+ throw new LambdaHttpResponse(500, 'Not a Buffer response content.');
108
+ const buffer = isGzip(response._body) ? gunzipSync(response._body) : response._body;
109
+ return new VectorTile(new Protobuf(buffer));
110
+ },
111
+ };
112
+ /**
113
+ * Check the existence of a feature property in side the vector tile
114
+ *
115
+ * @throws LambdaHttpResponse if any test feature not found from vector tile
116
+ */
117
+ function featureCheck(tile, testTile) {
118
+ const testTileName = `${testTile.tileSet}-${testTile.tile.x}/${testTile.tile.y}/z${testTile.tile.z}`;
119
+ if (testTile.testFeatures == null) {
120
+ throw new LambdaHttpResponse(500, `No test feature found from testTile: ${testTileName}`);
121
+ }
122
+ for (const testFeature of testTile.testFeatures) {
123
+ if (!checkFeatureExists(tile, testFeature)) {
124
+ throw new LambdaHttpResponse(500, `Failed to validate tile: ${testTileName} for layer: ${testFeature.layer}.`);
125
+ }
126
+ }
127
+ }
128
+ /**
129
+ * Health check the test vector tiles that contains all the expected features.
130
+ *
131
+ * @throws LambdaHttpResponse if test tiles not returned or features not exists
132
+ */
133
+ async function validateVectorTile(tileSet, test, req) {
134
+ // Get the parse response tile to raw buffer
135
+ const tile = await VectorTileProvider.getVectorTile(tileSet, test, req);
136
+ featureCheck(tile, test);
137
+ }
30
138
  /**
31
139
  * Health request get health TileSets and validate with test TileSets
32
140
  * - Valid response from get heath tile request
@@ -36,28 +144,18 @@ export async function updateExpectedTile(test, newTileData, difference) {
36
144
  */
37
145
  export async function healthGet(req) {
38
146
  const config = await ConfigLoader.load(req);
39
- const tileSet = await config.TileSet.get(config.TileSet.id('health'));
40
- if (tileSet == null)
41
- throw new LambdaHttpResponse(500, 'TileSet: "health" not found');
42
147
  for (const test of TestTiles) {
43
- // Get the parse response tile to raw buffer
44
- const response = await TileXyzRaster.tile(req, tileSet, test);
45
- if (response.status !== 200)
46
- return new LambdaHttpResponse(500, response.statusDescription);
47
- if (!Buffer.isBuffer(response._body))
48
- throw new LambdaHttpResponse(500, 'Not a Buffer response content.');
49
- const resImgBuffer = await Sharp(response._body).raw().toBuffer();
50
- // Get test tile to compare
51
- const testBuffer = await getTestBuffer(test);
52
- test.buf = testBuffer;
53
- const testImgBuffer = await Sharp(testBuffer).raw().toBuffer();
54
- const outputBuffer = Buffer.alloc(testImgBuffer.length);
55
- const missMatchedPixels = PixelMatch(testImgBuffer, resImgBuffer, outputBuffer, TileSize, TileSize);
56
- if (missMatchedPixels) {
57
- /** Uncomment this to overwite the expected files */
58
- // await updateExpectedTile(test, response._body as Buffer, outputBuffer);
59
- req.log.error({ missMatchedPixels, projection: test.tileMatrix.identifier, xyz: test.tile }, 'Health:MissMatch');
60
- return new LambdaHttpResponse(500, 'TileSet does not match.');
148
+ const tileSet = await config.TileSet.get(config.TileSet.id(test.tileSet));
149
+ if (tileSet == null)
150
+ throw new LambdaHttpResponse(500, `TileSet: ${test.tileSet} not found`);
151
+ if (tileSet.type === TileSetType.Raster) {
152
+ await validateRasterTile(tileSet, test, req);
153
+ }
154
+ else if (tileSet.type === TileSetType.Vector) {
155
+ await validateVectorTile(tileSet, test, req);
156
+ }
157
+ else {
158
+ throw new LambdaHttpResponse(500, `Invalid TileSet type for tileSet ${test.tileSet}`);
61
159
  }
62
160
  }
63
161
  // Return Ok response when all health test passed.
@@ -1 +1 @@
1
- {"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/routes/health.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/routes/health.ts"],"names":[],"mappings":"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"}
@@ -1,25 +1,53 @@
1
- import { ConfigTileSetRaster, Layer, StyleJson } from '@basemaps/config';
1
+ import { ConfigTileSetRaster, StyleJson } from '@basemaps/config';
2
2
  import { TileMatrixSet } from '@basemaps/geo';
3
3
  import { LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
4
4
  /**
5
- * Convert relative URLS into a full hostname url
5
+ * Convert relative URL into a full hostname URL, converting {tileMatrix} into the provided tileMatrix
6
+ *
7
+ * Will also add query parameters of apiKey and configuration if provided
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * convertRelativeUrl("/v1/tiles/aerial/{tileMatrix}/{z}/{x}/{y}.webp", NZTM2000Quad)
12
+ * "https://basemaps.linz.govt.nz/v1/tiles/aerial/NZTM2000Quad/{z}/{x}/{y}.webp?api=c..."
13
+ * ```
14
+ *
6
15
  * @param url possible url to update
7
16
  * @param apiKey ApiKey to append with ?api= if required
8
- * @returns Updated Url or empty string if url is empty
17
+ * @param tileMatrix replace {tileMatrix} with the tile matrix
18
+ *
19
+ * @returns Updated URL or empty string if url is empty
9
20
  */
10
21
  export declare function convertRelativeUrl(url?: string, tileMatrix?: TileMatrixSet, apiKey?: string, config?: string | null): string;
11
22
  /**
12
- * Create a new style json that has absolute urls to the current host and API Keys where required
23
+ * Update the style JSON to have absolute urls to the current host and API Keys where required
24
+ *
13
25
  * @param style style to update
26
+ * @param tileMatrix convert the tile matrix to the target tile matrix
14
27
  * @param apiKey api key to inject
15
- * @returns new stylejson
28
+ * @param config optional configuration url to use
29
+ * @param layers replace the layers in the style json
30
+ * @returns new style JSON
16
31
  */
17
- export declare function convertStyleJson(style: StyleJson, tileMatrix: TileMatrixSet, apiKey: string, config: string | null, layers?: Layer[]): StyleJson;
32
+ export declare function setStyleUrls(style: StyleJson, tileMatrix: TileMatrixSet, apiKey: string, config: string | null): void;
33
+ export interface StyleConfig {
34
+ /** Name of the terrain layer */
35
+ terrain?: string | null;
36
+ /** Combine layer with the labels layer */
37
+ labels: boolean;
38
+ }
39
+ /**
40
+ * Generate a StyleJSON from a tileset
41
+ * @returns
42
+ */
43
+ export declare function tileSetToStyle(req: LambdaHttpRequest<StyleGet>, tileSet: ConfigTileSetRaster, tileMatrix: TileMatrixSet, apiKey: string): StyleJson;
44
+ /**
45
+ * generate a style from a tile set which has a output
46
+ */
47
+ export declare function tileSetOutputToStyle(req: LambdaHttpRequest<StyleGet>, tileSet: ConfigTileSetRaster, tileMatrix: TileMatrixSet, apiKey: string): StyleJson;
18
48
  export interface StyleGet {
19
49
  Params: {
20
50
  styleName: string;
21
51
  };
22
52
  }
23
- export declare function tileSetToStyle(req: LambdaHttpRequest<StyleGet>, tileSet: ConfigTileSetRaster, tileMatrix: TileMatrixSet, apiKey: string, terrain?: string): Promise<LambdaHttpResponse>;
24
- export declare function tileSetOutputToStyle(req: LambdaHttpRequest<StyleGet>, tileSet: ConfigTileSetRaster, tileMatrix: TileMatrixSet, apiKey: string, terrain?: string): Promise<LambdaHttpResponse>;
25
53
  export declare function styleJsonGet(req: LambdaHttpRequest<StyleGet>): Promise<LambdaHttpResponse>;
@@ -1,18 +1,30 @@
1
- import { ConfigId, ConfigPrefix, TileSetType } from '@basemaps/config';
1
+ import { ConfigId, ConfigPrefix, TileSetType, } from '@basemaps/config';
2
2
  import { DefaultExaggeration } from '@basemaps/config/build/config/vector.style.js';
3
- import { GoogleTms, TileMatrixSets } from '@basemaps/geo';
3
+ import { GoogleTms, Nztm2000QuadTms, TileMatrixSets } from '@basemaps/geo';
4
4
  import { Env, toQueryString } from '@basemaps/shared';
5
5
  import { HttpHeader, LambdaHttpResponse } from '@linzjs/lambda';
6
6
  import { URL } from 'url';
7
7
  import { ConfigLoader } from '../util/config.loader.js';
8
8
  import { Etag } from '../util/etag.js';
9
+ import { convertStyleToNztmStyle } from '../util/nztm.style.js';
9
10
  import { NotFound, NotModified } from '../util/response.js';
10
11
  import { Validate } from '../util/validate.js';
11
12
  /**
12
- * Convert relative URLS into a full hostname url
13
+ * Convert relative URL into a full hostname URL, converting {tileMatrix} into the provided tileMatrix
14
+ *
15
+ * Will also add query parameters of apiKey and configuration if provided
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * convertRelativeUrl("/v1/tiles/aerial/{tileMatrix}/{z}/{x}/{y}.webp", NZTM2000Quad)
20
+ * "https://basemaps.linz.govt.nz/v1/tiles/aerial/NZTM2000Quad/{z}/{x}/{y}.webp?api=c..."
21
+ * ```
22
+ *
13
23
  * @param url possible url to update
14
24
  * @param apiKey ApiKey to append with ?api= if required
15
- * @returns Updated Url or empty string if url is empty
25
+ * @param tileMatrix replace {tileMatrix} with the tile matrix
26
+ *
27
+ * @returns Updated URL or empty string if url is empty
16
28
  */
17
29
  export function convertRelativeUrl(url, tileMatrix, apiKey, config) {
18
30
  if (url == null)
@@ -30,18 +42,18 @@ export function convertRelativeUrl(url, tileMatrix, apiKey, config) {
30
42
  return fullUrl.toString().replace(/%7B/g, '{').replace(/%7D/g, '}');
31
43
  }
32
44
  /**
33
- * Create a new style json that has absolute urls to the current host and API Keys where required
45
+ * Update the style JSON to have absolute urls to the current host and API Keys where required
46
+ *
34
47
  * @param style style to update
48
+ * @param tileMatrix convert the tile matrix to the target tile matrix
35
49
  * @param apiKey api key to inject
36
- * @returns new stylejson
50
+ * @param config optional configuration url to use
51
+ * @param layers replace the layers in the style json
52
+ * @returns new style JSON
37
53
  */
38
- export function convertStyleJson(style, tileMatrix, apiKey, config, layers) {
39
- const sources = JSON.parse(JSON.stringify(style.sources));
40
- for (const [key, value] of Object.entries(sources)) {
54
+ export function setStyleUrls(style, tileMatrix, apiKey, config) {
55
+ for (const [key, value] of Object.entries(style.sources ?? {})) {
41
56
  if (value.type === 'vector') {
42
- if (tileMatrix !== GoogleTms) {
43
- throw new LambdaHttpResponse(400, `TileMatrix is not supported for the vector source ${value.url}.`);
44
- }
45
57
  value.url = convertRelativeUrl(value.url, tileMatrix, apiKey, config);
46
58
  }
47
59
  else if ((value.type === 'raster' || value.type === 'raster-dem') && Array.isArray(value.tiles)) {
@@ -49,52 +61,80 @@ export function convertStyleJson(style, tileMatrix, apiKey, config, layers) {
49
61
  value.tiles[i] = convertRelativeUrl(value.tiles[i], tileMatrix, apiKey, config);
50
62
  }
51
63
  }
52
- sources[key] = value;
64
+ style.sources[key] = value;
53
65
  }
54
- const styleJson = {
55
- version: 8,
56
- id: style.id,
57
- name: style.name,
58
- sources,
59
- layers: layers ? layers : style.layers,
60
- };
61
- if (style.metadata)
62
- styleJson.metadata = style.metadata;
63
66
  if (style.glyphs)
64
- styleJson.glyphs = convertRelativeUrl(style.glyphs, undefined, undefined, config);
67
+ style.glyphs = convertRelativeUrl(style.glyphs, undefined, undefined, config);
65
68
  if (style.sprite)
66
- styleJson.sprite = convertRelativeUrl(style.sprite, undefined, undefined, config);
67
- if (style.sky)
68
- styleJson.sky = style.sky;
69
- return styleJson;
69
+ style.sprite = convertRelativeUrl(style.sprite, undefined, undefined, config);
70
70
  }
71
+ /**
72
+ * Turn on the terrain setting in the style json
73
+ */
71
74
  function setStyleTerrain(style, terrain, tileMatrix) {
72
75
  const source = Object.keys(style.sources).find((s) => s === terrain);
73
76
  if (source == null)
74
- throw new LambdaHttpResponse(400, `Terrain: ${terrain} is not exists in the style source.`);
77
+ throw new LambdaHttpResponse(400, `Terrain: ${terrain} does not exists in the style source.`);
75
78
  style.terrain = {
76
79
  source,
77
80
  exaggeration: DefaultExaggeration[tileMatrix.identifier] ?? DefaultExaggeration[GoogleTms.identifier],
78
81
  };
79
82
  }
80
- async function ensureTerrain(req, tileMatrix, apiKey, style) {
83
+ /**
84
+ * Merge the "labels" layer into the style json
85
+ */
86
+ async function setStyleLabels(req, style) {
81
87
  const config = await ConfigLoader.load(req);
82
- const terrain = await config.TileSet.get('ts_elevation');
83
- if (terrain) {
84
- const configLocation = ConfigLoader.extract(req);
85
- const elevationQuery = toQueryString({ config: configLocation, api: apiKey, pipeline: 'terrain-rgb' });
86
- style.sources['LINZ-Terrain'] = {
87
- type: 'raster-dem',
88
- tileSize: 256,
89
- maxzoom: 18,
90
- tiles: [convertRelativeUrl(`/v1/tiles/elevation/${tileMatrix.identifier}/{z}/{x}/{y}.png${elevationQuery}`)],
91
- };
88
+ const labels = await config.Style.get('labels');
89
+ if (labels == null) {
90
+ req.log.warn('LabelsStyle:Missing');
91
+ return;
92
+ }
93
+ const layerId = new Set();
94
+ for (const l of style.layers)
95
+ layerId.add(l.id);
96
+ for (const newLayers of labels.style.layers) {
97
+ if (layerId.has(newLayers.id)) {
98
+ throw new LambdaHttpResponse(400, 'Cannot merge styles with duplicate layerIds: ' + newLayers.id);
99
+ }
92
100
  }
101
+ if (style.glyphs == null)
102
+ style.glyphs = labels.style.glyphs;
103
+ if (style.sprite == null)
104
+ style.sprite = labels.style.sprite;
105
+ if (style.sky == null)
106
+ style.sky = labels.style.sky;
107
+ Object.assign(style.sources, labels.style.sources);
108
+ style.layers = style.layers.concat(labels.style.layers);
93
109
  }
94
- export async function tileSetToStyle(req, tileSet, tileMatrix, apiKey, terrain) {
110
+ /**
111
+ * Ensure that a "LINZ-Terrain" layer is force added into the output styleJSON source
112
+ */
113
+ async function ensureTerrain(req, tileMatrix, apiKey, style) {
114
+ const config = await ConfigLoader.load(req);
115
+ const terrain = await config.TileSet.get('elevation');
116
+ if (terrain == null)
117
+ return;
118
+ const configLocation = ConfigLoader.extract(req);
119
+ const elevationQuery = toQueryString({ config: configLocation, api: apiKey, pipeline: 'terrain-rgb' });
120
+ style.sources['LINZ-Terrain'] = {
121
+ type: 'raster-dem',
122
+ tileSize: 256,
123
+ maxzoom: 18, // TODO: this should be configurable based on the elevation layer
124
+ tiles: [convertRelativeUrl(`/v1/tiles/elevation/${tileMatrix.identifier}/{z}/{x}/{y}.png${elevationQuery}`)],
125
+ };
126
+ }
127
+ /**
128
+ * Generate a StyleJSON from a tileset
129
+ * @returns
130
+ */
131
+ export function tileSetToStyle(req, tileSet, tileMatrix, apiKey) {
132
+ // If the style has outputs defined it has a different process for generating the stylejson
133
+ if (tileSet.outputs)
134
+ return tileSetOutputToStyle(req, tileSet, tileMatrix, apiKey);
95
135
  const [tileFormat] = Validate.getRequestedFormats(req) ?? ['webp'];
96
136
  if (tileFormat == null)
97
- return new LambdaHttpResponse(400, 'Invalid image format');
137
+ throw new LambdaHttpResponse(400, 'Invalid image format');
98
138
  const pipeline = Validate.pipeline(tileSet, tileFormat, req.query.get('pipeline'));
99
139
  const pipelineName = pipeline?.name === 'rgba' ? undefined : pipeline?.name;
100
140
  const configLocation = ConfigLoader.extract(req);
@@ -102,132 +142,108 @@ export async function tileSetToStyle(req, tileSet, tileMatrix, apiKey, terrain)
102
142
  const tileUrl = (Env.get(Env.PublicUrlBase) ?? '') +
103
143
  `/v1/tiles/${tileSet.name}/${tileMatrix.identifier}/{z}/{x}/{y}.${tileFormat}${query}`;
104
144
  const styleId = `basemaps-${tileSet.name}`;
105
- const style = {
145
+ return {
106
146
  id: ConfigId.prefix(ConfigPrefix.Style, tileSet.name),
107
147
  name: tileSet.name,
108
148
  version: 8,
109
149
  sources: { [styleId]: { type: 'raster', tiles: [tileUrl], tileSize: 256 } },
110
150
  layers: [{ id: styleId, type: 'raster', source: styleId }],
111
151
  };
112
- // Ensure elevation for individual tilesets
113
- await ensureTerrain(req, tileMatrix, apiKey, style);
114
- // Add terrain in style
115
- if (terrain)
116
- setStyleTerrain(style, terrain, tileMatrix);
117
- const data = Buffer.from(JSON.stringify(style));
118
- const cacheKey = Etag.key(data);
119
- if (Etag.isNotModified(req, cacheKey))
120
- return NotModified();
121
- const response = new LambdaHttpResponse(200, 'ok');
122
- response.header(HttpHeader.ETag, cacheKey);
123
- response.header(HttpHeader.CacheControl, 'no-store');
124
- response.buffer(data, 'application/json');
125
- req.set('bytes', data.byteLength);
126
- return response;
127
152
  }
128
- export async function tileSetOutputToStyle(req, tileSet, tileMatrix, apiKey, terrain) {
153
+ /**
154
+ * generate a style from a tile set which has a output
155
+ */
156
+ export function tileSetOutputToStyle(req, tileSet, tileMatrix, apiKey) {
157
+ if (tileSet.outputs == null)
158
+ throw new LambdaHttpResponse(400, 'TileSet does not have any outputs to generate');
129
159
  const configLocation = ConfigLoader.extract(req);
130
- const query = toQueryString({ config: configLocation, api: apiKey });
131
160
  const styleId = `basemaps-${tileSet.name}`;
132
161
  const sources = {};
133
162
  const layers = [];
134
- if (tileSet.outputs) {
135
- //for loop output.
136
- for (const output of tileSet.outputs) {
137
- const format = output.format?.[0] ?? 'webp';
138
- const urlBase = Env.get(Env.PublicUrlBase) ?? '';
139
- const tileUrl = `${urlBase}/v1/tiles/${tileSet.name}/${tileMatrix.identifier}/{z}/{x}/{y}.${format}${query}`;
140
- if (output.name === 'terrain-rgb') {
141
- // Add both raster source and dem raster source for terrain-rgb output
142
- sources[`${styleId}-${output.name}`] = {
143
- type: 'raster',
144
- tiles: [tileUrl + `&pipeline=${output.name}`],
145
- tileSize: 256,
146
- };
147
- sources[`${styleId}-${output.name}-dem`] = {
148
- type: 'raster-dem',
149
- tiles: [tileUrl + `&pipeline=${output.name}`],
150
- tileSize: 256,
151
- };
152
- }
153
- else {
154
- // Add raster source other outputs
155
- sources[`${styleId}-${output.name}`] = {
156
- type: 'raster',
157
- tiles: [tileUrl + `&pipeline=${output.name}`],
158
- tileSize: 256,
159
- };
160
- }
163
+ for (const output of tileSet.outputs) {
164
+ const format = output.format?.[0] ?? 'webp';
165
+ const urlBase = Env.get(Env.PublicUrlBase) ?? '';
166
+ const query = toQueryString({ config: configLocation, api: apiKey, pipeline: output.name });
167
+ const tileUrl = `${urlBase}/v1/tiles/${tileSet.name}/${tileMatrix.identifier}/{z}/{x}/{y}.${format}${query}`;
168
+ if (output.name === 'terrain-rgb') {
169
+ // Add both raster source and dem raster source for terrain-rgb output
170
+ sources[`${styleId}-${output.name}`] = { type: 'raster', tiles: [tileUrl], tileSize: 256 };
171
+ sources[`${styleId}-${output.name}-dem`] = { type: 'raster-dem', tiles: [tileUrl], tileSize: 256 };
172
+ }
173
+ else {
174
+ // Add raster source other outputs
175
+ sources[`${styleId}-${output.name}`] = { type: 'raster', tiles: [tileUrl], tileSize: 256 };
161
176
  }
162
177
  }
163
178
  // Add first raster source as default layer
164
179
  for (const source of Object.keys(sources)) {
165
180
  if (sources[source].type === 'raster') {
166
- layers.push({
167
- id: styleId,
168
- type: 'raster',
169
- source,
170
- });
181
+ layers.push({ id: styleId, type: 'raster', source });
171
182
  break;
172
183
  }
173
184
  }
174
- const style = {
185
+ return {
175
186
  id: ConfigId.prefix(ConfigPrefix.Style, tileSet.name),
176
187
  name: tileSet.name,
177
188
  version: 8,
178
189
  sources,
179
190
  layers,
180
191
  };
181
- // Ensure elevation for style json config
182
- await ensureTerrain(req, tileMatrix, apiKey, style);
183
- // Add terrain in style
184
- if (terrain)
185
- setStyleTerrain(style, terrain, tileMatrix);
186
- const data = Buffer.from(JSON.stringify(style));
187
- const cacheKey = Etag.key(data);
188
- if (Etag.isNotModified(req, cacheKey))
189
- return Promise.resolve(NotModified());
190
- const response = new LambdaHttpResponse(200, 'ok');
191
- response.header(HttpHeader.ETag, cacheKey);
192
- response.header(HttpHeader.CacheControl, 'no-store');
193
- response.buffer(data, 'application/json');
194
- req.set('bytes', data.byteLength);
195
- return Promise.resolve(response);
192
+ }
193
+ async function generateStyleFromTileSet(req, config, tileSetName, tileMatrix, apiKey) {
194
+ const tileSet = await config.TileSet.get(tileSetName);
195
+ if (tileSet == null)
196
+ throw NotFound();
197
+ if (tileSet.type !== TileSetType.Raster) {
198
+ throw new LambdaHttpResponse(400, 'Only raster tile sets can generate style JSON');
199
+ }
200
+ if (tileSet.outputs)
201
+ return tileSetOutputToStyle(req, tileSet, tileMatrix, apiKey);
202
+ else
203
+ return tileSetToStyle(req, tileSet, tileMatrix, apiKey);
196
204
  }
197
205
  export async function styleJsonGet(req) {
198
206
  const apiKey = Validate.apiKey(req);
199
207
  const styleName = req.params.styleName;
200
- const excludeLayers = req.query.getAll('exclude');
201
- const excluded = new Set(excludeLayers.map((l) => l.toLowerCase()));
202
208
  const tileMatrix = TileMatrixSets.find(req.query.get('tileMatrix') ?? GoogleTms.identifier);
203
209
  if (tileMatrix == null)
204
210
  return new LambdaHttpResponse(400, 'Invalid tile matrix');
211
+ // Remove layers from the output style json
212
+ const excludeLayers = req.query.getAll('exclude');
213
+ const excluded = new Set(excludeLayers.map((l) => l.toLowerCase()));
214
+ if (excluded.size > 0)
215
+ req.set('excludedLayers', [...excluded]);
216
+ /**
217
+ * Configuration options used for the landing page:
218
+ * "terrain" - force add a terrain layer
219
+ * "labels" - merge the labels style with the current style
220
+ *
221
+ * TODO: (2024-08) this is not a very scalable way of configuring styles, it would be good to provide a styleJSON merge
222
+ */
205
223
  const terrain = req.query.get('terrain') ?? undefined;
224
+ const labels = Boolean(req.query.get('labels') ?? false);
225
+ req.set('styleConfig', { terrain, labels });
206
226
  // Get style Config from db
207
227
  const config = await ConfigLoader.load(req);
208
- const dbId = config.Style.id(styleName);
209
- const styleConfig = await config.Style.get(dbId);
210
- if (styleConfig == null) {
211
- // Were we given a tileset name instead, generated
212
- const tileSet = await config.TileSet.get(config.TileSet.id(styleName));
213
- if (tileSet == null)
214
- return NotFound();
215
- if (tileSet.type !== TileSetType.Raster)
216
- return NotFound();
217
- if (tileSet.outputs)
218
- return await tileSetOutputToStyle(req, tileSet, tileMatrix, apiKey, terrain);
219
- else
220
- return await tileSetToStyle(req, tileSet, tileMatrix, apiKey, terrain);
221
- }
222
- // Prepare sources and add linz source
223
- const style = convertStyleJson(styleConfig.style, tileMatrix, apiKey, ConfigLoader.extract(req), styleConfig.style.layers.filter((f) => !excluded.has(f.id.toLowerCase())));
228
+ const styleConfig = await config.Style.get(styleName);
229
+ const styleSource = styleConfig?.style ?? (await generateStyleFromTileSet(req, config, styleName, tileMatrix, apiKey));
230
+ const targetStyle = structuredClone(styleSource);
224
231
  // Ensure elevation for style json config
225
232
  // TODO: We should remove this after adding terrain source into style configs. PR-916
226
- await ensureTerrain(req, tileMatrix, apiKey, style);
233
+ await ensureTerrain(req, tileMatrix, apiKey, targetStyle);
227
234
  // Add terrain in style
228
235
  if (terrain)
229
- setStyleTerrain(style, terrain, tileMatrix);
230
- const data = Buffer.from(JSON.stringify(style));
236
+ setStyleTerrain(targetStyle, terrain, tileMatrix);
237
+ if (labels)
238
+ await setStyleLabels(req, targetStyle);
239
+ // convert sources to full URLS and convert style between projections
240
+ setStyleUrls(targetStyle, tileMatrix, apiKey, ConfigLoader.extract(req));
241
+ if (tileMatrix.identifier === Nztm2000QuadTms.identifier)
242
+ convertStyleToNztmStyle(targetStyle, false);
243
+ // filter out any excluded layers
244
+ if (excluded.size > 0)
245
+ targetStyle.layers = targetStyle.layers.filter((f) => !excluded.has(f.id.toLowerCase()));
246
+ const data = Buffer.from(JSON.stringify(targetStyle));
231
247
  const cacheKey = Etag.key(data);
232
248
  if (Etag.isNotModified(req, cacheKey))
233
249
  return NotModified();