@basemaps/lambda-tiler 7.1.0 → 7.2.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 +23 -0
- package/build/routes/__tests__/wmts.test.js +19 -50
- package/build/routes/__tests__/wmts.test.js.map +1 -1
- package/build/routes/attribution.d.ts +6 -0
- package/build/routes/attribution.js +11 -3
- package/build/routes/attribution.js.map +1 -1
- package/build/routes/tile.json.js +1 -2
- package/build/routes/tile.json.js.map +1 -1
- package/build/routes/tile.style.json.js +2 -3
- package/build/routes/tile.style.json.js.map +1 -1
- package/build/routes/tile.wmts.js +4 -4
- package/build/routes/tile.wmts.js.map +1 -1
- package/build/routes/tile.xyz.raster.js +7 -6
- package/build/routes/tile.xyz.raster.js.map +1 -1
- package/build/util/response.d.ts +1 -1
- package/build/util/response.js +1 -1
- package/build/util/response.js.map +1 -1
- package/package.json +2 -2
- package/src/routes/__tests__/wmts.test.ts +20 -62
- package/src/routes/attribution.ts +11 -3
- package/src/routes/tile.json.ts +1 -2
- package/src/routes/tile.style.json.ts +2 -3
- package/src/routes/tile.wmts.ts +3 -4
- package/src/routes/tile.xyz.raster.ts +8 -7
- package/src/util/response.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/build/util/__test__/filter.test.d.ts +0 -1
- package/build/util/__test__/filter.test.js +0 -65
- package/build/util/__test__/filter.test.js.map +0 -1
- package/build/util/filter.d.ts +0 -14
- package/build/util/filter.js +0 -58
- package/build/util/filter.js.map +0 -1
- package/src/util/__test__/filter.test.ts +0 -83
- package/src/util/filter.ts +0 -60
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
import { afterEach, beforeEach, describe, it } from 'node:test';
|
|
3
3
|
|
|
4
|
-
import { ConfigProviderMemory } from '@basemaps/config';
|
|
4
|
+
import { ConfigProviderMemory, ConfigTileSetRaster } from '@basemaps/config';
|
|
5
5
|
import { Env } from '@basemaps/shared';
|
|
6
|
-
import { createSandbox } from 'sinon';
|
|
7
6
|
|
|
8
7
|
import { Imagery2193, Imagery3857, Provider, TileSetAerial } from '../../__tests__/config.data.js';
|
|
9
8
|
import { Api, mockUrlRequest } from '../../__tests__/xyz.util.js';
|
|
@@ -11,17 +10,10 @@ import { handler } from '../../index.js';
|
|
|
11
10
|
import { ConfigLoader } from '../../util/config.loader.js';
|
|
12
11
|
|
|
13
12
|
describe('WMTSRouting', () => {
|
|
14
|
-
const sandbox = createSandbox();
|
|
15
13
|
const config = new ConfigProviderMemory();
|
|
16
14
|
const imagery = new Map();
|
|
17
15
|
|
|
18
16
|
beforeEach(() => {
|
|
19
|
-
sandbox.stub(ConfigLoader, 'load').resolves(config);
|
|
20
|
-
sandbox.stub(Env, 'get').callsFake((arg) => {
|
|
21
|
-
if (arg === Env.PublicUrlBase) return 'https://tiles.test';
|
|
22
|
-
return process.env[arg];
|
|
23
|
-
});
|
|
24
|
-
|
|
25
17
|
imagery.set(Imagery3857.id, Imagery3857);
|
|
26
18
|
imagery.set(Imagery2193.id, Imagery2193);
|
|
27
19
|
|
|
@@ -33,10 +25,15 @@ describe('WMTSRouting', () => {
|
|
|
33
25
|
|
|
34
26
|
afterEach(() => {
|
|
35
27
|
config.objects.clear();
|
|
36
|
-
sandbox.restore();
|
|
37
28
|
});
|
|
38
29
|
|
|
39
|
-
it('should default to the aerial layer', async () => {
|
|
30
|
+
it('should default to the aerial layer', async (t) => {
|
|
31
|
+
t.mock.method(Env, 'get', (arg: string) => {
|
|
32
|
+
if (arg === Env.PublicUrlBase) return 'https://tiles.test';
|
|
33
|
+
return process.env[arg];
|
|
34
|
+
});
|
|
35
|
+
t.mock.method(ConfigLoader, 'load', () => Promise.resolve(config));
|
|
36
|
+
|
|
40
37
|
const req = mockUrlRequest(
|
|
41
38
|
'/v1/tiles/WMTSCapabilities.xml',
|
|
42
39
|
`format=png&api=${Api.key}&config=s3://linz-basemaps/config.json`,
|
|
@@ -62,64 +59,25 @@ describe('WMTSRouting', () => {
|
|
|
62
59
|
]);
|
|
63
60
|
});
|
|
64
61
|
|
|
65
|
-
it('should
|
|
66
|
-
|
|
67
|
-
'
|
|
68
|
-
|
|
69
|
-
);
|
|
70
|
-
|
|
62
|
+
it('should 404 when no layers are found', async (t) => {
|
|
63
|
+
t.mock.method(Env, 'get', (arg: string) => {
|
|
64
|
+
if (arg === Env.PublicUrlBase) return 'https://tiles.test';
|
|
65
|
+
return process.env[arg];
|
|
66
|
+
});
|
|
67
|
+
t.mock.method(ConfigLoader, 'load', () => Promise.resolve(config));
|
|
71
68
|
|
|
72
|
-
|
|
73
|
-
const lines = Buffer.from(res.body, 'base64').toString().split('\n');
|
|
74
|
-
const titles = lines.filter((f) => f.startsWith(' <ows:Title>')).map((f) => f.trim());
|
|
69
|
+
config.put({ ...TileSetAerial, id: 'ts_all', name: 'all', layers: [] } as ConfigTileSetRaster);
|
|
75
70
|
|
|
76
|
-
|
|
77
|
-
'<ows:Title>Aerial Imagery</ows:Title>',
|
|
78
|
-
'<ows:Title>Google Maps Compatible for the World</ows:Title>',
|
|
79
|
-
'<ows:Title>LINZ NZTM2000 Map Tile Grid V2</ows:Title>',
|
|
80
|
-
]);
|
|
81
|
-
});
|
|
71
|
+
config.createVirtualTileSets;
|
|
82
72
|
|
|
83
|
-
it('should filter out date[before] by year', async () => {
|
|
84
73
|
const req = mockUrlRequest(
|
|
85
|
-
'/v1/tiles/WMTSCapabilities.xml',
|
|
86
|
-
`format=png&api=${Api.key}&config=s3://linz-basemaps/config.json
|
|
74
|
+
'/v1/tiles/all/WMTSCapabilities.xml',
|
|
75
|
+
`format=png&api=${Api.key}&config=s3://linz-basemaps/config.json`,
|
|
87
76
|
);
|
|
88
|
-
const res = await handler.router.handle(req);
|
|
89
|
-
|
|
90
|
-
assert.equal(res.status, 200);
|
|
91
|
-
const lines = Buffer.from(res.body, 'base64').toString().split('\n');
|
|
92
|
-
const titles = lines.filter((f) => f.startsWith(' <ows:Title>')).map((f) => f.trim());
|
|
93
|
-
|
|
94
|
-
assert.deepEqual(titles, [
|
|
95
|
-
'<ows:Title>Aerial Imagery</ows:Title>',
|
|
96
|
-
'<ows:Title>Google Maps Compatible for the World</ows:Title>',
|
|
97
|
-
'<ows:Title>LINZ NZTM2000 Map Tile Grid V2</ows:Title>',
|
|
98
|
-
]);
|
|
99
|
-
});
|
|
100
77
|
|
|
101
|
-
it('should filter inclusive date[before] by year', async () => {
|
|
102
|
-
const req = mockUrlRequest(
|
|
103
|
-
'/v1/tiles/WMTSCapabilities.xml',
|
|
104
|
-
`format=png&api=${Api.key}&config=s3://linz-basemaps/config.json&date[before]=2021`,
|
|
105
|
-
);
|
|
106
78
|
const res = await handler.router.handle(req);
|
|
107
79
|
|
|
108
|
-
assert.equal(res.status,
|
|
109
|
-
|
|
110
|
-
const titles = lines.filter((f) => f.startsWith(' <ows:Title>')).map((f) => f.trim());
|
|
111
|
-
|
|
112
|
-
assert.deepEqual(titles, [
|
|
113
|
-
'<ows:Title>Aerial Imagery</ows:Title>',
|
|
114
|
-
'<ows:Title>Ōtorohanga 0.1m Urban Aerial Photos (2021)</ows:Title>',
|
|
115
|
-
'<ows:Title>Google Maps Compatible for the World</ows:Title>',
|
|
116
|
-
'<ows:Title>LINZ NZTM2000 Map Tile Grid V2</ows:Title>',
|
|
117
|
-
]);
|
|
118
|
-
|
|
119
|
-
const resourceURLs = lines.filter((f) => f.includes('<ResourceURL')).map((f) => f.trim());
|
|
120
|
-
assert.deepEqual(resourceURLs, [
|
|
121
|
-
'<ResourceURL format="image/png" resourceType="tile" template="https://tiles.test/v1/tiles/aerial/{TileMatrixSet}/{TileMatrix}/{TileCol}/{TileRow}.png?api=d01f7w7rnhdzg0p7fyrc9v9ard1&config=Q5pC4UjWdtFLU1CYtLcRSmB49RekgDgMa5EGJnB2M&date%5Bbefore%5D=2021" />',
|
|
122
|
-
'<ResourceURL format="image/png" resourceType="tile" template="https://tiles.test/v1/tiles/ōtorohanga-urban-2021-0.1m/{TileMatrixSet}/{TileMatrix}/{TileCol}/{TileRow}.png?api=d01f7w7rnhdzg0p7fyrc9v9ard1&config=Q5pC4UjWdtFLU1CYtLcRSmB49RekgDgMa5EGJnB2M" />',
|
|
123
|
-
]);
|
|
80
|
+
assert.equal(res.status, 404);
|
|
81
|
+
assert.equal(res.statusDescription, 'No layers found for tile set: ts_all');
|
|
124
82
|
});
|
|
125
83
|
});
|
|
@@ -18,7 +18,6 @@ import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambd
|
|
|
18
18
|
|
|
19
19
|
import { ConfigLoader } from '../util/config.loader.js';
|
|
20
20
|
import { Etag } from '../util/etag.js';
|
|
21
|
-
import { filterLayers, yearRangeToInterval } from '../util/filter.js';
|
|
22
21
|
import { NotFound, NotModified } from '../util/response.js';
|
|
23
22
|
import { Validate } from '../util/validate.js';
|
|
24
23
|
|
|
@@ -38,6 +37,16 @@ function roundPair(p: Pair): Pair {
|
|
|
38
37
|
return [roundNumber(p[0]), roundNumber(p[1])];
|
|
39
38
|
}
|
|
40
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Convert the year range into full ISO date year range
|
|
42
|
+
*
|
|
43
|
+
* Expand to the full year of jan 1st 00:00 -> Dec 31st 23:59
|
|
44
|
+
*/
|
|
45
|
+
export function yearRangeToInterval(x: [number] | [number, number]): [Date, Date] {
|
|
46
|
+
if (x.length === 1) return [new Date(`${x[0]}-01-01T00:00:00.000Z`), new Date(`${x[0]}-12-31T23:59:59.999Z`)];
|
|
47
|
+
return [new Date(`${x[0]}-01-01T00:00:00.000Z`), new Date(`${x[1]}-12-31T23:59:59.999Z`)];
|
|
48
|
+
}
|
|
49
|
+
|
|
41
50
|
/**
|
|
42
51
|
* Convert a list of COG file bounds into a MultiPolygon. If the bounds spans more than half the
|
|
43
52
|
* globe then return a simple MultiPolygon for the bounding box.
|
|
@@ -84,11 +93,10 @@ async function tileSetAttribution(
|
|
|
84
93
|
|
|
85
94
|
const config = await ConfigLoader.load(req);
|
|
86
95
|
const imagery = await getAllImagery(config, tileSet.layers, [tileMatrix.projection]);
|
|
87
|
-
const filteredLayers = filterLayers(req, tileSet.layers);
|
|
88
96
|
|
|
89
97
|
const host = await config.Provider.get(config.Provider.id('linz'));
|
|
90
98
|
|
|
91
|
-
for (const layer of
|
|
99
|
+
for (const layer of tileSet.layers) {
|
|
92
100
|
const imgId = layer[proj.epsg.code];
|
|
93
101
|
if (imgId == null) continue;
|
|
94
102
|
const im = imagery.get(imgId);
|
package/src/routes/tile.json.ts
CHANGED
|
@@ -4,7 +4,6 @@ import { Env, toQueryString } from '@basemaps/shared';
|
|
|
4
4
|
import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
|
|
5
5
|
|
|
6
6
|
import { ConfigLoader } from '../util/config.loader.js';
|
|
7
|
-
import { getFilters } from '../util/filter.js';
|
|
8
7
|
import { NotFound } from '../util/response.js';
|
|
9
8
|
import { Validate } from '../util/validate.js';
|
|
10
9
|
|
|
@@ -39,7 +38,7 @@ export async function tileJsonGet(req: LambdaHttpRequest<TileJsonGet>): Promise<
|
|
|
39
38
|
|
|
40
39
|
const configLocation = ConfigLoader.extract(req);
|
|
41
40
|
|
|
42
|
-
const query = toQueryString({ api: apiKey, config: configLocation
|
|
41
|
+
const query = toQueryString({ api: apiKey, config: configLocation });
|
|
43
42
|
|
|
44
43
|
const tileUrl =
|
|
45
44
|
[host, 'v1', 'tiles', tileSet.name, tileMatrix.identifier, '{z}', '{x}', '{y}'].join('/') + `.${format[0]}${query}`;
|
|
@@ -6,7 +6,6 @@ import { URL } from 'url';
|
|
|
6
6
|
|
|
7
7
|
import { ConfigLoader } from '../util/config.loader.js';
|
|
8
8
|
import { Etag } from '../util/etag.js';
|
|
9
|
-
import { getFilters } from '../util/filter.js';
|
|
10
9
|
import { NotFound, NotModified } from '../util/response.js';
|
|
11
10
|
import { Validate } from '../util/validate.js';
|
|
12
11
|
|
|
@@ -77,7 +76,7 @@ export async function tileSetToStyle(
|
|
|
77
76
|
const pipelineName = pipeline?.name === 'rgba' ? undefined : pipeline?.name;
|
|
78
77
|
|
|
79
78
|
const configLocation = ConfigLoader.extract(req);
|
|
80
|
-
const query = toQueryString({ config: configLocation, api: apiKey,
|
|
79
|
+
const query = toQueryString({ config: configLocation, api: apiKey, pipeline: pipelineName });
|
|
81
80
|
|
|
82
81
|
const tileUrl =
|
|
83
82
|
(Env.get(Env.PublicUrlBase) ?? '') +
|
|
@@ -111,7 +110,7 @@ export async function tileSetOutputToStyle(
|
|
|
111
110
|
if (tileMatrix == null) return new LambdaHttpResponse(400, 'Invalid tile matrix');
|
|
112
111
|
|
|
113
112
|
const configLocation = ConfigLoader.extract(req);
|
|
114
|
-
const query = toQueryString({ config: configLocation, api: apiKey
|
|
113
|
+
const query = toQueryString({ config: configLocation, api: apiKey });
|
|
115
114
|
|
|
116
115
|
const styleId = `basemaps-${tileSet.name}`;
|
|
117
116
|
const sources: Sources = {};
|
package/src/routes/tile.wmts.ts
CHANGED
|
@@ -6,7 +6,6 @@ import { createHash } from 'crypto';
|
|
|
6
6
|
|
|
7
7
|
import { ConfigLoader } from '../util/config.loader.js';
|
|
8
8
|
import { Etag } from '../util/etag.js';
|
|
9
|
-
import { filterLayers, getFilters } from '../util/filter.js';
|
|
10
9
|
import { NotFound, NotModified } from '../util/response.js';
|
|
11
10
|
import { Validate } from '../util/validate.js';
|
|
12
11
|
import { WmtsCapabilities } from '../wmts.capability.js';
|
|
@@ -44,7 +43,7 @@ export async function wmtsCapabilitiesGet(req: LambdaHttpRequest<WmtsCapabilitie
|
|
|
44
43
|
req.timer.start('tileset:load');
|
|
45
44
|
const tileSet = await config.TileSet.get(config.TileSet.id(tileSetName ?? 'aerial'));
|
|
46
45
|
req.timer.end('tileset:load');
|
|
47
|
-
if (tileSet == null || tileSet.type !== TileSetType.Raster) return NotFound();
|
|
46
|
+
if (tileSet == null || tileSet.type !== TileSetType.Raster) return NotFound('Tileset not found');
|
|
48
47
|
|
|
49
48
|
const provider = await config.Provider.get(config.Provider.id('linz'));
|
|
50
49
|
|
|
@@ -55,12 +54,12 @@ export async function wmtsCapabilitiesGet(req: LambdaHttpRequest<WmtsCapabilitie
|
|
|
55
54
|
tileMatrix.map((tms) => tms.projection),
|
|
56
55
|
);
|
|
57
56
|
req.timer.end('imagery:load');
|
|
57
|
+
if (imagery.size === 0) return NotFound('No layers found for tile set: ' + tileSet.id);
|
|
58
58
|
|
|
59
59
|
const wmts = new WmtsCapabilities({
|
|
60
60
|
httpBase: host,
|
|
61
61
|
apiKey,
|
|
62
62
|
config: ConfigLoader.extract(req),
|
|
63
|
-
filters: getFilters(req),
|
|
64
63
|
});
|
|
65
64
|
|
|
66
65
|
wmts.fromParams({
|
|
@@ -69,7 +68,7 @@ export async function wmtsCapabilitiesGet(req: LambdaHttpRequest<WmtsCapabilitie
|
|
|
69
68
|
tileMatrix,
|
|
70
69
|
imagery,
|
|
71
70
|
formats: Validate.getRequestedFormats(req) ?? [],
|
|
72
|
-
layers: req.params.tileMatrix == null ?
|
|
71
|
+
layers: req.params.tileMatrix == null ? tileSet.layers : undefined,
|
|
73
72
|
});
|
|
74
73
|
|
|
75
74
|
const xml = wmts.toXml();
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { ConfigTileSetRaster, getAllImagery } from '@basemaps/config';
|
|
2
|
-
import { Bounds, Epsg,
|
|
2
|
+
import { Bounds, Epsg, TileMatrixSet, TileMatrixSets } from '@basemaps/geo';
|
|
3
3
|
import { Cotar, Env, stringToUrlFolder, Tiff } from '@basemaps/shared';
|
|
4
|
-
import { Tiler } from '@basemaps/tiler';
|
|
4
|
+
import { getImageFormat, Tiler } from '@basemaps/tiler';
|
|
5
5
|
import { TileMakerSharp } from '@basemaps/tiler-sharp';
|
|
6
6
|
import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
|
|
7
7
|
import pLimit from 'p-limit';
|
|
8
8
|
|
|
9
9
|
import { ConfigLoader } from '../util/config.loader.js';
|
|
10
10
|
import { Etag } from '../util/etag.js';
|
|
11
|
-
import { filterLayers } from '../util/filter.js';
|
|
12
11
|
import { NotFound, NotModified } from '../util/response.js';
|
|
13
12
|
import { CoSources } from '../util/source.cache.js';
|
|
14
13
|
import { TileXyz, Validate } from '../util/validate.js';
|
|
@@ -47,13 +46,12 @@ export const TileXyzRaster = {
|
|
|
47
46
|
): Promise<URL[]> {
|
|
48
47
|
const config = await ConfigLoader.load(req);
|
|
49
48
|
const imagery = await getAllImagery(config, tileSet.layers, [tileMatrix.projection]);
|
|
50
|
-
const filteredLayers = filterLayers(req, tileSet.layers);
|
|
51
49
|
|
|
52
50
|
const output: URL[] = [];
|
|
53
51
|
|
|
54
52
|
// All zoom level config is stored as Google zoom levels
|
|
55
53
|
const filterZoom = TileMatrixSet.convertZoomLevel(zoom, tileMatrix, TileMatrixSets.get(Epsg.Google));
|
|
56
|
-
for (const layer of
|
|
54
|
+
for (const layer of tileSet.layers) {
|
|
57
55
|
if (layer.maxZoom != null && filterZoom > layer.maxZoom) continue;
|
|
58
56
|
if (layer.minZoom != null && filterZoom < layer.minZoom) continue;
|
|
59
57
|
|
|
@@ -132,10 +130,13 @@ export const TileXyzRaster = {
|
|
|
132
130
|
const tiler = new Tiler(xyz.tileMatrix);
|
|
133
131
|
const layers = await tiler.tile(assets, xyz.tile.x, xyz.tile.y, xyz.tile.z);
|
|
134
132
|
|
|
133
|
+
const format = getImageFormat(xyz.tileType);
|
|
134
|
+
if (format == null) return new LambdaHttpResponse(400, 'Invalid image format: ' + xyz.tileType);
|
|
135
|
+
|
|
135
136
|
const res = await TileComposer.compose({
|
|
136
137
|
layers,
|
|
137
138
|
pipeline: tileOutput.pipeline,
|
|
138
|
-
format
|
|
139
|
+
format,
|
|
139
140
|
background: tileOutput.background ?? tileSet.background ?? DefaultBackground,
|
|
140
141
|
resizeKernel: tileOutput.resizeKernel ?? tileSet.resizeKernel ?? DefaultResizeKernel,
|
|
141
142
|
metrics: req.timer,
|
|
@@ -148,7 +149,7 @@ export const TileXyzRaster = {
|
|
|
148
149
|
const response = new LambdaHttpResponse(200, 'ok');
|
|
149
150
|
response.header(HttpHeader.ETag, cacheKey);
|
|
150
151
|
response.header(HttpHeader.CacheControl, 'public, max-age=604800, stale-while-revalidate=86400');
|
|
151
|
-
response.buffer(res.buffer,
|
|
152
|
+
response.buffer(res.buffer, `image/${format}`);
|
|
152
153
|
return response;
|
|
153
154
|
},
|
|
154
155
|
};
|
package/src/util/response.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
|
|
2
2
|
|
|
3
|
-
export const NotFound = (): LambdaHttpResponse => new LambdaHttpResponse(404,
|
|
3
|
+
export const NotFound = (msg: string = 'Not Found'): LambdaHttpResponse => new LambdaHttpResponse(404, msg);
|
|
4
4
|
export const NotModified = (): LambdaHttpResponse => new LambdaHttpResponse(304, 'Not modified');
|
|
5
5
|
export const NoContent = (): LambdaHttpResponse => new LambdaHttpResponse(204, 'No Content');
|
|
6
6
|
export const OkResponse = (req: LambdaHttpRequest): LambdaHttpResponse =>
|