@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.
- package/CHANGELOG.md +32 -0
- package/build/__tests__/config.data.js +3 -3
- package/build/__tests__/config.data.js.map +1 -1
- package/build/__tests__/tile.style.json.test.js +13 -12
- package/build/__tests__/tile.style.json.test.js.map +1 -1
- package/build/routes/__tests__/health.test.js +40 -20
- package/build/routes/__tests__/health.test.js.map +1 -1
- package/build/routes/__tests__/tile.style.json.test.js +81 -0
- package/build/routes/__tests__/tile.style.json.test.js.map +1 -1
- package/build/routes/__tests__/xyz.test.js +13 -0
- package/build/routes/__tests__/xyz.test.js.map +1 -1
- package/build/routes/health.d.ts +17 -0
- package/build/routes/health.js +119 -21
- package/build/routes/health.js.map +1 -1
- package/build/routes/tile.style.json.d.ts +36 -8
- package/build/routes/tile.style.json.js +144 -128
- package/build/routes/tile.style.json.js.map +1 -1
- package/build/routes/tile.xyz.raster.js.map +1 -1
- package/build/routes/tile.xyz.vector.js +9 -9
- package/build/routes/tile.xyz.vector.js.map +1 -1
- package/build/util/__test__/cache.test.d.ts +1 -0
- package/build/util/__test__/cache.test.js +29 -0
- package/build/util/__test__/cache.test.js.map +1 -0
- package/build/util/__test__/nztm.style.test.d.ts +1 -0
- package/build/util/__test__/nztm.style.test.js +87 -0
- package/build/util/__test__/nztm.style.test.js.map +1 -0
- package/build/util/nztm.style.d.ts +12 -0
- package/build/util/nztm.style.js +45 -0
- package/build/util/nztm.style.js.map +1 -0
- package/build/util/source.cache.d.ts +2 -8
- package/build/util/source.cache.js +6 -24
- package/build/util/source.cache.js.map +1 -1
- package/build/util/swapping.lru.d.ts +1 -0
- package/build/util/swapping.lru.js +4 -0
- package/build/util/swapping.lru.js.map +1 -1
- package/package.json +7 -6
- package/src/__tests__/config.data.ts +3 -3
- package/src/__tests__/tile.style.json.test.ts +16 -14
- package/src/routes/__tests__/health.test.ts +46 -22
- package/src/routes/__tests__/tile.style.json.test.ts +91 -0
- package/src/routes/__tests__/xyz.test.ts +18 -0
- package/src/routes/health.ts +129 -21
- package/src/routes/tile.style.json.ts +172 -149
- package/src/routes/tile.xyz.raster.ts +0 -1
- package/src/routes/tile.xyz.vector.ts +10 -6
- package/src/util/__test__/cache.test.ts +36 -0
- package/src/util/__test__/nztm.style.test.ts +100 -0
- package/src/util/nztm.style.ts +44 -0
- package/src/util/source.cache.ts +10 -20
- package/src/util/swapping.lru.ts +5 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -4,13 +4,15 @@ import { afterEach, before, beforeEach, describe, it } from 'node:test';
|
|
|
4
4
|
import { ConfigProviderMemory } from '@basemaps/config';
|
|
5
5
|
import { LogConfig } from '@basemaps/shared';
|
|
6
6
|
import { LambdaAlbRequest, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
|
|
7
|
+
import { VectorTile, VectorTileFeature, VectorTileLayer } from '@mapbox/vector-tile';
|
|
7
8
|
import { ALBEventRequestContext, Context } from 'aws-lambda';
|
|
8
9
|
import sinon from 'sinon';
|
|
9
10
|
|
|
10
11
|
import { FakeData } from '../../__tests__/config.data.js';
|
|
11
12
|
import { ConfigLoader } from '../../util/config.loader.js';
|
|
12
|
-
import { getTestBuffer, healthGet, TestTiles } from '../health.js';
|
|
13
|
+
import { getTestBuffer, healthGet, TestFeature, TestTiles, VectorTileProvider } from '../health.js';
|
|
13
14
|
import { TileXyzRaster } from '../tile.xyz.raster.js';
|
|
15
|
+
import { tileXyzVector } from '../tile.xyz.vector.js';
|
|
14
16
|
|
|
15
17
|
const ctx: LambdaHttpRequest = new LambdaAlbRequest(
|
|
16
18
|
{
|
|
@@ -24,15 +26,38 @@ const ctx: LambdaHttpRequest = new LambdaAlbRequest(
|
|
|
24
26
|
LogConfig.get(),
|
|
25
27
|
);
|
|
26
28
|
|
|
29
|
+
// Function to create a vector tile with specific properties for testing
|
|
30
|
+
function createTestTile(testFeatures: TestFeature[]): VectorTile {
|
|
31
|
+
const layers: Record<string, VectorTileLayer> = {};
|
|
32
|
+
for (const testFeature of testFeatures) {
|
|
33
|
+
const layer = {
|
|
34
|
+
features: [{ properties: { [testFeature.key]: testFeature.value } }],
|
|
35
|
+
|
|
36
|
+
feature(i: number): VectorTileFeature {
|
|
37
|
+
return this.features[i] as VectorTileFeature;
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
get length(): number {
|
|
41
|
+
return this.features.length;
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
layers[testFeature.layer] = layer as unknown as VectorTileLayer;
|
|
46
|
+
}
|
|
47
|
+
return { layers } as unknown as VectorTile;
|
|
48
|
+
}
|
|
49
|
+
|
|
27
50
|
describe('/v1/health', () => {
|
|
28
51
|
const sandbox = sinon.createSandbox();
|
|
29
52
|
const config = new ConfigProviderMemory();
|
|
30
53
|
|
|
31
|
-
const
|
|
54
|
+
const fakeTileSetRaster = FakeData.tileSetRaster('health');
|
|
55
|
+
const fakeTileSetVector = FakeData.tileSetVector('topographic');
|
|
32
56
|
beforeEach(() => {
|
|
33
57
|
config.objects.clear();
|
|
34
58
|
sandbox.stub(ConfigLoader, 'getDefaultConfig').resolves(config);
|
|
35
|
-
config.put(
|
|
59
|
+
config.put(fakeTileSetRaster);
|
|
60
|
+
config.put(fakeTileSetVector);
|
|
36
61
|
});
|
|
37
62
|
|
|
38
63
|
afterEach(() => {
|
|
@@ -40,17 +65,14 @@ describe('/v1/health', () => {
|
|
|
40
65
|
sandbox.restore();
|
|
41
66
|
});
|
|
42
67
|
|
|
43
|
-
it('Should return bad response',
|
|
68
|
+
it('Should return bad response', () => {
|
|
44
69
|
// Given ... a bad get tile response
|
|
45
70
|
const BadResponse = new LambdaHttpResponse(500, 'Can not get Tile Set.');
|
|
46
71
|
sandbox.stub(TileXyzRaster, 'tile').resolves(BadResponse);
|
|
72
|
+
sandbox.stub(tileXyzVector, 'tile').resolves(BadResponse);
|
|
47
73
|
|
|
48
|
-
// When ...
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
// Then ...
|
|
52
|
-
assert.equal(res.status, 500);
|
|
53
|
-
assert.equal(res.statusDescription, 'Can not get Tile Set.');
|
|
74
|
+
// When ...Then ...
|
|
75
|
+
assert.rejects(() => healthGet(ctx), BadResponse);
|
|
54
76
|
});
|
|
55
77
|
|
|
56
78
|
const Response1 = new LambdaHttpResponse(200, 'ok');
|
|
@@ -66,9 +88,15 @@ describe('/v1/health', () => {
|
|
|
66
88
|
|
|
67
89
|
it('Should give a 200 response', async () => {
|
|
68
90
|
// Given ... a series good get tile response
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
91
|
+
const callbackRaster = sandbox.stub(TileXyzRaster, 'tile');
|
|
92
|
+
|
|
93
|
+
callbackRaster.onCall(0).resolves(Response1);
|
|
94
|
+
callbackRaster.onCall(1).resolves(Response2);
|
|
95
|
+
const callbackVectorTile = sandbox.stub(VectorTileProvider, 'getVectorTile');
|
|
96
|
+
const testVectorTile1 = createTestTile(TestTiles[2].testFeatures!);
|
|
97
|
+
const testVectorTile2 = createTestTile(TestTiles[3].testFeatures!);
|
|
98
|
+
callbackVectorTile.onCall(0).resolves(testVectorTile1);
|
|
99
|
+
callbackVectorTile.onCall(1).resolves(testVectorTile2);
|
|
72
100
|
|
|
73
101
|
// When ...
|
|
74
102
|
const res = await healthGet(ctx);
|
|
@@ -78,17 +106,13 @@ describe('/v1/health', () => {
|
|
|
78
106
|
assert.equal(res.statusDescription, 'ok');
|
|
79
107
|
});
|
|
80
108
|
|
|
81
|
-
it('Should return mis-match tile response',
|
|
109
|
+
it('Should return mis-match tile response', () => {
|
|
82
110
|
// Given ... a bad get tile response for second get tile
|
|
83
111
|
const callback = sandbox.stub(TileXyzRaster, 'tile');
|
|
84
|
-
callback.onCall(0).resolves(
|
|
85
|
-
callback.onCall(1).resolves(Response1);
|
|
112
|
+
callback.onCall(0).resolves(Response2);
|
|
86
113
|
|
|
87
|
-
// When ...
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
// Then ...
|
|
91
|
-
assert.equal(res.status, 500);
|
|
92
|
-
assert.equal(res.statusDescription, 'TileSet does not match.');
|
|
114
|
+
// When ... Then ...
|
|
115
|
+
const BadResponse = new LambdaHttpResponse(500, 'TileSet does not match.');
|
|
116
|
+
assert.rejects(() => healthGet(ctx), BadResponse);
|
|
93
117
|
});
|
|
94
118
|
});
|
|
@@ -380,4 +380,95 @@ describe('/v1/styles', () => {
|
|
|
380
380
|
assert.deepEqual(terrain.source, 'LINZ-Terrain');
|
|
381
381
|
assert.deepEqual(terrain.exaggeration, 1.2);
|
|
382
382
|
});
|
|
383
|
+
|
|
384
|
+
it('should set labels via parameter', async () => {
|
|
385
|
+
config.put(TileSetAerial);
|
|
386
|
+
config.put(TileSetElevation);
|
|
387
|
+
|
|
388
|
+
const fakeStyle = { id: 'st_labels', name: 'labels', style: fakeVectorStyleConfig };
|
|
389
|
+
config.put(fakeStyle);
|
|
390
|
+
|
|
391
|
+
const request = mockUrlRequest('/v1/styles/aerial.json', `?terrain=LINZ-Terrain&labels=true`, Api.header);
|
|
392
|
+
const res = await handler.router.handle(request);
|
|
393
|
+
assert.equal(res.status, 200, res.statusDescription);
|
|
394
|
+
|
|
395
|
+
const body = JSON.parse(Buffer.from(res.body, 'base64').toString()) as StyleJson;
|
|
396
|
+
const terrain = body.terrain as unknown as Terrain;
|
|
397
|
+
assert.deepEqual(terrain.source, 'LINZ-Terrain');
|
|
398
|
+
assert.deepEqual(terrain.exaggeration, 1.2);
|
|
399
|
+
|
|
400
|
+
assert.equal(body.sources['basemaps_vector']?.type, 'vector');
|
|
401
|
+
assert.equal(body.layers.length, 2);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it('should error when joining layers with duplicate ids', async () => {
|
|
405
|
+
const fakeStyle = { id: 'st_labels', name: 'labels', style: fakeVectorStyleConfig };
|
|
406
|
+
config.put(fakeStyle);
|
|
407
|
+
config.put(TileSetAerial);
|
|
408
|
+
|
|
409
|
+
const request = mockUrlRequest('/v1/styles/labels.json', `?labels=true`, Api.header);
|
|
410
|
+
const res = await handler.router.handle(request);
|
|
411
|
+
assert.equal(res.status, 400, res.statusDescription);
|
|
412
|
+
assert.equal(res.statusDescription.includes('Background1'), true);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it('should convert NZTM2000Quad styles', async () => {
|
|
416
|
+
const fakeStyle = {
|
|
417
|
+
id: 'st_labels',
|
|
418
|
+
name: 'source',
|
|
419
|
+
style: {
|
|
420
|
+
layers: [
|
|
421
|
+
{
|
|
422
|
+
minzoom: 5,
|
|
423
|
+
maxzoom: 5,
|
|
424
|
+
layout: {
|
|
425
|
+
'line-width': {
|
|
426
|
+
stops: [
|
|
427
|
+
[16, 0.75],
|
|
428
|
+
[24, 1.5],
|
|
429
|
+
],
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
|
|
433
|
+
paint: {
|
|
434
|
+
'line-width': {
|
|
435
|
+
stops: [
|
|
436
|
+
[16, 0.75],
|
|
437
|
+
[24, 1.5],
|
|
438
|
+
],
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
],
|
|
443
|
+
terrain: {
|
|
444
|
+
exaggeration: 1.1,
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
};
|
|
448
|
+
config.put(fakeStyle);
|
|
449
|
+
|
|
450
|
+
const request = mockUrlRequest('/v1/styles/labels.json', `?tileMatrix=NZTM2000Quad`, Api.header);
|
|
451
|
+
const res = await handler.router.handle(request);
|
|
452
|
+
assert.equal(res.status, 200, res.statusDescription);
|
|
453
|
+
const style = JSON.parse(Buffer.from(res.body, 'base64').toString()) as StyleJson;
|
|
454
|
+
assert.equal(style.layers[0].minzoom, 3);
|
|
455
|
+
assert.equal(style.layers[0].maxzoom, 3);
|
|
456
|
+
assert.equal(style.terrain?.exaggeration, 4.4);
|
|
457
|
+
assert.deepEqual(style.layers[0].layout, {
|
|
458
|
+
'line-width': {
|
|
459
|
+
stops: [
|
|
460
|
+
[14, 0.75],
|
|
461
|
+
[22, 1.5],
|
|
462
|
+
],
|
|
463
|
+
},
|
|
464
|
+
});
|
|
465
|
+
assert.deepEqual(style.layers[0].paint, {
|
|
466
|
+
'line-width': {
|
|
467
|
+
stops: [
|
|
468
|
+
[14, 0.75],
|
|
469
|
+
[22, 1.5],
|
|
470
|
+
],
|
|
471
|
+
},
|
|
472
|
+
});
|
|
473
|
+
});
|
|
383
474
|
});
|
|
@@ -10,6 +10,7 @@ import { Api, mockRequest, mockUrlRequest } from '../../__tests__/xyz.util.js';
|
|
|
10
10
|
import { handler } from '../../index.js';
|
|
11
11
|
import { ConfigLoader } from '../../util/config.loader.js';
|
|
12
12
|
import { Etag } from '../../util/etag.js';
|
|
13
|
+
import { CoSources } from '../../util/source.cache.js';
|
|
13
14
|
|
|
14
15
|
const TileSetNames = ['aerial', 'aerial:ōtorohanga_urban_2021_0-1m_RGB', '01FYWKAJ86W9P7RWM1VB62KD0H'];
|
|
15
16
|
describe('/v1/tiles', () => {
|
|
@@ -197,4 +198,21 @@ describe('/v1/tiles', () => {
|
|
|
197
198
|
);
|
|
198
199
|
assert.equal(resZz.status, 400, resZz.statusDescription);
|
|
199
200
|
});
|
|
201
|
+
|
|
202
|
+
it('should support NZTM2000Quad vector tiles', async (t) => {
|
|
203
|
+
t.mock.method(ConfigLoader, 'getDefaultConfig', () => Promise.resolve(config));
|
|
204
|
+
|
|
205
|
+
// Force resolve a basic tile
|
|
206
|
+
const mockGetTile = t.mock.fn(() => Promise.resolve(Buffer.from('ABC123')));
|
|
207
|
+
t.mock.method(CoSources, 'getCotar', () => Promise.resolve({ get: mockGetTile }));
|
|
208
|
+
|
|
209
|
+
const topographic = FakeData.tileSetVector('topographic');
|
|
210
|
+
topographic.layers[0][2193] = 'memory://fake-tiles.tar.co';
|
|
211
|
+
config.put(topographic);
|
|
212
|
+
const resPbf = await handler.router.handle(
|
|
213
|
+
mockUrlRequest('/v1/tiles/topographic/NZTM2000Quad/11/2022/1283.pbf', 'get', Api.header),
|
|
214
|
+
);
|
|
215
|
+
assert.equal(resPbf.status, 200);
|
|
216
|
+
assert.equal(resPbf.body, Buffer.from('ABC123').toString('base64'));
|
|
217
|
+
});
|
|
200
218
|
});
|
package/src/routes/health.ts
CHANGED
|
@@ -1,23 +1,67 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
|
|
3
|
-
import { ConfigTileSetRaster } from '@basemaps/config';
|
|
3
|
+
import { ConfigTileSetRaster, ConfigTileSetVector, TileSetType } from '@basemaps/config';
|
|
4
4
|
import { GoogleTms, Nztm2000QuadTms } from '@basemaps/geo';
|
|
5
5
|
import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
|
|
6
|
+
import { VectorTile } from '@mapbox/vector-tile';
|
|
7
|
+
import Protobuf from 'pbf';
|
|
6
8
|
import PixelMatch from 'pixelmatch';
|
|
7
9
|
import Sharp from 'sharp';
|
|
10
|
+
import { gunzipSync } from 'zlib';
|
|
8
11
|
|
|
9
12
|
import { ConfigLoader } from '../util/config.loader.js';
|
|
13
|
+
import { isGzip } from '../util/cotar.serve.js';
|
|
10
14
|
import { TileXyz } from '../util/validate.js';
|
|
11
15
|
import { TileXyzRaster } from './tile.xyz.raster.js';
|
|
16
|
+
import { tileXyzVector } from './tile.xyz.vector.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Vector feature that need to check existence
|
|
20
|
+
*/
|
|
21
|
+
export interface TestFeature {
|
|
22
|
+
layer: string;
|
|
23
|
+
key: string;
|
|
24
|
+
value: string;
|
|
25
|
+
}
|
|
12
26
|
|
|
13
27
|
interface TestTile extends TileXyz {
|
|
14
28
|
buf?: Buffer;
|
|
29
|
+
testFeatures?: TestFeature[];
|
|
15
30
|
}
|
|
16
31
|
|
|
17
32
|
export const TestTiles: TestTile[] = [
|
|
18
33
|
{ tileSet: 'health', tileMatrix: GoogleTms, tileType: 'png', tile: { x: 252, y: 156, z: 8 } },
|
|
19
34
|
{ tileSet: 'health', tileMatrix: Nztm2000QuadTms, tileType: 'png', tile: { x: 30, y: 33, z: 6 } },
|
|
35
|
+
{
|
|
36
|
+
tileSet: 'topographic',
|
|
37
|
+
tileMatrix: GoogleTms,
|
|
38
|
+
tileType: 'pbf',
|
|
39
|
+
tile: { x: 1009, y: 641, z: 10 },
|
|
40
|
+
testFeatures: [
|
|
41
|
+
{ layer: 'aeroway', key: 'name', value: 'Wellington Airport' },
|
|
42
|
+
{ layer: 'place', key: 'name', value: 'Wellington' },
|
|
43
|
+
{ layer: 'coastline', key: 'class', value: 'coastline' },
|
|
44
|
+
{ layer: 'landcover', key: 'class', value: 'grass' },
|
|
45
|
+
{ layer: 'poi', key: 'name', value: 'Seatoun Wharf' },
|
|
46
|
+
{ layer: 'transportation', key: 'name', value: 'Mt Victoria Tunnel' },
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
tileSet: 'topographic',
|
|
51
|
+
tileMatrix: GoogleTms,
|
|
52
|
+
tileType: 'pbf',
|
|
53
|
+
tile: { x: 62, y: 40, z: 6 },
|
|
54
|
+
testFeatures: [
|
|
55
|
+
{ layer: 'landuse', key: 'name', value: 'Queenstown' },
|
|
56
|
+
{ layer: 'place', key: 'name', value: 'Christchurch' },
|
|
57
|
+
{ layer: 'water', key: 'name', value: 'Tasman Lake' },
|
|
58
|
+
{ layer: 'coastline', key: 'class', value: 'coastline' },
|
|
59
|
+
{ layer: 'landcover', key: 'class', value: 'wood' },
|
|
60
|
+
{ layer: 'transportation', key: 'name', value: 'STATE HIGHWAY 6' },
|
|
61
|
+
],
|
|
62
|
+
},
|
|
20
63
|
];
|
|
64
|
+
|
|
21
65
|
const TileSize = 256;
|
|
22
66
|
|
|
23
67
|
export async function getTestBuffer(test: TestTile): Promise<Buffer> {
|
|
@@ -40,6 +84,82 @@ export async function updateExpectedTile(test: TestTile, newTileData: Buffer, di
|
|
|
40
84
|
await fs.promises.writeFile(`${expectedFileName}.diff.png`, imgPng);
|
|
41
85
|
}
|
|
42
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Compare and validate the raster test tile from server with pixel match
|
|
89
|
+
*/
|
|
90
|
+
async function validateRasterTile(tileSet: ConfigTileSetRaster, test: TestTile, req: LambdaHttpRequest): Promise<void> {
|
|
91
|
+
// Get the parse response tile to raw buffer
|
|
92
|
+
const response = await TileXyzRaster.tile(req, tileSet, test);
|
|
93
|
+
if (response.status !== 200) throw new LambdaHttpResponse(500, response.statusDescription);
|
|
94
|
+
if (!Buffer.isBuffer(response._body)) throw new LambdaHttpResponse(500, 'Not a Buffer response content.');
|
|
95
|
+
const resImgBuffer = await Sharp(response._body).raw().toBuffer();
|
|
96
|
+
|
|
97
|
+
// Get test tile to compare
|
|
98
|
+
const testBuffer = await getTestBuffer(test);
|
|
99
|
+
test.buf = testBuffer;
|
|
100
|
+
const testImgBuffer = await Sharp(testBuffer).raw().toBuffer();
|
|
101
|
+
|
|
102
|
+
const outputBuffer = Buffer.alloc(testImgBuffer.length);
|
|
103
|
+
const missMatchedPixels = PixelMatch(testImgBuffer, resImgBuffer, outputBuffer, TileSize, TileSize);
|
|
104
|
+
if (missMatchedPixels) {
|
|
105
|
+
/** Uncomment this to overwite the expected files */
|
|
106
|
+
// await updateExpectedTile(test, response._body as Buffer, outputBuffer);
|
|
107
|
+
req.log.error({ missMatchedPixels, projection: test.tileMatrix.identifier, xyz: test.tile }, 'Health:MissMatch');
|
|
108
|
+
throw new LambdaHttpResponse(500, 'TileSet does not match.');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function checkFeatureExists(tile: VectorTile, testFeature: TestFeature): boolean {
|
|
113
|
+
const layer = tile.layers[testFeature.layer];
|
|
114
|
+
for (let i = 0; i < layer.length; i++) {
|
|
115
|
+
const feature = layer.feature(i);
|
|
116
|
+
if (feature.properties[testFeature.key] === testFeature.value) return true;
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Fetch vector tile and decode into mapbox VectorTile
|
|
123
|
+
*/
|
|
124
|
+
export const VectorTileProvider = {
|
|
125
|
+
async getVectorTile(tileSet: ConfigTileSetVector, test: TestTile, req: LambdaHttpRequest): Promise<VectorTile> {
|
|
126
|
+
// Get the parse response tile to raw buffer
|
|
127
|
+
const response = await tileXyzVector.tile(req, tileSet, test);
|
|
128
|
+
if (response.status !== 200) throw new LambdaHttpResponse(500, response.statusDescription);
|
|
129
|
+
if (!Buffer.isBuffer(response._body)) throw new LambdaHttpResponse(500, 'Not a Buffer response content.');
|
|
130
|
+
const buffer = isGzip(response._body) ? gunzipSync(response._body) : response._body;
|
|
131
|
+
return new VectorTile(new Protobuf(buffer));
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Check the existence of a feature property in side the vector tile
|
|
137
|
+
*
|
|
138
|
+
* @throws LambdaHttpResponse if any test feature not found from vector tile
|
|
139
|
+
*/
|
|
140
|
+
function featureCheck(tile: VectorTile, testTile: TestTile): void {
|
|
141
|
+
const testTileName = `${testTile.tileSet}-${testTile.tile.x}/${testTile.tile.y}/z${testTile.tile.z}`;
|
|
142
|
+
if (testTile.testFeatures == null) {
|
|
143
|
+
throw new LambdaHttpResponse(500, `No test feature found from testTile: ${testTileName}`);
|
|
144
|
+
}
|
|
145
|
+
for (const testFeature of testTile.testFeatures) {
|
|
146
|
+
if (!checkFeatureExists(tile, testFeature)) {
|
|
147
|
+
throw new LambdaHttpResponse(500, `Failed to validate tile: ${testTileName} for layer: ${testFeature.layer}.`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Health check the test vector tiles that contains all the expected features.
|
|
154
|
+
*
|
|
155
|
+
* @throws LambdaHttpResponse if test tiles not returned or features not exists
|
|
156
|
+
*/
|
|
157
|
+
async function validateVectorTile(tileSet: ConfigTileSetVector, test: TestTile, req: LambdaHttpRequest): Promise<void> {
|
|
158
|
+
// Get the parse response tile to raw buffer
|
|
159
|
+
const tile = await VectorTileProvider.getVectorTile(tileSet, test, req);
|
|
160
|
+
featureCheck(tile, test);
|
|
161
|
+
}
|
|
162
|
+
|
|
43
163
|
/**
|
|
44
164
|
* Health request get health TileSets and validate with test TileSets
|
|
45
165
|
* - Valid response from get heath tile request
|
|
@@ -49,27 +169,15 @@ export async function updateExpectedTile(test: TestTile, newTileData: Buffer, di
|
|
|
49
169
|
*/
|
|
50
170
|
export async function healthGet(req: LambdaHttpRequest): Promise<LambdaHttpResponse> {
|
|
51
171
|
const config = await ConfigLoader.load(req);
|
|
52
|
-
const tileSet = await config.TileSet.get(config.TileSet.id('health'));
|
|
53
|
-
if (tileSet == null) throw new LambdaHttpResponse(500, 'TileSet: "health" not found');
|
|
54
172
|
for (const test of TestTiles) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
test.buf = testBuffer;
|
|
64
|
-
const testImgBuffer = await Sharp(testBuffer).raw().toBuffer();
|
|
65
|
-
|
|
66
|
-
const outputBuffer = Buffer.alloc(testImgBuffer.length);
|
|
67
|
-
const missMatchedPixels = PixelMatch(testImgBuffer, resImgBuffer, outputBuffer, TileSize, TileSize);
|
|
68
|
-
if (missMatchedPixels) {
|
|
69
|
-
/** Uncomment this to overwite the expected files */
|
|
70
|
-
// await updateExpectedTile(test, response._body as Buffer, outputBuffer);
|
|
71
|
-
req.log.error({ missMatchedPixels, projection: test.tileMatrix.identifier, xyz: test.tile }, 'Health:MissMatch');
|
|
72
|
-
return new LambdaHttpResponse(500, 'TileSet does not match.');
|
|
173
|
+
const tileSet = await config.TileSet.get(config.TileSet.id(test.tileSet));
|
|
174
|
+
if (tileSet == null) throw new LambdaHttpResponse(500, `TileSet: ${test.tileSet} not found`);
|
|
175
|
+
if (tileSet.type === TileSetType.Raster) {
|
|
176
|
+
await validateRasterTile(tileSet, test, req);
|
|
177
|
+
} else if (tileSet.type === TileSetType.Vector) {
|
|
178
|
+
await validateVectorTile(tileSet, test, req);
|
|
179
|
+
} else {
|
|
180
|
+
throw new LambdaHttpResponse(500, `Invalid TileSet type for tileSet ${test.tileSet}`);
|
|
73
181
|
}
|
|
74
182
|
}
|
|
75
183
|
|