@basemaps/lambda-tiler 6.29.0 → 6.32.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 (259) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/build/__tests__/config.data.d.ts +11 -0
  3. package/build/__tests__/config.data.d.ts.map +1 -0
  4. package/build/__tests__/config.data.js +112 -0
  5. package/build/__tests__/config.data.js.map +1 -0
  6. package/build/__tests__/index.test.js +5 -14
  7. package/build/__tests__/index.test.js.map +1 -0
  8. package/build/__tests__/tile.style.json.test.js +1 -0
  9. package/build/__tests__/tile.style.json.test.js.map +1 -0
  10. package/build/__tests__/wmts.capability.test.d.ts +1 -1
  11. package/build/__tests__/wmts.capability.test.d.ts.map +1 -1
  12. package/build/__tests__/wmts.capability.test.js +286 -125
  13. package/build/__tests__/wmts.capability.test.js.map +1 -0
  14. package/build/__tests__/xyz.util.d.ts +7 -11
  15. package/build/__tests__/xyz.util.d.ts.map +1 -1
  16. package/build/__tests__/xyz.util.js +14 -42
  17. package/build/__tests__/xyz.util.js.map +1 -0
  18. package/build/index.d.ts +0 -2
  19. package/build/index.d.ts.map +1 -1
  20. package/build/index.js +68 -41
  21. package/build/index.js.map +1 -0
  22. package/build/routes/__tests__/attribution.test.js +351 -399
  23. package/build/routes/__tests__/attribution.test.js.map +1 -0
  24. package/build/routes/__tests__/fonts.test.js +17 -3
  25. package/build/routes/__tests__/fonts.test.js.map +1 -0
  26. package/build/routes/__tests__/health.test.js +17 -13
  27. package/build/routes/__tests__/health.test.js.map +1 -0
  28. package/build/routes/__tests__/imagery.test.js +1 -0
  29. package/build/routes/__tests__/imagery.test.js.map +1 -0
  30. package/build/routes/__tests__/memory.fs.js +1 -0
  31. package/build/routes/__tests__/memory.fs.js.map +1 -0
  32. package/build/routes/__tests__/sprites.test.js +7 -0
  33. package/build/routes/__tests__/sprites.test.js.map +1 -0
  34. package/build/routes/__tests__/tile.json.test.d.ts +2 -0
  35. package/build/routes/__tests__/tile.json.test.d.ts.map +1 -0
  36. package/build/routes/__tests__/tile.json.test.js +124 -0
  37. package/build/routes/__tests__/tile.json.test.js.map +1 -0
  38. package/build/routes/__tests__/tile.style.json.test.d.ts +2 -0
  39. package/build/routes/__tests__/tile.style.json.test.d.ts.map +1 -0
  40. package/build/routes/__tests__/tile.style.json.test.js +95 -0
  41. package/build/routes/__tests__/tile.style.json.test.js.map +1 -0
  42. package/build/routes/__tests__/wmts.test.js +37 -27
  43. package/build/routes/__tests__/wmts.test.js.map +1 -0
  44. package/build/{__tests__ → routes/__tests__}/xyz.test.d.ts +0 -0
  45. package/build/routes/__tests__/xyz.test.d.ts.map +1 -0
  46. package/build/routes/__tests__/xyz.test.js +99 -0
  47. package/build/routes/__tests__/xyz.test.js.map +1 -0
  48. package/build/routes/attribution.d.ts +7 -5
  49. package/build/routes/attribution.d.ts.map +1 -1
  50. package/build/routes/attribution.js +50 -91
  51. package/build/routes/attribution.js.map +1 -0
  52. package/build/routes/fonts.d.ts +1 -1
  53. package/build/routes/fonts.d.ts.map +1 -1
  54. package/build/routes/fonts.js +33 -10
  55. package/build/routes/fonts.js.map +1 -0
  56. package/build/routes/health.d.ts +3 -3
  57. package/build/routes/health.d.ts.map +1 -1
  58. package/build/routes/health.js +16 -13
  59. package/build/routes/health.js.map +1 -0
  60. package/build/routes/imagery.d.ts +8 -1
  61. package/build/routes/imagery.d.ts.map +1 -1
  62. package/build/routes/imagery.js +17 -17
  63. package/build/routes/imagery.js.map +1 -0
  64. package/build/routes/ping.d.ts +3 -0
  65. package/build/routes/ping.d.ts.map +1 -0
  66. package/build/routes/ping.js +7 -0
  67. package/build/routes/ping.js.map +1 -0
  68. package/build/routes/sprites.d.ts.map +1 -1
  69. package/build/routes/sprites.js +22 -22
  70. package/build/routes/sprites.js.map +1 -0
  71. package/build/routes/tile.json.d.ts +7 -1
  72. package/build/routes/tile.json.d.ts.map +1 -1
  73. package/build/routes/tile.json.js +19 -22
  74. package/build/routes/tile.json.js.map +1 -0
  75. package/build/routes/tile.style.json.d.ts +6 -1
  76. package/build/routes/tile.style.json.d.ts.map +1 -1
  77. package/build/routes/tile.style.json.js +11 -13
  78. package/build/routes/tile.style.json.js.map +1 -0
  79. package/build/routes/tile.wmts.d.ts +9 -3
  80. package/build/routes/tile.wmts.d.ts.map +1 -1
  81. package/build/routes/tile.wmts.js +37 -50
  82. package/build/routes/tile.wmts.js.map +1 -0
  83. package/build/routes/tile.xyz.d.ts +14 -4
  84. package/build/routes/tile.xyz.d.ts.map +1 -1
  85. package/build/routes/tile.xyz.js +22 -17
  86. package/build/routes/tile.xyz.js.map +1 -0
  87. package/build/routes/tile.xyz.raster.d.ts +11 -0
  88. package/build/routes/tile.xyz.raster.d.ts.map +1 -0
  89. package/build/routes/tile.xyz.raster.js +90 -0
  90. package/build/routes/tile.xyz.raster.js.map +1 -0
  91. package/build/routes/tile.xyz.vector.d.ts +8 -0
  92. package/build/routes/tile.xyz.vector.d.ts.map +1 -0
  93. package/build/routes/tile.xyz.vector.js +46 -0
  94. package/build/routes/tile.xyz.vector.js.map +1 -0
  95. package/build/routes/version.d.ts +3 -0
  96. package/build/routes/version.d.ts.map +1 -0
  97. package/build/routes/version.js +9 -0
  98. package/build/routes/version.js.map +1 -0
  99. package/build/util/__test__/validate.test.d.ts +2 -0
  100. package/build/util/__test__/validate.test.d.ts.map +1 -0
  101. package/build/util/__test__/validate.test.js +66 -0
  102. package/build/util/__test__/validate.test.js.map +1 -0
  103. package/build/util/cotar.serve.d.ts +20 -0
  104. package/build/util/cotar.serve.d.ts.map +1 -0
  105. package/build/util/cotar.serve.js +41 -0
  106. package/build/util/cotar.serve.js.map +1 -0
  107. package/build/util/etag.d.ts +6 -0
  108. package/build/util/etag.d.ts.map +1 -0
  109. package/build/util/etag.js +20 -0
  110. package/build/util/etag.js.map +1 -0
  111. package/build/util/response.d.ts +4 -0
  112. package/build/util/response.d.ts.map +1 -0
  113. package/build/util/response.js +4 -0
  114. package/build/util/response.js.map +1 -0
  115. package/build/util/source.cache.d.ts +28 -0
  116. package/build/util/source.cache.d.ts.map +1 -0
  117. package/build/util/source.cache.js +53 -0
  118. package/build/util/source.cache.js.map +1 -0
  119. package/build/{source.tracer.d.ts → util/source.tracer.d.ts} +1 -0
  120. package/build/util/source.tracer.d.ts.map +1 -0
  121. package/build/{source.tracer.js → util/source.tracer.js} +4 -0
  122. package/build/util/source.tracer.js.map +1 -0
  123. package/build/util/swapping.lru.d.ts +21 -0
  124. package/build/util/swapping.lru.d.ts.map +1 -0
  125. package/build/util/swapping.lru.js +56 -0
  126. package/build/util/swapping.lru.js.map +1 -0
  127. package/build/util/validate.d.ts +46 -0
  128. package/build/util/validate.d.ts.map +1 -0
  129. package/build/util/validate.js +107 -0
  130. package/build/util/validate.js.map +1 -0
  131. package/build/wmts.capability.d.ts +27 -13
  132. package/build/wmts.capability.d.ts.map +1 -1
  133. package/build/wmts.capability.js +156 -55
  134. package/build/wmts.capability.js.map +1 -0
  135. package/dist/index.js +89 -73
  136. package/dist/node_modules/.package-lock.json +1 -1
  137. package/dist/package-lock.json +2 -2
  138. package/dist/package.json +1 -1
  139. package/package.json +10 -10
  140. package/src/__tests__/config.data.ts +120 -0
  141. package/src/__tests__/index.test.ts +4 -20
  142. package/src/__tests__/wmts.capability.test.ts +312 -139
  143. package/src/__tests__/xyz.util.ts +17 -45
  144. package/src/index.ts +75 -41
  145. package/src/routes/__tests__/attribution.test.ts +356 -403
  146. package/src/routes/__tests__/fonts.test.ts +18 -3
  147. package/src/routes/__tests__/health.test.ts +17 -13
  148. package/src/routes/__tests__/sprites.test.ts +6 -1
  149. package/src/routes/__tests__/tile.json.test.ts +145 -0
  150. package/src/routes/__tests__/tile.style.json.test.ts +105 -0
  151. package/src/routes/__tests__/wmts.test.ts +44 -34
  152. package/src/routes/__tests__/xyz.test.ts +119 -0
  153. package/src/routes/attribution.ts +59 -111
  154. package/src/routes/fonts.ts +32 -10
  155. package/src/routes/health.ts +17 -16
  156. package/src/routes/imagery.ts +18 -15
  157. package/src/routes/ping.ts +8 -0
  158. package/src/routes/sprites.ts +20 -22
  159. package/src/routes/tile.json.ts +24 -19
  160. package/src/routes/tile.style.json.ts +15 -12
  161. package/src/routes/tile.wmts.ts +41 -44
  162. package/src/routes/tile.xyz.raster.ts +106 -0
  163. package/src/routes/tile.xyz.ts +31 -16
  164. package/src/routes/tile.xyz.vector.ts +47 -0
  165. package/src/routes/version.ts +8 -0
  166. package/src/util/__test__/validate.test.ts +74 -0
  167. package/src/util/cotar.serve.ts +46 -0
  168. package/src/util/etag.ts +20 -0
  169. package/src/util/response.ts +4 -0
  170. package/src/util/source.cache.ts +71 -0
  171. package/src/{source.tracer.ts → util/source.tracer.ts} +4 -0
  172. package/src/util/swapping.lru.ts +63 -0
  173. package/src/util/validate.ts +126 -0
  174. package/src/wmts.capability.ts +170 -68
  175. package/tsconfig.tsbuildinfo +1 -1
  176. package/build/__tests__/route.test.d.ts +0 -2
  177. package/build/__tests__/route.test.d.ts.map +0 -1
  178. package/build/__tests__/route.test.js +0 -20
  179. package/build/__tests__/tiff.cache.test.d.ts +0 -2
  180. package/build/__tests__/tiff.cache.test.d.ts.map +0 -1
  181. package/build/__tests__/tiff.cache.test.js +0 -58
  182. package/build/__tests__/tile.cache.key.test.d.ts +0 -2
  183. package/build/__tests__/tile.cache.key.test.d.ts.map +0 -1
  184. package/build/__tests__/tile.cache.key.test.js +0 -48
  185. package/build/__tests__/tile.set.cache.test.d.ts +0 -2
  186. package/build/__tests__/tile.set.cache.test.d.ts.map +0 -1
  187. package/build/__tests__/tile.set.cache.test.js +0 -123
  188. package/build/__tests__/tile.set.test.d.ts +0 -2
  189. package/build/__tests__/tile.set.test.d.ts.map +0 -1
  190. package/build/__tests__/tile.set.test.js +0 -11
  191. package/build/__tests__/xyz.test.d.ts.map +0 -1
  192. package/build/__tests__/xyz.test.js +0 -306
  193. package/build/api.key.d.ts +0 -2
  194. package/build/api.key.d.ts.map +0 -1
  195. package/build/api.key.js +0 -23
  196. package/build/cli/dump.d.ts +0 -2
  197. package/build/cli/dump.d.ts.map +0 -1
  198. package/build/cli/dump.js +0 -47
  199. package/build/cli/tile.set.local.d.ts +0 -12
  200. package/build/cli/tile.set.local.d.ts.map +0 -1
  201. package/build/cli/tile.set.local.js +0 -39
  202. package/build/router.d.ts +0 -15
  203. package/build/router.d.ts.map +0 -1
  204. package/build/router.js +0 -49
  205. package/build/routes/api.d.ts +0 -5
  206. package/build/routes/api.d.ts.map +0 -1
  207. package/build/routes/api.js +0 -16
  208. package/build/routes/esri/rest.d.ts +0 -10
  209. package/build/routes/esri/rest.d.ts.map +0 -1
  210. package/build/routes/esri/rest.js +0 -87
  211. package/build/routes/response.d.ts +0 -4
  212. package/build/routes/response.d.ts.map +0 -1
  213. package/build/routes/response.js +0 -3
  214. package/build/routes/tile.d.ts +0 -3
  215. package/build/routes/tile.d.ts.map +0 -1
  216. package/build/routes/tile.etag.d.ts +0 -11
  217. package/build/routes/tile.etag.d.ts.map +0 -1
  218. package/build/routes/tile.etag.js +0 -29
  219. package/build/routes/tile.js +0 -27
  220. package/build/source.tracer.d.ts.map +0 -1
  221. package/build/tiff.cache.d.ts +0 -17
  222. package/build/tiff.cache.d.ts.map +0 -1
  223. package/build/tiff.cache.js +0 -45
  224. package/build/tile.set.cache.d.ts +0 -21
  225. package/build/tile.set.cache.d.ts.map +0 -1
  226. package/build/tile.set.cache.js +0 -100
  227. package/build/tile.set.d.ts +0 -4
  228. package/build/tile.set.d.ts.map +0 -1
  229. package/build/tile.set.js +0 -1
  230. package/build/tile.set.raster.d.ts +0 -49
  231. package/build/tile.set.raster.d.ts.map +0 -1
  232. package/build/tile.set.raster.js +0 -186
  233. package/build/tile.set.vector.d.ts +0 -25
  234. package/build/tile.set.vector.d.ts.map +0 -1
  235. package/build/tile.set.vector.js +0 -71
  236. package/build/validate.d.ts +0 -16
  237. package/build/validate.d.ts.map +0 -1
  238. package/build/validate.js +0 -31
  239. package/src/__tests__/route.test.ts +0 -24
  240. package/src/__tests__/tiff.cache.test.ts +0 -73
  241. package/src/__tests__/tile.cache.key.test.ts +0 -56
  242. package/src/__tests__/tile.set.cache.test.ts +0 -146
  243. package/src/__tests__/tile.set.test.ts +0 -12
  244. package/src/__tests__/xyz.test.ts +0 -362
  245. package/src/api.key.ts +0 -23
  246. package/src/cli/dump.ts +0 -61
  247. package/src/cli/tile.set.local.ts +0 -51
  248. package/src/router.ts +0 -58
  249. package/src/routes/api.ts +0 -19
  250. package/src/routes/esri/rest.ts +0 -90
  251. package/src/routes/response.ts +0 -4
  252. package/src/routes/tile.etag.ts +0 -36
  253. package/src/routes/tile.ts +0 -23
  254. package/src/tiff.cache.ts +0 -51
  255. package/src/tile.set.cache.ts +0 -111
  256. package/src/tile.set.raster.ts +0 -228
  257. package/src/tile.set.ts +0 -4
  258. package/src/tile.set.vector.ts +0 -79
  259. package/src/validate.ts +0 -32
