@basemaps/lambda-tiler 7.9.0 → 7.11.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 (36) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/build/__tests__/tile.style.json.test.js +13 -7
  3. package/build/__tests__/tile.style.json.test.js.map +1 -1
  4. package/build/routes/__tests__/health.test.js +40 -20
  5. package/build/routes/__tests__/health.test.js.map +1 -1
  6. package/build/routes/__tests__/tile.style.json.test.js +57 -0
  7. package/build/routes/__tests__/tile.style.json.test.js.map +1 -1
  8. package/build/routes/__tests__/xyz.test.js +13 -0
  9. package/build/routes/__tests__/xyz.test.js.map +1 -1
  10. package/build/routes/attribution.js.map +1 -1
  11. package/build/routes/health.d.ts +17 -0
  12. package/build/routes/health.js +119 -21
  13. package/build/routes/health.js.map +1 -1
  14. package/build/routes/tile.style.json.d.ts +35 -13
  15. package/build/routes/tile.style.json.js +108 -123
  16. package/build/routes/tile.style.json.js.map +1 -1
  17. package/build/routes/tile.xyz.vector.js +9 -9
  18. package/build/routes/tile.xyz.vector.js.map +1 -1
  19. package/build/util/__test__/nztm.style.test.d.ts +1 -0
  20. package/build/util/__test__/nztm.style.test.js +87 -0
  21. package/build/util/__test__/nztm.style.test.js.map +1 -0
  22. package/build/util/nztm.style.d.ts +12 -0
  23. package/build/util/nztm.style.js +45 -0
  24. package/build/util/nztm.style.js.map +1 -0
  25. package/package.json +11 -10
  26. package/src/__tests__/tile.style.json.test.ts +16 -7
  27. package/src/routes/__tests__/health.test.ts +46 -22
  28. package/src/routes/__tests__/tile.style.json.test.ts +60 -0
  29. package/src/routes/__tests__/xyz.test.ts +18 -0
  30. package/src/routes/attribution.ts +3 -0
  31. package/src/routes/health.ts +129 -21
  32. package/src/routes/tile.style.json.ts +131 -145
  33. package/src/routes/tile.xyz.vector.ts +10 -6
  34. package/src/util/__test__/nztm.style.test.ts +100 -0
  35. package/src/util/nztm.style.ts +44 -0
  36. 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,31 +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;
18
- export interface StyleGet {
19
- Params: {
20
- styleName: string;
21
- };
22
- }
32
+ export declare function setStyleUrls(style: StyleJson, tileMatrix: TileMatrixSet, apiKey: string, config: string | null): void;
23
33
  export interface StyleConfig {
24
34
  /** Name of the terrain layer */
25
35
  terrain?: string | null;
26
36
  /** Combine layer with the labels layer */
27
37
  labels: boolean;
28
38
  }
29
- export declare function tileSetToStyle(req: LambdaHttpRequest<StyleGet>, tileSet: ConfigTileSetRaster, tileMatrix: TileMatrixSet, apiKey: string, cfg: StyleConfig): Promise<LambdaHttpResponse>;
30
- export declare function tileSetOutputToStyle(req: LambdaHttpRequest<StyleGet>, tileSet: ConfigTileSetRaster, tileMatrix: TileMatrixSet, apiKey: string, cfg: StyleConfig): Promise<LambdaHttpResponse>;
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;
48
+ export interface StyleGet {
49
+ Params: {
50
+ styleName: string;
51
+ };
52
+ }
31
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,14 +42,17 @@ 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
57
  value.url = convertRelativeUrl(value.url, tileMatrix, apiKey, config);
43
58
  }
@@ -46,36 +61,28 @@ export function convertStyleJson(style, tileMatrix, apiKey, config, layers) {
46
61
  value.tiles[i] = convertRelativeUrl(value.tiles[i], tileMatrix, apiKey, config);
47
62
  }
48
63
  }
49
- sources[key] = value;
64
+ style.sources[key] = value;
50
65
  }
51
- const styleJson = {
52
- version: 8,
53
- id: style.id,
54
- name: style.name,
55
- sources,
56
- layers: layers ? layers : style.layers,
57
- };
58
- if (style.metadata)
59
- styleJson.metadata = style.metadata;
60
66
  if (style.glyphs)
61
- styleJson.glyphs = convertRelativeUrl(style.glyphs, undefined, undefined, config);
67
+ style.glyphs = convertRelativeUrl(style.glyphs, undefined, undefined, config);
62
68
  if (style.sprite)
63
- styleJson.sprite = convertRelativeUrl(style.sprite, undefined, undefined, config);
64
- if (style.sky)
65
- styleJson.sky = style.sky;
66
- if (style.terrain)
67
- styleJson.terrain = style.terrain;
68
- return styleJson;
69
+ style.sprite = convertRelativeUrl(style.sprite, undefined, undefined, config);
69
70
  }
