@basemaps/lambda-tiler 6.30.0 → 6.31.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,6 +1,6 @@
1
1
  {
2
2
  "name": "@basemaps/lambda-tiler",
3
- "version": "6.30.0",
3
+ "version": "6.31.0",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@basemaps/lambda-tiler",
3
- "version": "6.30.0",
3
+ "version": "6.31.0",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@basemaps/lambda-tiler",
9
- "version": "6.30.0",
9
+ "version": "6.31.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "sharp": "0.30.7"
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@basemaps/lambda-tiler",
3
- "version": "6.30.0",
3
+ "version": "6.31.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/linz/basemaps.git",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@basemaps/lambda-tiler",
3
- "version": "6.30.0",
3
+ "version": "6.31.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/linz/basemaps.git",
@@ -22,12 +22,12 @@
22
22
  "types": "./build/index.d.ts",
23
23
  "license": "MIT",
24
24
  "dependencies": {
25
- "@basemaps/config": "^6.30.0",
26
- "@basemaps/geo": "^6.28.1",
25
+ "@basemaps/config": "^6.31.0",
26
+ "@basemaps/geo": "^6.31.0",
27
27
  "@basemaps/lambda": "^6.7.0",
28
- "@basemaps/shared": "^6.30.0",
29
- "@basemaps/tiler": "^6.29.0",
30
- "@basemaps/tiler-sharp": "^6.30.0",
28
+ "@basemaps/shared": "^6.31.0",
29
+ "@basemaps/tiler": "^6.31.0",
30
+ "@basemaps/tiler-sharp": "^6.31.0",
31
31
  "@chunkd/fs": "^8.4.0",
32
32
  "@cogeotiff/core": "^7.0.0",
33
33
  "@cotar/core": "^5.4.0",
@@ -52,12 +52,12 @@
52
52
  "bundle": "./bundle.sh"
53
53
  },
54
54
  "devDependencies": {
55
- "@basemaps/attribution": "^6.28.1",
55
+ "@basemaps/attribution": "^6.31.0",
56
56
  "@types/aws-lambda": "^8.10.75",
57
57
  "@types/node": "^17.0.34",
58
58
  "@types/pixelmatch": "^5.0.0",
59
59
  "@types/sharp": "^0.30.3",
60
60
  "pretty-json-log": "^1.0.0"
61
61
  },
62
- "gitHead": "c4858d318fd769e086a11e8b2a8f052b54964aab"
62
+ "gitHead": "2ab942c2a8cd82b94b20c2ad616042ea2fc38518"
63
63
  }
