@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.
@@ -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 filter out date[after] by year', async () => {
66
- const req = mockUrlRequest(
67
- '/v1/tiles/WMTSCapabilities.xml',
68
- `format=png&api=${Api.key}&config=s3://linz-basemaps/config.json&date[after]=2022`,
69
- );
70
- const res = await handler.router.handle(req);
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
- assert.equal(res.status, 200);
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
- assert.deepEqual(titles, [
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&date[before]=2020`,
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, 200);
109
- const lines = Buffer.from(res.body, 'base64').toString().split('\n');
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&amp;config=Q5pC4UjWdtFLU1CYtLcRSmB49RekgDgMa5EGJnB2M&amp;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&amp;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 filteredLayers) {
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);
@@ -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, ...getFilters(req) });
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, ...getFilters(req), pipeline: pipelineName });
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, ...getFilters(req) });
113
+ const query = toQueryString({ config: configLocation, api: apiKey });
115
114
 
116
115
  const styleId = `basemaps-${tileSet.name}`;
117
116
  const sources: Sources = {};
@@ -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 ? filterLayers(req, tileSet.layers) : undefined,
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, ImageFormat, TileMatrixSet, TileMatrixSets } from '@basemaps/geo';
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 filteredLayers) {
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: xyz.tileType as ImageFormat,
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, 'image/' + xyz.tileType);
152
+ response.buffer(res.buffer, `image/${format}`);
152
153
  return response;
153
154
  },
154
155
  };
@@ -1,6 +1,6 @@
1
1
  import { LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
2
2
 
3
- export const NotFound = (): LambdaHttpResponse => new LambdaHttpResponse(404, 'Not Found');
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 =>