71
+ /**
72
+ * Turn on the terrain setting in the style json
73
+ */
70
74
  function setStyleTerrain(style, terrain, tileMatrix) {
71
75
  const source = Object.keys(style.sources).find((s) => s === terrain);
72
76
  if (source == null)
73
- 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.`);
74
78
  style.terrain = {
75
79
  source,
76
80
  exaggeration: DefaultExaggeration[tileMatrix.identifier] ?? DefaultExaggeration[GoogleTms.identifier],
77
81
  };
78
82
  }
83
+ /**
84
+ * Merge the "labels" layer into the style json
85
+ */
79
86
  async function setStyleLabels(req, style) {
80
87
  const config = await ConfigLoader.load(req);
81
88
  const labels = await config.Style.get('labels');
@@ -100,6 +107,9 @@ async function setStyleLabels(req, style) {
100
107
  Object.assign(style.sources, labels.style.sources);
101
108
  style.layers = style.layers.concat(labels.style.layers);
102
109
  }
110
+ /**
111
+ * Ensure that a "LINZ-Terrain" layer is force added into the output styleJSON source
112
+ */
103
113
  async function ensureTerrain(req, tileMatrix, apiKey, style) {
104
114
  const config = await ConfigLoader.load(req);
105
115
  const terrain = await config.TileSet.get('elevation');
@@ -110,14 +120,21 @@ async function ensureTerrain(req, tileMatrix, apiKey, style) {
110
120
  style.sources['LINZ-Terrain'] = {
111
121
  type: 'raster-dem',
112
122
  tileSize: 256,
113
- maxzoom: 18,
123
+ maxzoom: 18, // TODO: this should be configurable based on the elevation layer
114
124
  tiles: [convertRelativeUrl(`/v1/tiles/elevation/${tileMatrix.identifier}/{z}/{x}/{y}.png${elevationQuery}`)],
115
125
  };
116
126
  }
117
- export async function tileSetToStyle(req, tileSet, tileMatrix, apiKey, cfg) {
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);
118
135
  const [tileFormat] = Validate.getRequestedFormats(req) ?? ['webp'];
119
136
  if (tileFormat == null)
120
- return new LambdaHttpResponse(400, 'Invalid image format');
137
+ throw new LambdaHttpResponse(400, 'Invalid image format');
121
138
  const pipeline = Validate.pipeline(tileSet, tileFormat, req.query.get('pipeline'));
122
139
  const pipelineName = pipeline?.name === 'rgba' ? undefined : pipeline?.name;
123
140
  const configLocation = ConfigLoader.extract(req);
@@ -125,140 +142,108 @@ export async function tileSetToStyle(req, tileSet, tileMatrix, apiKey, cfg) {
125
142
  const tileUrl = (Env.get(Env.PublicUrlBase) ?? '') +
126
143
  `/v1/tiles/${tileSet.name}/${tileMatrix.identifier}/{z}/{x}/{y}.${tileFormat}${query}`;
127
144
  const styleId = `basemaps-${tileSet.name}`;
128
- const style = {
145
+ return {
129
146
  id: ConfigId.prefix(ConfigPrefix.Style, tileSet.name),
130
147
  name: tileSet.name,
131
148
  version: 8,
132
149
  sources: { [styleId]: { type: 'raster', tiles: [tileUrl], tileSize: 256 } },
133
150
  layers: [{ id: styleId, type: 'raster', source: styleId }],
134
151
  };
135
- // Ensure elevation for individual tilesets
136
- await ensureTerrain(req, tileMatrix, apiKey, style);
137
- // Add terrain in style
138
- if (cfg.terrain)
139
- setStyleTerrain(style, cfg.terrain, tileMatrix);
140
- if (cfg.labels)
141
- await setStyleLabels(req, style);
142
- const data = Buffer.from(JSON.stringify(convertStyleJson(style, tileMatrix, apiKey, configLocation)));
143
- const cacheKey = Etag.key(data);
144
- if (Etag.isNotModified(req, cacheKey))
145
- return NotModified();
146
- const response = new LambdaHttpResponse(200, 'ok');
147
- response.header(HttpHeader.ETag, cacheKey);
148
- response.header(HttpHeader.CacheControl, 'no-store');
149
- response.buffer(data, 'application/json');
150
- req.set('bytes', data.byteLength);
151
- return response;
152
152
  }
153
- export async function tileSetOutputToStyle(req, tileSet, tileMatrix, apiKey, cfg) {
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');
154
159
  const configLocation = ConfigLoader.extract(req);
155
- const query = toQueryString({ config: configLocation, api: apiKey });
156
160
  const styleId = `basemaps-${tileSet.name}`;
157
161
  const sources = {};
158
162
  const layers = [];
159
- if (tileSet.outputs) {
160
- //for loop output.
161
- for (const output of tileSet.outputs) {
162
- const format = output.format?.[0] ?? 'webp';
163
- const urlBase = Env.get(Env.PublicUrlBase) ?? '';
164
- const tileUrl = `${urlBase}/v1/tiles/${tileSet.name}/${tileMatrix.identifier}/{z}/{x}/{y}.${format}${query}`;
165
- if (output.name === 'terrain-rgb') {
166
- // Add both raster source and dem raster source for terrain-rgb output
167
- sources[`${styleId}-${output.name}`] = {
168
- type: 'raster',
169
- tiles: [tileUrl + `&pipeline=${output.name}`],
170
- tileSize: 256,
171
- };
172
- sources[`${styleId}-${output.name}-dem`] = {
173
- type: 'raster-dem',
174
- tiles: [tileUrl + `&pipeline=${output.name}`],
175
- tileSize: 256,
176
- };
177
- }
178
- else {
179
- // Add raster source other outputs
180
- sources[`${styleId}-${output.name}`] = {
181
- type: 'raster',
182
- tiles: [tileUrl + `&pipeline=${output.name}`],
183
- tileSize: 256,
184
- };
185
- }
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 };
186
176
  }
187
177
  }
188
178
  // Add first raster source as default layer
189
179
  for (const source of Object.keys(sources)) {
190
180
  if (sources[source].type === 'raster') {
191
- layers.push({
192
- id: styleId,
193
- type: 'raster',
194
- source,
195
- });
181
+ layers.push({ id: styleId, type: 'raster', source });
196
182
  break;
197
183
  }
198
184
  }
199
- const style = {
185
+ return {
200
186
  id: ConfigId.prefix(ConfigPrefix.Style, tileSet.name),
201
187
  name: tileSet.name,
202
188
  version: 8,
203
189
  sources,
204
190
  layers,
205
191
  };
206
- // Ensure elevation for style json config
207
- await ensureTerrain(req, tileMatrix, apiKey, style);
208
- // Add terrain in style
209
- if (cfg.terrain)
210
- setStyleTerrain(style, cfg.terrain, tileMatrix);
211
- if (cfg.labels)
212
- await setStyleLabels(req, style);
213
- const data = Buffer.from(JSON.stringify(convertStyleJson(style, tileMatrix, apiKey, configLocation)));
214
- const cacheKey = Etag.key(data);
215
- if (Etag.isNotModified(req, cacheKey))
216
- return Promise.resolve(NotModified());
217
- const response = new LambdaHttpResponse(200, 'ok');
218
- response.header(HttpHeader.ETag, cacheKey);
219
- response.header(HttpHeader.CacheControl, 'no-store');
220
- response.buffer(data, 'application/json');
221
- req.set('bytes', data.byteLength);
222
- 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);
223
204
  }
224
205
  export async function styleJsonGet(req) {
225
206
  const apiKey = Validate.apiKey(req);
226
207
  const styleName = req.params.styleName;
227
- const excludeLayers = req.query.getAll('exclude');
228
- const excluded = new Set(excludeLayers.map((l) => l.toLowerCase()));
229
208
  const tileMatrix = TileMatrixSets.find(req.query.get('tileMatrix') ?? GoogleTms.identifier);
230
209
  if (tileMatrix == null)
231
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
+ */
232
223
  const terrain = req.query.get('terrain') ?? undefined;
233
224
  const labels = Boolean(req.query.get('labels') ?? false);
225
+ req.set('styleConfig', { terrain, labels });
234
226
  // Get style Config from db
235
227
  const config = await ConfigLoader.load(req);
236
- const dbId = config.Style.id(styleName);
237
- const styleConfig = await config.Style.get(dbId);
238
- req.set('styleConfig', { terrain, labels });
239
- if (styleConfig == null) {
240
- // Were we given a tileset name instead, generated
241
- const tileSet = await config.TileSet.get(config.TileSet.id(styleName));
242
- if (tileSet == null)
243
- return NotFound();
244
- if (tileSet.type !== TileSetType.Raster)
245
- return NotFound();
246
- if (tileSet.outputs)
247
- return await tileSetOutputToStyle(req, tileSet, tileMatrix, apiKey, { terrain, labels });
248
- else
249
- return await tileSetToStyle(req, tileSet, tileMatrix, apiKey, { terrain, labels });
250
- }
251
- // Prepare sources and add linz source
252
- 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);
253
231
  // Ensure elevation for style json config
254
232
  // TODO: We should remove this after adding terrain source into style configs. PR-916
255
- await ensureTerrain(req, tileMatrix, apiKey, style);
233
+ await ensureTerrain(req, tileMatrix, apiKey, targetStyle);
256
234
  // Add terrain in style
257
235
  if (terrain)
258
- setStyleTerrain(style, terrain, tileMatrix);
236
+ setStyleTerrain(targetStyle, terrain, tileMatrix);
259
237
  if (labels)
260
- await setStyleLabels(req, style);
261
- const data = Buffer.from(JSON.stringify(style));
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));
262
247
  const cacheKey = Etag.key(data);
263
248
  if (Etag.isNotModified(req, cacheKey))
264
249
  return NotModified();