@@ -0,0 +1,63 @@
1
+ export class SwappingLru<T extends { size: number }> {
2
+ cacheA: Map<string, T> = new Map();
3
+ cacheB: Map<string, T> = new Map();
4
+ maxSize: number;
5
+
6
+ hits = 0;
7
+ misses = 0;
8
+ resets = 0;
9
+
10
+ _lastCheckedAt = -1;
11
+
12
+ constructor(maxSize: number) {
13
+ this.maxSize = maxSize;
14
+ }
15
+
16
+ get(id: string): T | null {
17
+ const cacheA = this.cacheA.get(id);
18
+ if (cacheA) {
19
+ this.hits++;
20
+ return cacheA;
21
+ }
22
+
23
+ const cacheB = this.cacheB.get(id);
24
+ if (cacheB == null) {
25
+ this.misses++;
26
+ return null;
27
+ }
28
+
29
+ this.hits++;
30
+ // If a object is still useful move it into the main cache
31
+ this.cacheA.set(id, cacheB);
32
+ this.cacheB.delete(id);
33
+ return cacheB;
34
+ }
35
+
36
+ /** Reset the cache */
37
+ clear(): void {
38
+ this.cacheA.clear();
39
+ this.cacheB.clear();
40
+ }
41
+
42
+ set(id: string, tiff: T): void {
43
+ this.cacheA.set(id, tiff);
44
+ this.check();
45
+ }
46
+
47
+ /** Validate the size of the cache has not exploded */
48
+ check(): void {
49
+ this._lastCheckedAt = Date.now();
50
+ if (this.maxSize <= 0) return;
51
+ if (this.currentSize <= this.maxSize) return;
52
+ this.resets++;
53
+ this.cacheB = this.cacheA;
54
+ this.cacheA = new Map();
55
+ }
56
+
57
+ /** Calculate the total number of bytes used by this cache */
58
+ get currentSize(): number {
59
+ let size = 0;
60
+ for (const value of this.cacheA.values()) size += value.size;
61
+ return size;
62
+ }
63
+ }
@@ -0,0 +1,126 @@
1
+ import { ImageFormat, TileMatrixSet, TileMatrixSets, VectorFormat } from '@basemaps/geo';
2
+ import { Const, Projection } from '@basemaps/shared';
3
+ import { getImageFormat } from '@basemaps/tiler';
4
+ import { LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
5
+ import * as ulid from 'ulid';
6
+ import { TileXyzGet } from '../routes/tile.xyz';
7
+
8
+ export interface TileXyz {
9
+ tile: { x: number; y: number; z: number };
10
+ tileSet: string;
11
+ tileMatrix: TileMatrixSet;
12
+ tileType: VectorFormat | ImageFormat;
13
+ }
14
+
15
+ export interface TileMatrixRequest {
16
+ Params: { tileMatrix?: string };
17
+ }
18
+
19
+ const OneHourMs = 60 * 60 * 1000;
20
+ const OneDayMs = 24 * OneHourMs;
21
+ const MaxApiAgeMs = 91 * OneDayMs;
22
+
23
+ export interface ApiKeyStatus {
24
+ valid: boolean;
25
+ message: 'ok' | 'malformed' | 'missing' | 'expired';
26
+ }
27
+
28
+ export function isValidApiKey(apiKey?: string | null): ApiKeyStatus {
29
+ if (apiKey == null) return { valid: false, message: 'missing' };
30
+ if (!apiKey.startsWith('c') && !apiKey.startsWith('d')) return { valid: false, message: 'malformed' };
31
+ const ulidId = apiKey.slice(1).toUpperCase();
32
+ try {
33
+ const ulidTime = ulid.decodeTime(ulidId);
34
+ if (apiKey.startsWith('d')) return { valid: true, message: 'ok' };
35
+
36
+ if (Date.now() - ulidTime > MaxApiAgeMs) return { valid: false, message: 'expired' };
37
+ } catch (e) {
38
+ return { valid: false, message: 'malformed' };
39
+ }
40
+
41
+ return { valid: true, message: 'ok' };
42
+ }
43
+
44
+ export const Validate = {
45
+ /**
46
+ * Validate that the api key exists and is valid
47
+ * @throws if api key is not valid
48
+ */
49
+ apiKey(req: LambdaHttpRequest): string {
50
+ const apiKey = req.query.get(Const.ApiKey.QueryString) ?? req.header('X-LINZ-Api-Key');
51
+ const valid = isValidApiKey(apiKey);
52
+
53
+ if (!valid.valid) throw new LambdaHttpResponse(400, 'API Key Invalid: ' + valid.message);
54
+ req.set('api', apiKey);
55
+ return apiKey as string;
56
+ },
57
+
58
+ getTileMatrixSet(str?: string): TileMatrixSet | null {
59
+ return TileMatrixSets.find(str);
60
+ },
61
+
62
+ /** Read in all image formats specified in the query parameters "format" or "tileFormat" */
63
+ getRequestedFormats(req: LambdaHttpRequest): ImageFormat[] | null {
64
+ const formats = [...req.query.getAll('format'), ...req.query.getAll('tileFormat')];
65
+ if (formats.length === 0) return null;
66
+
67
+ const output: Set<ImageFormat> = new Set();
68
+ for (const fmt of formats) {
69
+ const parsed = getImageFormat(fmt);
70
+ if (parsed == null) continue;
71
+ output.add(parsed);
72
+ }
73
+ if (output.size === 0) return null;
74
+ return [...output.values()];
75
+ },
76
+
77
+ getTileFormat(tileType: string): ImageFormat | VectorFormat | null {
78
+ const ext = getImageFormat(tileType);
79
+ if (ext) return ext;
80
+ if (tileType === VectorFormat.MapboxVectorTiles) return VectorFormat.MapboxVectorTiles;
81
+ return null;
82
+ },
83
+ /**
84
+ * Validate that the tile request is somewhat valid
85
+ * - Valid projection
86
+ * - Valid range
87
+ *
88
+ * @throws LambdaHttpResponse for tile requests that are not valid
89
+ *
90
+ * @param req request to validate
91
+ * @param xyzData
92
+ */
93
+ xyz(req: LambdaHttpRequest<TileXyzGet>): TileXyz {
94
+ Validate.apiKey(req);
95
+
96
+ req.set('tileSet', req.params.tileSet);
97
+
98
+ const x = parseInt(req.params.x, 10);
99
+ const y = parseInt(req.params.y, 10);
100
+ const z = parseInt(req.params.z, 10);
101
+
102
+ const tileMatrix = Validate.getTileMatrixSet(req.params.tileMatrix);
103
+ if (tileMatrix == null) throw new LambdaHttpResponse(404, 'Tile Matrix not found');
104
+
105
+ req.set('tileMatrix', tileMatrix.identifier);
106
+ req.set('projection', tileMatrix.projection.code);
107
+
108
+ const tileType = Validate.getTileFormat(req.params.tileType);
109
+ if (tileType == null) throw new LambdaHttpResponse(404, 'Tile extension not found');
110
+ req.set('extension', tileType);
111
+
112
+ if (isNaN(z) || z > tileMatrix.maxZoom || z < 0) throw new LambdaHttpResponse(404, `Zoom not found: ${z}`);
113
+
114
+ const zoom = tileMatrix.zooms[z];
115
+ if (isNaN(x) || x < 0 || x > zoom.matrixWidth) throw new LambdaHttpResponse(404, `X not found: ${x}`);
116
+ if (isNaN(y) || y < 0 || y > zoom.matrixHeight) throw new LambdaHttpResponse(404, `Y not found: ${y}`);
117
+
118
+ const xyzData = { tile: { x, y, z }, tileSet: req.params.tileSet, tileMatrix, tileType };
119
+ req.set('xyz', xyzData.tile);
120
+
121
+ const latLon = Projection.tileCenterToLatLon(tileMatrix, xyzData.tile);
122
+ req.set('location', latLon);
123
+
124
+ return xyzData;
125
+ },
126
+ };
@@ -1,8 +1,9 @@
1
- import { Bounds, ImageFormat, Nztm2000QuadTms, TileMatrixSet, WmtsProvider } from '@basemaps/geo';
1
+ import { Config, ConfigImagery, ConfigLayer, ConfigTileSet, standardizeLayerName } from '@basemaps/config';
2
+ import { Bounds, GoogleTms, ImageFormat, TileMatrixSet, WmtsProvider } from '@basemaps/geo';
2
3
  import { Projection, V, VNodeElement } from '@basemaps/shared';
3
4
  import { ImageFormatOrder } from '@basemaps/tiler';
4
- import { BBox, Wgs84 } from '@linzjs/geojson';
5
- import { TileSetRaster } from './tile.set.raster.js';
5
+ import { BoundingBox } from '@cogeotiff/core';
6
+ import { BBox } from '@linzjs/geojson';
6
7
 
7
8
  const CapabilitiesAttrs = {
8
9
  xmlns: 'http://www.opengis.net/wmts/1.0',
@@ -15,77 +16,115 @@ const CapabilitiesAttrs = {
15
16
  version: '1.0.0',
16
17
  };
17
18
 
18
- function wgs84Extent(layer: TileSetRaster): BBox {
19
- return Projection.get(layer.tileMatrix).boundsToWgs84BoundingBox(layer.extent);
20
- }
21
-
22
- /**
23
- * Default the tile matrix id to the projection of the TileMatrixSet
24
- */
25
- export function getTileMatrixId(tileMatrix: TileMatrixSet): string {
26
- // TODO this should really change everything to identifier
27
- if (tileMatrix.identifier === Nztm2000QuadTms.identifier) return Nztm2000QuadTms.identifier;
28
- return tileMatrix.projection.toEpsgString();
19
+ function wgs84Extent(tileMatrix: TileMatrixSet, bbox: BoundingBox): BBox {
20
+ return Projection.get(tileMatrix).boundsToWgs84BoundingBox(bbox);
29
21
  }
30
22
 
31
23
  export interface WmtsCapabilitiesParams {
24
+ /** Base URL for tile server */
32
25
  httpBase: string;
33
26
  provider?: WmtsProvider;
34
- layers: TileSetRaster[];
27
+ /** Tileset to export into WMTS */
28
+ tileSet: ConfigTileSet;
29
+ /** List of tile matrixes to output */
30
+ tileMatrix: TileMatrixSet[];
31
+ /** Should WMTS Layers be created for each imagery set inside this tileSet */
32
+ isIndividualLayers: boolean;
33
+ /** All the imagery used by the tileSet and tileMatrixes */
34
+ imagery: Map<string, ConfigImagery>;
35
+ /** API key to append to all resource urls */
35
36
  apiKey?: string;
36
- formats?: ImageFormat[];
37
+ /** Limit the output to the following image formats other wise @see ImageFormatOrder */
38
+ formats?: ImageFormat[] | null;
39
+ }
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)}`;
37
52
  }
38
53
 
39
54
  export class WmtsCapabilities {
40
55
  httpBase: string;
41
56
  provider?: WmtsProvider;
42
-
43
- layers: Map<string, TileSetRaster[]> = new Map();
44
-
57
+ tileSet: ConfigTileSet;
45
58
  apiKey?: string;
46
59
  tileMatrixSets = new Map<string, TileMatrixSet>();
60
+ imagery: Map<string, ConfigImagery>;
47
61
  formats: ImageFormat[];
62
+ isIndividualLayers = false;
48
63
 
49
64
  constructor(params: WmtsCapabilitiesParams) {
50
65
  this.httpBase = params.httpBase;
51
66
  this.provider = params.provider;
67
+ this.tileSet = params.tileSet;
68
+ this.isIndividualLayers = params.isIndividualLayers;
69
+ for (const tms of params.tileMatrix) this.tileMatrixSets.set(tms.identifier, tms);
70
+ this.apiKey = params.apiKey;
71
+ this.formats = params.formats ?? ImageFormatOrder;
72
+ this.imagery = params.imagery;
73
+ }
52
74
 
53
- for (const layer of params.layers) {
54
- // TODO is grouping by name the best option
55
- let existing = this.layers.get(layer.fullName);
56
- if (existing == null) {
57
- existing = [];
58
- this.layers.set(layer.fullName, existing);
75
+ async loadImagery(): Promise<void> {
76
+ const ids = new Set<string>();
77
+ for (const tms of this.tileMatrixSets.values()) {
78
+ for (const layer of this.tileSet.layers) {
79
+ const layerId = layer[tms.projection.code];
80
+ if (layerId != null) ids.add(layerId);
59
81
  }
60
- // TODO should a error be thrown here if the projection is invalid
61
- existing.push(layer);
62
-
63
- this.tileMatrixSets.set(layer.tileMatrix.identifier, layer.tileMatrix);
64
82
  }
65
- this.apiKey = params.apiKey;
66
- this.formats = params.formats ?? ImageFormatOrder;
83
+ this.imagery = await Config.Imagery.getAll(ids);
67
84
  }
68
85
 
69
- buildWgs84BoundingBox(layers: TileSetRaster[], tagName = 'ows:WGS84BoundingBox'): VNodeElement {
70
- let bbox = wgs84Extent(layers[0]);
71
- for (let i = 1; i < layers.length; ++i) {
72
- bbox = Wgs84.union(bbox, wgs84Extent(layers[i]));
86
+ buildWgs84BoundingBox(tms: TileMatrixSet, layers: Bounds[]): VNodeElement {
87
+ let bbox: BBox;
88
+ if (layers.length > 0) {
89
+ let bounds = layers[0];
90
+ for (let i = 1; i < layers.length; i++) {
91
+ bounds = bounds.union(layers[i]);
92
+ }
93
+ bbox = wgs84Extent(tms, bounds.toJson());
94
+ } else {
95
+ // No layers provided assume extent is the size of the tile matrix set :shrug: ?
96
+ bbox = wgs84Extent(tms, tms.extent);
97
+ }
98
+
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;
73
103
  }
74
104
 
75
- return V(
76
- tagName,
77
- { crs: 'urn:ogc:def:crs:OGC:2:84' },
78
- bbox[2] > 180
79
- ? [V('ows:LowerCorner', `-180 -90`), V('ows:UpperCorner', `180 90`)]
80
- : [V('ows:LowerCorner', `${bbox[0]} ${bbox[1]}`), V('ows:UpperCorner', `${bbox[2]} ${bbox[3]}`)],
81
- );
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
+ ]);
82
109
  }
83
110
 
84
- buildBoundingBox(tms: TileMatrixSet, extent: Bounds): VNodeElement {
85
- const bbox = extent.toBbox();
111
+ /** Combine all the bounds of the imagery inside the layers into a extent for the imagery set */
112
+ buildBoundingBoxFromImagery(tms: TileMatrixSet, layers: ConfigLayer[]): VNodeElement | null {
113
+ let bounds;
114
+ for (const layer of layers) {
115
+ const imgId = layer[tms.projection.code];
116
+ if (imgId == null) continue;
117
+ const img = this.imagery.get(imgId);
118
+ if (img == null) continue;
119
+ if (bounds == null) bounds = Bounds.fromJson(img.bounds);
120
+ else bounds = bounds.union(Bounds.fromJson(img.bounds));
121
+ }
122
+ if (bounds == null) return null;
123
+
124
+ const bbox = bounds.toBbox();
86
125
  return V('ows:BoundingBox', { crs: tms.projection.toUrn() }, [
87
- V('ows:LowerCorner', `${bbox[tms.indexX]} ${bbox[tms.indexY]}`),
88
- 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)),
89
128
  ]);
90
129
  }
91
130
 
@@ -124,13 +163,13 @@ export class WmtsCapabilities {
124
163
  ];
125
164
  }
126
165
 
127
- buildTileUrl(tileSet: TileSetRaster, suffix: string): string {
166
+ buildTileUrl(tileSetId: string, suffix: string): string {
128
167
  const apiSuffix = this.apiKey ? `?api=${this.apiKey}` : '';
129
168
  return [
130
169
  this.httpBase,
131
170
  'v1',
132
171
  'tiles',
133
- tileSet.fullName,
172
+ tileSetId,
134
173
  '{TileMatrixSet}',
135
174
  '{TileMatrix}',
136
175
  '{TileCol}',
@@ -138,37 +177,91 @@ export class WmtsCapabilities {
138
177
  ].join('/');
139
178
  }
140
179
 
141
- buildResourceUrl(tileSet: TileSetRaster, suffix: string): VNodeElement {
180
+ buildResourceUrl(tileSetId: string, suffix: string): VNodeElement {
142
181
  return V('ResourceURL', {
143
182
  format: 'image/' + suffix,
144
183
  resourceType: 'tile',
145
- template: this.buildTileUrl(tileSet, suffix),
184
+ template: this.buildTileUrl(tileSetId, suffix),
146
185
  });
147
186
  }
148
187
 
149
- buildLayer(layers: TileSetRaster[]): VNodeElement {
150
- const matrixSets = new Set<string>();
188
+ buildLayerFromImagery(layer: ConfigLayer): VNodeElement | null {
189
+ const matrixSets = new Set<TileMatrixSet>();
151
190
  const matrixSetNodes: VNodeElement[] = [];
152
- for (const layer of layers) {
153
- if (matrixSets.has(layer.tileMatrix.identifier)) continue;
154
- matrixSets.add(layer.tileMatrix.identifier);
155
- matrixSetNodes.push(V('TileMatrixSetLink', [V('TileMatrixSet', getTileMatrixId(layer.tileMatrix))]));
191
+ for (const tms of this.tileMatrixSets.values()) {
192
+ const imdIg = layer[tms.projection.code];
193
+ if (imdIg == null) continue;
194
+ const img = this.imagery.get(imdIg);
195
+ if (img == null) continue;
196
+ matrixSetNodes.push(V('TileMatrixSetLink', [V('TileMatrixSet', tms.identifier)]));
197
+ matrixSets.add(tms);
156
198
  }
157
199
 
158
- const [firstLayer] = layers;
200
+ const layerNameId = standardizeLayerName(layer.name);
201
+ const matrixSetList = [...matrixSets.values()];
202
+ const firstMatrix = matrixSetList[0];
203
+ if (firstMatrix == null) return null;
204
+ const firstImg = this.imagery.get(layer[firstMatrix.projection.code] ?? '');
205
+ if (firstImg == null) return null;
206
+
159
207
  return V('Layer', [
160
- V('ows:Title', firstLayer.title),
161
- V('ows:Abstract', firstLayer.description),
162
- V('ows:Identifier', firstLayer.fullName),
163
- ...layers.map((layer) => this.buildBoundingBox(layer.tileMatrix, layer.extent)),
164
- this.buildWgs84BoundingBox(layers),
208
+ V('ows:Title', layer.title ?? layerNameId),
209
+ V('ows:Abstract', ''),
210
+ V('ows:Identifier', layerNameId),
211
+ this.buildKeywords(firstImg),
212
+ ...matrixSetList.map((tms) => {
213
+ return this.buildBoundingBoxFromImagery(tms, [layer]);
214
+ }),
215
+ this.buildWgs84BoundingBox(firstMatrix, [Bounds.fromJson(firstImg.bounds)]),
165
216
  this.buildStyle(),
166
217
  ...this.formats.map((fmt) => V('Format', 'image/' + fmt)),
167
218
  ...matrixSetNodes,
168
- ...this.formats.map((fmt) => this.buildResourceUrl(firstLayer, fmt)),
219
+ ...this.formats.map((fmt) => this.buildResourceUrl(layerNameId, fmt)),
169
220
  ]);
170
221
  }
171
222
 
223
+ buildLayer(layer: ConfigTileSet): VNodeElement {
224
+ const matrixSets = new Set<TileMatrixSet>();
225
+ const matrixSetNodes: VNodeElement[] = [];
226
+ for (const tms of this.tileMatrixSets.values()) {
227
+ if (layer.layers.find((f) => f[tms.projection.code] != null)) {
228
+ matrixSetNodes.push(V('TileMatrixSetLink', [V('TileMatrixSet', tms.identifier)]));
229
+ matrixSets.add(tms);
230
+ }
231
+ }
232
+ const layerNameId = standardizeLayerName(layer.name);
233
+ const matrixSetList = [...matrixSets.values()];
234
+ const firstMatrix = matrixSetList[0];
235
+ if (firstMatrix == null) throw new Error('No matrix sets found for layer ' + layer.name);
236
+
237
+ // Prefer using the web mercator tms for bounds
238
+ const webMercatorOrFirst = matrixSetList.find((f) => f.identifier === GoogleTms.identifier) ?? firstMatrix;
239
+ const bounds: Bounds[] = [];
240
+ for (const l of layer.layers) {
241
+ const img = this.imagery.get(l[webMercatorOrFirst.projection.code] ?? '');
242
+ if (img == null) continue;
243
+ bounds.push(Bounds.fromJson(img.bounds));
244
+ }
245
+
246
+ return V('Layer', [
247
+ V('ows:Title', layer.title ?? layerNameId),
248
+ V('ows:Abstract', layer.description ?? ''),
249
+ V('ows:Identifier', layerNameId),
250
+ this.buildKeywords(layer),
251
+ ...[...matrixSets.values()].map((tms) => this.buildBoundingBoxFromImagery(tms, layer.layers)),
252
+ this.buildWgs84BoundingBox(webMercatorOrFirst, bounds),
253
+ this.buildStyle(),
254
+ ...this.formats.map((fmt) => V('Format', 'image/' + fmt)),
255
+ ...matrixSetNodes,
256
+ ...this.formats.map((fmt) => this.buildResourceUrl(layerNameId, fmt)),
257
+ ]);
258
+ }
259
+
260
+ buildKeywords(tileSet: { category?: string }): VNodeElement {
261
+ if (tileSet.category == null) return V('ows:Keywords');
262
+ return V('ows:Keywords', [V('ows:Keyword', tileSet.category)]);
263
+ }
264
+
172
265
  buildStyle(): VNodeElement {
173
266
  return V('Style', { isDefault: 'true' }, [V('ows:Title', 'Default Style'), V('ows:Identifier', 'default')]);
174
267
  }
@@ -177,7 +270,7 @@ export class WmtsCapabilities {
177
270
  return V('TileMatrixSet', [
178
271
  V('ows:Title', tms.def.title),
179
272
  tms.def.abstract ? V('ows:Abstract', tms.def.abstract) : null,
180
- V('ows:Identifier', getTileMatrixId(tms)),
273
+ V('ows:Identifier', tms.identifier),
181
274
  V('ows:SupportedCRS', tms.projection.toUrn()),
182
275
  tms.def.wellKnownScaleSet ? V('WellKnownScaleSet', tms.def.wellKnownScaleSet) : null,
183
276
  ...tms.def.tileMatrix.map((c) => {
@@ -194,16 +287,25 @@ export class WmtsCapabilities {
194
287
  ]);
195
288
  }
196
289
  toVNode(): VNodeElement {
197
- const layers: VNodeElement[] = [];
198
- for (const tileSets of this.layers.values()) {
199
- layers.push(this.buildLayer(tileSets));
290
+ const layers: (VNodeElement | null)[] = [];
291
+ layers.push(this.buildLayer(this.tileSet));
292
+
293
+ if (this.isIndividualLayers) {
294
+ const layerByName = new Map<string, ConfigLayer>();
295
+ // Dedupe the layers by unique name
296
+ for (const img of this.tileSet.layers) layerByName.set(standardizeLayerName(img.name), img);
297
+ const orderedLayers = Array.from(layerByName.values()).sort((a, b) =>
298
+ (a.title ?? a.name).localeCompare(b.title ?? b.name),
299
+ );
300
+ for (const img of orderedLayers) layers.push(this.buildLayerFromImagery(img));
200
301
  }
302
+
201
303
  for (const tms of this.tileMatrixSets.values()) layers.push(this.buildTileMatrixSet(tms));
202
304
 
203
305
  return V('Capabilities', CapabilitiesAttrs, [...this.buildProvider(), V('Contents', layers)]);
204
306
  }
205
307
 
206
308
  toXml(): string {
207
- return '<?xml version="1.0"?>\n' + this.toVNode().toString();
309
+ return '<?xml version="1.0" encoding="utf-8"?>\n' + this.toVNode().toString();
208
310
  }
209
311
  }