@@ -316,22 +316,19 @@ o.spec('WmtsCapabilities', () => {
316
316
  const boundingBoxes = tags(layer, 'ows:BoundingBox');
317
317
  o(boundingBoxes.length).equals(2);
318
318
  o(boundingBoxes[0].attrs.crs).equals('urn:ogc:def:crs:EPSG::3857');
319
- o(boundingBoxes[0].children.map((c) => roundNumbersInString(c.textContent, 4))).deepEquals([
319
+ o(boundingBoxes[0].children.map((c) => c.textContent)).deepEquals([
320
320
  '19457809.9203 -4609458.5537',
321
321
  '19509787.0995 -4578883.7424',
322
322
  ]);
323
323
  o(boundingBoxes[1].attrs.crs).equals('urn:ogc:def:crs:EPSG::2193');
324
- o(boundingBoxes[1].children.map((c) => roundNumbersInString(c.textContent, 4))).deepEquals([
324
+ o(boundingBoxes[1].children.map((c) => c.textContent)).deepEquals([
325
325
  '5766358.9964 1757351.3045',
326
326
  '5793264.8304 1798321.5516',
327
327
  ]);
328
328
 
329
329
  const wgs84 = layer.find('ows:WGS84BoundingBox');
330
330
  o(wgs84?.attrs.crs).equals('urn:ogc:def:crs:OGC:2:84');
331
- o(wgs84?.children.map((c) => roundNumbersInString(c.textContent, 4))).deepEquals([
332
- '174.7925 -38.2123',
333
- '175.2594 -37.9962',
334
- ]);
331
+ o(wgs84?.children.map((c) => c.textContent)).deepEquals(['174.79248 -38.212288', '175.259399 -37.996163']);
335
332
  });
336
333
 
337
334
  o('should only output imagery if exists', () => {
@@ -363,4 +360,110 @@ o.spec('WmtsCapabilities', () => {
363
360
  const layersB = tags(rawB, 'Layer');
364
361
  o(layersB.length).equals(1);
365
362
  });
363
+
364
+ o('should cover the entire WebMercatorBounds', () => {
365
+ const halfSize = GoogleTms.extent.width / 2;
366
+ // Create two fake imagery sets one covers tile z1 x0 y0 another covers tile z1 x1 y1
367
+ // so the entire bounding box should be tile z0 x0 y0 or the full extent
368
+ const imagery = new Map();
369
+ const imageTopLeft = { ...Imagery3857, id: 'im_top_left', name: 'top_left' };
370
+ imageTopLeft.bounds = { x: -halfSize, y: 0, width: halfSize, height: halfSize };
371
+ imagery.set(imageTopLeft.id, imageTopLeft);
372
+
373
+ const imageBottomRight = { ...Imagery3857, id: 'im_bottom_right', name: 'bottom_right' };
374
+ imageBottomRight.bounds = { x: 0, y: -halfSize, width: halfSize, height: halfSize };
375
+ imagery.set(imageBottomRight.id, imageBottomRight);
376
+
377
+ const tileSet = { ...TileSetAerial };
378
+ tileSet.layers = [
379
+ { 3857: imageTopLeft.id, name: 'a_top_left' },
380
+ { 3857: imageBottomRight.id, name: 'b_bottom_right' },
381
+ ];
382
+
383
+ const raw = new WmtsCapabilities({
384
+ httpBase: 'https://basemaps.test',
385
+ provider: Provider,
386
+ tileMatrix: [GoogleTms],
387
+ tileSet,
388
+ imagery,
389
+ formats: [ImageFormat.Png],
390
+ isIndividualLayers: true,
391
+ }).toVNode();
392
+
393
+ const boundingBox = tags(raw, 'ows:WGS84BoundingBox').map((c) =>
394
+ c
395
+ .toString()
396
+ .split('\n')
397
+ .map((c) => c.trim()),
398
+ );
399
+ o(boundingBox[0][1]).deepEquals('<ows:LowerCorner>-180 -85.051129</ows:LowerCorner>');
400
+ o(boundingBox[0][2]).equals('<ows:UpperCorner>180 85.051129</ows:UpperCorner>');
401
+
402
+ o(boundingBox[1][1]).deepEquals('<ows:LowerCorner>-180 0</ows:LowerCorner>');
403
+ o(boundingBox[1][2]).equals('<ows:UpperCorner>0 85.051129</ows:UpperCorner>');
404
+
405
+ o(boundingBox[2][1]).deepEquals('<ows:LowerCorner>0 -85.051129</ows:LowerCorner>');
406
+ o(boundingBox[2][2]).equals('<ows:UpperCorner>180 0</ows:UpperCorner>');
407
+ });
408
+
409
+ o('should work when crossing anti meridian', () => {
410
+ const halfSize = GoogleTms.extent.width / 2;
411
+
412
+ const imagery = new Map();
413
+ // This image covers z1 x1.5 y1 to z1 x0.5 y1
414
+ // which cross the AM and covers half the width of two tiles
415
+ const imageBottomRight = { ...Imagery3857, id: 'im_bottom_right', name: 'bottom_right' };
416
+ imageBottomRight.bounds = { x: halfSize / 2, y: -halfSize, width: halfSize, height: halfSize };
417
+ imagery.set(imageBottomRight.id, imageBottomRight);
418
+
419
+ const tileSet = { ...TileSetAerial };
420
+ tileSet.layers = [{ 3857: imageBottomRight.id, name: 'b_bottom_right' }];
421
+
422
+ const raw = new WmtsCapabilities({
423
+ httpBase: 'https://basemaps.test',
424
+ provider: Provider,
425
+ tileMatrix: [GoogleTms],
426
+ tileSet,
427
+ imagery,
428
+ formats: [ImageFormat.Png],
429
+ isIndividualLayers: true,
430
+ }).toVNode();
431
+
432
+ const boundingBox = tags(raw, 'ows:WGS84BoundingBox').map((c) =>
433
+ roundNumbersInString(c.toString(), 4)
434
+ .split('\n')
435
+ .map((c) => c.trim()),
436
+ );
437
+ o(boundingBox[0][1]).deepEquals('<ows:LowerCorner>-180 -85.0511</ows:LowerCorner>');
438
+ o(boundingBox[0][2]).equals('<ows:UpperCorner>180 0</ows:UpperCorner>');
439
+
440
+ o(boundingBox[1][1]).deepEquals('<ows:LowerCorner>-180 -85.0511</ows:LowerCorner>');
441
+ o(boundingBox[1][2]).equals('<ows:UpperCorner>180 0</ows:UpperCorner>');
442
+ });
443
+
444
+ o('should work with NZTM2000Quad', () => {
445
+ const wmts = new WmtsCapabilities({ tileMatrix: [] } as any);
446
+
447
+ // Full NZTM200Quad coverage
448
+ const bbox = wmts.buildWgs84BoundingBox(Nztm2000QuadTms, []);
449
+ o(bbox.children[0].textContent).equals('-180 -49.929855');
450
+ o(bbox.children[1].textContent).equals('180 2.938603');
451
+
452
+ // Full NZTM200Quad coverage at z1
453
+ const bboxB = wmts.buildWgs84BoundingBox(Nztm2000QuadTms, [
454
+ Nztm2000QuadTms.tileToSourceBounds({ z: 1, x: 0, y: 0 }),
455
+ Nztm2000QuadTms.tileToSourceBounds({ z: 1, x: 1, y: 1 }),
456
+ ]);
457
+ o(bboxB.children[0].textContent).equals('-180 -49.929855');
458
+ o(bboxB.children[1].textContent).equals('180 2.938603');
459
+
460
+ // Full NZTM200Quad coverage at z5
461
+ const tileCount = Nztm2000QuadTms.zooms[5].matrixWidth;
462
+ const bboxC = wmts.buildWgs84BoundingBox(Nztm2000QuadTms, [
463
+ Nztm2000QuadTms.tileToSourceBounds({ z: 5, x: 0, y: 0 }),
464
+ Nztm2000QuadTms.tileToSourceBounds({ z: 5, x: tileCount - 1, y: tileCount - 1 }),
465
+ ]);
466
+ o(bboxC.children[0].textContent).equals('-180 -49.929855');
467
+ o(bboxC.children[1].textContent).equals('180 2.938603');
468
+ });
366
469
  });
@@ -401,7 +401,7 @@ o.spec('attribution', () => {
401
401
  const ts = new TileSetRaster('Fake', Nztm2000Tms);
402
402
  ts.tileSet = { ...ts.tileSet, layers: [fakeLayer] };
403
403
 
404
- const output = createAttributionCollection(ts, null, fakeIm, fakeLayer, fakeHost, null as any);
404
+ const output = createAttributionCollection(ts, fakeIm, fakeLayer, fakeHost, null as any);
405
405
  o(output.title).equals('SomeName');
406
406
  o(output.summaries['linz:zoom']).deepEquals({ min: 5, max: 11 });
407
407
  });
@@ -409,7 +409,7 @@ o.spec('attribution', () => {
409
409
  o('should generate with correct zooms for NZTM2000Quad', () => {
410
410
  const ts = new TileSetRaster('Fake', Nztm2000QuadTms);
411
411
  ts.tileSet = { ...ts.tileSet, layers: [fakeLayer] };
412
- const output = createAttributionCollection(ts, null, fakeIm, fakeLayer, fakeHost, null as any);
412
+ const output = createAttributionCollection(ts, fakeIm, fakeLayer, fakeHost, null as any);
413
413
  o(output.title).equals('SomeName');
414
414
  o(output.summaries['linz:zoom']).deepEquals({ min: 7, max: 14 });
415
415
  });
@@ -418,7 +418,7 @@ o.spec('attribution', () => {
418
418
  const fakeGebco = { ...fakeLayer, minZoom: 0, maxZoom: 15 };
419
419
  const ts = new TileSetRaster('Fake', Nztm2000QuadTms);
420
420
  ts.tileSet = { ...ts.tileSet, layers: [fakeLayer] };
421
- const output = createAttributionCollection(ts, null, fakeIm, fakeGebco, fakeHost, null as any);
421
+ const output = createAttributionCollection(ts, fakeIm, fakeGebco, fakeHost, null as any);
422
422
  o(output.title).equals('SomeName');
423
423
  o(output.summaries['linz:zoom']).deepEquals({ min: 0, max: 13 });
424
424
  });
@@ -427,7 +427,7 @@ o.spec('attribution', () => {
427
427
  const fakeGebco = { ...fakeLayer, minZoom: 0, maxZoom: 32 };
428
428
  const ts = new TileSetRaster('Fake', Nztm2000QuadTms);
429
429
  ts.tileSet = { ...ts.tileSet, layers: [fakeLayer] };
430
- const output = createAttributionCollection(ts, null, fakeIm, fakeGebco, fakeHost, null as any);
430
+ const output = createAttributionCollection(ts, fakeIm, fakeGebco, fakeHost, null as any);
431
431
  o(output.title).equals('SomeName');
432
432
  o(output.summaries['linz:zoom']).deepEquals({ min: 0, max: Nztm2000QuadTms.maxZoom });
433
433
  });
@@ -87,7 +87,6 @@ async function readStac(uri: string): Promise<StacCollection | null> {
87
87
 
88
88
  export function createAttributionCollection(
89
89
  tileSet: TileSetRaster,
90
- stac: StacCollection | null | undefined,
91
90
  imagery: ConfigImagery,
92
91
  layer: ConfigLayer,
93
92
  host: ConfigProvider,
@@ -96,16 +95,15 @@ export function createAttributionCollection(
96
95
  const tileMatrix = tileSet.tileMatrix;
97
96
  return {
98
97
  stac_version: Stac.Version,
99
- license: stac?.license ?? Stac.License,
98
+ license: Stac.License,
100
99
  id: imagery.id,
101
- providers: stac?.providers ?? [
102
- { name: host.serviceProvider.name, url: host.serviceProvider.site, roles: ['host'] },
103
- ],
104
- title: stac?.title ?? titleizeImageryName(imagery.name),
105
- description: stac?.description ?? 'No description',
100
+ providers: [{ name: host.serviceProvider.name, url: host.serviceProvider.site, roles: ['host'] }],
101
+ title: imagery.title ?? titleizeImageryName(imagery.name),
102
+ description: 'No description',
106
103
  extent,
107
104
  links: [],
108
105
  summaries: {
106
+ 'linz:category': imagery.category,
109
107
  'linz:zoom': {
110
108
  min: TileMatrixSet.convertZoomLevel(layer.minZoom ? layer.minZoom : 0, GoogleTms, tileMatrix, true),
111
109
  max: TileMatrixSet.convertZoomLevel(layer.maxZoom ? layer.maxZoom : 32, GoogleTms, tileMatrix, true),
@@ -146,18 +144,13 @@ async function tileSetAttribution(tileSet: TileSetRaster): Promise<AttributionSt
146
144
  if (imgId == null) continue;
147
145
  const im = tileSet.imagery.get(imgId);
148
146
  if (im == null) continue;
149
- const stac = await stacFiles.get(im.uri);
150
147
 
151
148
  const bbox = proj.boundsToWgs84BoundingBox(im.bounds).map(roundNumber) as BBox;
152
149
 
153
- let interval: [string, string][] | undefined = stac?.extent.temporal.interval;
154
- if (interval == null) {
155
- const years = extractYearRangeFromName(im.name);
156
- if (years[0] === -1) {
157
- throw new Error('Missing date in imagery name: ' + im.name);
158
- }
159
- interval = [years.map((y) => `${y}-01-01T00:00:00Z`) as [string, string]];
160
- }
150
+ const years = extractYearRangeFromName(im.name);
151
+ if (years[0] === -1) throw new Error('Missing date in imagery name: ' + im.name);
152
+ const interval = [years.map((y) => `${y}-01-01T00:00:00Z`) as [string, string]];
153
+
161
154
  const extent: StacExtent = { spatial: { bbox: [bbox] }, temporal: { interval } };
162
155
 
163
156
  items.push({
@@ -173,14 +166,15 @@ async function tileSetAttribution(tileSet: TileSetRaster): Promise<AttributionSt
173
166
  coordinates: createCoordinates(bbox, im.files, proj),
174
167
  },
175
168
  properties: {
176
- title: titleizeImageryName(im.name),
169
+ title: im.title ?? titleizeImageryName(im.name),
170
+ category: im.category,
177
171
  datetime: null,
178
172
  start_datetime: interval[0][0],
179
173
  end_datetime: interval[0][1],
180
174
  },
181
175
  });
182
176
 
183
- cols.push(createAttributionCollection(tileSet, stac, im, layer, host, extent));
177
+ cols.push(createAttributionCollection(tileSet, im, layer, host, extent));
184
178
  }
185
179
  return {
186
180
  id: tileSet.id,
@@ -3,7 +3,7 @@ import { Bounds, GoogleTms, ImageFormat, TileMatrixSet, WmtsProvider } from '@ba
3
3
  import { Projection, V, VNodeElement } from '@basemaps/shared';
4
4
  import { ImageFormatOrder } from '@basemaps/tiler';
5
5
  import { BoundingBox } from '@cogeotiff/core';
6
- import { BBox, Wgs84 } from '@linzjs/geojson';
6
+ import { BBox } from '@linzjs/geojson';
7
7
 
8
8
  const CapabilitiesAttrs = {
9
9
  xmlns: 'http://www.opengis.net/wmts/1.0',
@@ -38,6 +38,19 @@ export interface WmtsCapabilitiesParams {
38
38
  formats?: ImageFormat[];
39
39
  }
40
40
 
41
+ /** Number of decimal places to use in lat lng */
42
+ const LngLatPrecision = 6;
43
+ const MeterPrecision = 4;
44
+
45
+ function formatCoords(x: number, precision: number): string {
46
+ return Number(x.toFixed(precision)).toString();
47
+ }
48
+
49
+ /** Format a bounding box XY as `${x} ${y}` while restricting to precision decimal places */
50
+ function formatBbox(x: number, y: number, precision: number): string {
51
+ return `${formatCoords(x, precision)} ${formatCoords(y, precision)}`;
52
+ }
53
+
41
54
  export class WmtsCapabilities {
42
55
  httpBase: string;
43
56
  provider?: WmtsProvider;
@@ -73,22 +86,26 @@ export class WmtsCapabilities {
73
86
  buildWgs84BoundingBox(tms: TileMatrixSet, layers: Bounds[]): VNodeElement {
74
87
  let bbox: BBox;
75
88
  if (layers.length > 0) {
76
- bbox = wgs84Extent(tms, layers[0]);
89
+ let bounds = layers[0];
77
90
  for (let i = 1; i < layers.length; i++) {
78
- bbox = Wgs84.union(bbox, wgs84Extent(tms, layers[i]));
91
+ bounds = bounds.union(layers[i]);
79
92
  }
93
+ bbox = wgs84Extent(tms, bounds.toJson());
80
94
  } else {
81
95
  // No layers provided assume extent is the size of the tile matrix set :shrug: ?
82
96
  bbox = wgs84Extent(tms, tms.extent);
83
97
  }
84
98
 
85
- return V(
86
- 'ows:WGS84BoundingBox',
87
- { crs: 'urn:ogc:def:crs:OGC:2:84' },
88
- bbox[2] > 180
89
- ? [V('ows:LowerCorner', `-180 -90`), V('ows:UpperCorner', `180 90`)]
90
- : [V('ows:LowerCorner', `${bbox[0]} ${bbox[1]}`), V('ows:UpperCorner', `${bbox[2]} ${bbox[3]}`)],
91
- );
99
+ // If east is less than west, then this has crossed the anti meridian, so cover the entire globe
100
+ if (bbox[2] < bbox[0]) {
101
+ bbox[0] = -180;
102
+ bbox[2] = 180;
103
+ }
104
+
105
+ return V('ows:WGS84BoundingBox', { crs: 'urn:ogc:def:crs:OGC:2:84' }, [
106
+ V('ows:LowerCorner', formatBbox(bbox[0], bbox[1], LngLatPrecision)),
107
+ V('ows:UpperCorner', formatBbox(bbox[2], bbox[3], LngLatPrecision)),
108
+ ]);
92
109
  }
93
110
 
94
111
  /** Combine all the bounds of the imagery inside the layers into a extent for the imagery set */
@@ -106,8 +123,8 @@ export class WmtsCapabilities {
106
123
 
107
124
  const bbox = bounds.toBbox();
108
125
  return V('ows:BoundingBox', { crs: tms.projection.toUrn() }, [
109
- V('ows:LowerCorner', `${bbox[tms.indexX]} ${bbox[tms.indexY]}`),
110
- V('ows:UpperCorner', `${bbox[tms.indexX + 2]} ${bbox[tms.indexY + 2]}`),
126
+ V('ows:LowerCorner', formatBbox(bbox[tms.indexX], bbox[tms.indexY], MeterPrecision)),
127
+ V('ows:UpperCorner', formatBbox(bbox[tms.indexX + 2], bbox[tms.indexY + 2], MeterPrecision)),
111
128
  ]);
112
129
  }
113
130