@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
@@ -1,4 +1,4 @@
1
- import { ConfigImagery, ConfigLayer, ConfigProvider, TileSetType } from '@basemaps/config';
1
+ import { ConfigTileSet, TileSetType } from '@basemaps/config';
2
2
  import {
3
3
  AttributionCollection,
4
4
  AttributionItem,
@@ -7,32 +7,20 @@ import {
7
7
  GoogleTms,
8
8
  NamedBounds,
9
9
  Stac,
10
- StacCollection,
11
10
  StacExtent,
12
11
  TileMatrixSet,
13
12
  } from '@basemaps/geo';
14
- import {
15
- CompositeError,
16
- Config,
17
- extractYearRangeFromName,
18
- fsa,
19
- Projection,
20
- setNameAndProjection,
21
- tileAttributionFromPath,
22
- titleizeImageryName,
23
- } from '@basemaps/shared';
13
+ import { Config, extractYearRangeFromName, Projection, titleizeImageryName } from '@basemaps/shared';
24
14
  import { BBox, MultiPolygon, multiPolygonToWgs84, Pair, union, Wgs84 } from '@linzjs/geojson';
25
15
  import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
26
- import { createHash } from 'crypto';
27
- import { Router } from '../router.js';
28
- import { TileSets } from '../tile.set.cache.js';
29
- import { TileSetRaster } from '../tile.set.raster.js';
16
+
17
+ import { Etag } from '../util/etag.js';
18
+ import { NotFound, NotModified } from '../util/response.js';
19
+ import { Validate } from '../util/validate.js';
30
20
 
31
21
  /** Amount to pad imagery bounds to avoid fragmenting polygons */
32
22
  const SmoothPadding = 1 + 1e-10; // about 1/100th of a millimeter at equator
33
23
 
34
- const NotFound = new LambdaHttpResponse(404, 'Not Found');
35
-
36
24
  const Precision = 10 ** 8;
37
25
 
38
26
  /**
@@ -74,90 +62,38 @@ function createCoordinates(bbox: BBox, files: NamedBounds[], proj: Projection):
74
62
  return multiPolygonToWgs84(coordinates, roundToWgs84);
75
63
  }
76
64
 
77
- async function readStac(uri: string): Promise<StacCollection | null> {
78
- try {
79
- return await fsa.readJson<StacCollection>(uri);
80
- } catch (err) {
81
- if (CompositeError.isCompositeError(err) && err.code < 500) {
82
- return null;
83
- }
84
- throw err;
85
- }
86
- }
87
-
88
- export function createAttributionCollection(
89
- tileSet: TileSetRaster,
90
- stac: StacCollection | null | undefined,
91
- imagery: ConfigImagery,
92
- layer: ConfigLayer,
93
- host: ConfigProvider,
94
- extent: StacExtent,
95
- ): AttributionCollection {
96
- const tileMatrix = tileSet.tileMatrix;
97
- return {
98
- stac_version: Stac.Version,
99
- license: stac?.license ?? Stac.License,
100
- 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',
106
- extent,
107
- links: [],
108
- summaries: {
109
- 'linz:zoom': {
110
- min: TileMatrixSet.convertZoomLevel(layer.minZoom ? layer.minZoom : 0, GoogleTms, tileMatrix, true),
111
- max: TileMatrixSet.convertZoomLevel(layer.maxZoom ? layer.maxZoom : 32, GoogleTms, tileMatrix, true),
112
- },
113
- 'linz:priority': [1000 + tileSet.tileSet.layers.indexOf(layer)],
114
- },
115
- };
116
- }
117
-
118
65
  /**
119
66
  * Build a Single File STAC for the given TileSet.
120
67
  *
121
68
  * For now this is the minimal set required for attribution. This can be embellished later with
122
69
  * links and assets for a more comprehensive STAC file.
123
70
  */
124
- async function tileSetAttribution(tileSet: TileSetRaster): Promise<AttributionStac | null> {
125
- const proj = Projection.get(tileSet.tileMatrix);
126
- const stacFiles = new Map<string, Promise<StacCollection | null>>();
71
+ async function tileSetAttribution(
72
+ req: LambdaHttpRequest,
73
+ tileSet: ConfigTileSet,
74
+ tileMatrix: TileMatrixSet,
75
+ ): Promise<AttributionStac | null> {
76
+ const proj = Projection.get(tileMatrix);
127
77
  const cols: AttributionCollection[] = [];
128
78
  const items: AttributionItem[] = [];
129
79
 
130
- // read all stac files in parallel
131
- for (const layer of tileSet.tileSet.layers) {
132
- const imgId = layer[proj.epsg.code];
133
- if (imgId == null) continue;
134
- const im = tileSet.imagery.get(imgId);
135
- if (im == null) continue;
136
- if (stacFiles.get(im.uri) == null) {
137
- stacFiles.set(im.uri, readStac(fsa.join(im.uri, 'collection.json')));
138
- }
139
- }
80
+ const imagery = await Config.getAllImagery(tileSet.layers, [tileMatrix.projection]);
140
81
 
141
82
  const host = await Config.Provider.get(Config.Provider.id('linz'));
142
83
  if (host == null) return null;
143
84
 
144
- for (const layer of tileSet.tileSet.layers) {
85
+ for (const layer of tileSet.layers) {
145
86
  const imgId = layer[proj.epsg.code];
146
87
  if (imgId == null) continue;
147
- const im = tileSet.imagery.get(imgId);
88
+ const im = imagery.get(imgId);
148
89
  if (im == null) continue;
149
- const stac = await stacFiles.get(im.uri);
150
90
 
151
91
  const bbox = proj.boundsToWgs84BoundingBox(im.bounds).map(roundNumber) as BBox;
152
92
 
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
- }
93
+ const years = extractYearRangeFromName(im.name);
94
+ if (years[0] === -1) throw new Error('Missing date in imagery name: ' + im.name);
95
+ const interval = [years.map((y) => `${y}-01-01T00:00:00Z`) as [string, string]];
96
+
161
97
  const extent: StacExtent = { spatial: { bbox: [bbox] }, temporal: { interval } };
162
98
 
163
99
  items.push({
@@ -168,68 +104,80 @@ async function tileSetAttribution(tileSet: TileSetRaster): Promise<AttributionSt
168
104
  assets: {},
169
105
  links: [],
170
106
  bbox,
171
- geometry: {
172
- type: 'MultiPolygon',
173
- coordinates: createCoordinates(bbox, im.files, proj),
174
- },
107
+ geometry: { type: 'MultiPolygon', coordinates: createCoordinates(bbox, im.files, proj) },
175
108
  properties: {
176
- title: titleizeImageryName(im.name),
109
+ title: im.title ?? titleizeImageryName(im.name),
110
+ category: im.category,
177
111
  datetime: null,
178
112
  start_datetime: interval[0][0],
179
113
  end_datetime: interval[0][1],
180
114
  },
181
115
  });
182
116
 
183
- cols.push(createAttributionCollection(tileSet, stac, im, layer, host, extent));
117
+ const zoomMin = TileMatrixSet.convertZoomLevel(layer.minZoom ? layer.minZoom : 0, GoogleTms, tileMatrix, true);
118
+ const zoomMax = TileMatrixSet.convertZoomLevel(layer.maxZoom ? layer.maxZoom : 32, GoogleTms, tileMatrix, true);
119
+ cols.push({
120
+ stac_version: Stac.Version,
121
+ license: Stac.License,
122
+ id: im.id,
123
+ providers: [{ name: host.serviceProvider.name, url: host.serviceProvider.site, roles: ['host'] }],
124
+ title: im.title ?? titleizeImageryName(im.name),
125
+ description: 'No description',
126
+ extent,
127
+ links: [],
128
+ summaries: {
129
+ 'linz:category': im.category,
130
+ 'linz:zoom': { min: zoomMin, max: zoomMax },
131
+ 'linz:priority': [1000 + tileSet.layers.indexOf(layer)],
132
+ },
133
+ });
184
134
  }
185
135
  return {
186
136
  id: tileSet.id,
187
137
  type: 'FeatureCollection',
188
138
  stac_version: Stac.Version,
189
139
  stac_extensions: ['single-file-stac'],
190
- title: tileSet.title,
191
- description: tileSet.description,
140
+ title: tileSet.title ?? 'No title',
141
+ description: tileSet.description ?? 'No Description',
192
142
  features: items,
193
143
  collections: cols,
194
144
  links: [],
195
145
  };
196
146
  }
197
147
 
148
+ export interface TileAttributionGet {
149
+ Params: {
150
+ tileSet: string;
151
+ tileMatrix: string;
152
+ };
153
+ }
154
+
198
155
  /**
199
156
  * Create a LambdaHttpResponse for a attribution request
200
157
  */
201
- export async function attribution(req: LambdaHttpRequest): Promise<LambdaHttpResponse> {
202
- const action = Router.action(req);
203
- const data = tileAttributionFromPath(action.rest);
204
- if (data == null) return NotFound;
205
- setNameAndProjection(req, data);
158
+ export async function tileAttributionGet(req: LambdaHttpRequest<TileAttributionGet>): Promise<LambdaHttpResponse> {
159
+ const tileMatrix = Validate.getTileMatrixSet(req.params.tileMatrix);
160
+ if (tileMatrix == null) throw new LambdaHttpResponse(404, 'Tile Matrix not found');
206
161
 
207
162
  req.timer.start('tileset:load');
208
- const tileSet = await TileSets.get(data.name, data.tileMatrix);
163
+ const tileSet = await Config.TileSet.get(Config.TileSet.id(req.params.tileSet));
209
164
  req.timer.end('tileset:load');
210
- if (tileSet == null || tileSet.type === TileSetType.Vector) return NotFound;
165
+ if (tileSet == null || tileSet.type === TileSetType.Vector) return NotFound();
211
166
 
212
- const cacheKey = createHash('sha256').update(JSON.stringify(tileSet.tileSet)).digest('base64');
213
-
214
- const ifNoneMatch = req.header(HttpHeader.IfNoneMatch);
215
- if (ifNoneMatch != null && ifNoneMatch.indexOf(cacheKey) > -1) {
216
- req.set('cache', { key: cacheKey, hit: true, match: ifNoneMatch });
217
- return new LambdaHttpResponse(304, 'Not modified');
218
- }
167
+ const cacheKey = Etag.key(tileSet);
168
+ if (Etag.isNotModified(req, cacheKey)) return NotModified();
219
169
 
220
170
  req.timer.start('stac:load');
221
- const attributions = await tileSetAttribution(tileSet);
171
+ const attributions = await tileSetAttribution(req, tileSet, tileMatrix);
222
172
  req.timer.end('stac:load');
223
173
 
224
- if (attributions == null) return NotFound;
174
+ if (attributions == null) return NotFound();
225
175
 
226
176
  const response = new LambdaHttpResponse(200, 'ok');
227
177
 
228
178
  response.header(HttpHeader.ETag, cacheKey);
229
- // Keep fresh for one day; otherwise use cache but refresh cache for next time
230
- response.header(HttpHeader.CacheControl, 'public, max-age=86400, stale-while-revalidate=604800');
231
-
179
+ // Keep fresh for 7 days; otherwise use cache but refresh cache for next time
180
+ response.header(HttpHeader.CacheControl, 'public, max-age=604800, stale-while-revalidate=86400');
232
181
  response.json(attributions);
233
-
234
182
  return response;
235
183
  }
@@ -1,8 +1,10 @@
1
1
  import { Env } from '@basemaps/shared';
2
2
  import { fsa } from '@chunkd/fs';
3
- import { LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
3
+ import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
4
4
  import path from 'path';
5
- import { NotFound } from './response.js';
5
+ import { isGzip, serveFromCotar } from '../util/cotar.serve.js';
6
+ import { Etag } from '../util/etag.js';
7
+ import { NotFound, NotModified } from '../util/response.js';
6
8
 
7
9
  interface FontGet {
8
10
  Params: { fontStack: string; range: string };
@@ -10,15 +12,27 @@ interface FontGet {
10
12
 
11
13
  export async function fontGet(req: LambdaHttpRequest<FontGet>): Promise<LambdaHttpResponse> {
12
14
  const assetLocation = Env.get(Env.AssetLocation);
13
- if (assetLocation == null) return NotFound;
15
+ if (assetLocation == null) return NotFound();
16
+
17
+ const targetFile = path.join('fonts', req.params.fontStack, req.params.range) + '.pbf';
18
+ if (assetLocation.endsWith('.tar.co')) {
19
+ return serveFromCotar(req, assetLocation, targetFile, 'application/x-protobuf');
20
+ }
14
21
 
15
22
  try {
16
- const filePath = fsa.join(assetLocation, path.join('fonts', req.params.fontStack, req.params.range)) + '.pbf';
23
+ const filePath = fsa.join(assetLocation, targetFile);
17
24
  const buf = await fsa.read(filePath);
18
25
 
19
- return LambdaHttpResponse.ok().buffer(buf, 'application/x-protobuf');
26
+ const cacheKey = Etag.key(buf);
27
+ if (Etag.isNotModified(req, cacheKey)) return NotModified();
28
+
29
+ const response = LambdaHttpResponse.ok().buffer(buf, 'application/x-protobuf');
30
+ response.header(HttpHeader.ETag, cacheKey);
31
+ response.header(HttpHeader.CacheControl, 'public, max-age=604800, stale-while-revalidate=86400');
32
+ if (isGzip(buf)) response.header(HttpHeader.ContentEncoding, 'gzip');
33
+ return response;
20
34
  } catch (e: any) {
21
- if (e.code === 404) return NotFound;
35
+ if (e.code === 404) return NotFound();
22
36
  throw e;
23
37
  }
24
38
  }
@@ -38,17 +52,25 @@ export async function getFonts(fontPath: string): Promise<string[]> {
38
52
  return [...fonts].sort();
39
53
  }
40
54
 
41
- export async function fontList(): Promise<LambdaHttpResponse> {
55
+ export async function fontList(req: LambdaHttpRequest): Promise<LambdaHttpResponse> {
42
56
  const assetLocation = Env.get(Env.AssetLocation);
43
- if (assetLocation == null) return NotFound;
57
+ if (assetLocation == null) return NotFound();
58
+
59
+ if (assetLocation.endsWith('.tar.co')) return serveFromCotar(req, assetLocation, 'fonts.json', 'application/json');
44
60
 
45
61
  try {
46
62
  const filePath = fsa.join(assetLocation, '/fonts');
47
63
  const fonts = await getFonts(filePath);
48
64
 
49
- return LambdaHttpResponse.ok().buffer(JSON.stringify(fonts), 'application/json');
65
+ const cacheKey = Etag.key(fonts);
66
+ if (Etag.isNotModified(req, cacheKey)) return NotModified();
67
+
68
+ const response = LambdaHttpResponse.ok().buffer(JSON.stringify(fonts), 'application/json');
69
+ response.header(HttpHeader.ETag, cacheKey);
70
+ response.header(HttpHeader.CacheControl, 'public, max-age=604800, stale-while-revalidate=86400');
71
+ return response;
50
72
  } catch (e: any) {
51
- if (e.code === 404) return NotFound;
73
+ if (e.code === 404) return NotFound();
52
74
  throw e;
53
75
  }
54
76
  }
@@ -1,27 +1,29 @@
1
- import { GoogleTms, Nztm2000QuadTms, ImageFormat } from '@basemaps/geo';
2
- import { TileDataXyz, TileType } from '@basemaps/shared';
1
+ import { Config, ConfigTileSetRaster } from '@basemaps/config';
2
+ import { GoogleTms, ImageFormat, Nztm2000QuadTms } from '@basemaps/geo';
3
3
  import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
4
4
  import * as fs from 'fs';
5
5
  import * as path from 'path';
6
6
  import PixelMatch from 'pixelmatch';
7
7
  import Sharp from 'sharp';
8
8
  import url from 'url';
9
- import { TileSets } from '../tile.set.cache.js';
9
+ import { TileXyz } from '../util/validate.js';
10
+ import { TileXyzRaster } from './tile.xyz.raster.js';
10
11
 
11
- interface TestTile extends TileDataXyz {
12
+ interface TestTile extends TileXyz {
12
13
  buf?: Buffer;
13
14
  }
14
15
 
15
16
  export const TestTiles: TestTile[] = [
16
- { type: TileType.Tile, name: 'health', tileMatrix: GoogleTms, ext: ImageFormat.Png, x: 252, y: 156, z: 8 },
17
- { type: TileType.Tile, name: 'health', tileMatrix: Nztm2000QuadTms, ext: ImageFormat.Png, x: 30, y: 33, z: 6 },
17
+ { tileSet: 'health', tileMatrix: GoogleTms, tileType: ImageFormat.Png, tile: { x: 252, y: 156, z: 8 } },
18
+ { tileSet: 'health', tileMatrix: Nztm2000QuadTms, tileType: ImageFormat.Png, tile: { x: 30, y: 33, z: 6 } },
18
19
  ];
19
20
  const TileSize = 256;
20
21
 
21
22
  export async function getTestBuffer(test: TestTile): Promise<Buffer> {
22
23
  if (Buffer.isBuffer(test.buf)) return test.buf;
24
+ const tile = test.tile;
23
25
 
24
- const expectedFile = `static/expected_tile_${test.tileMatrix.identifier}_${test.x}_${test.y}_z${test.z}.${test.ext}`;
26
+ const expectedFile = `static/expected_tile_${test.tileMatrix.identifier}_${tile.x}_${tile.y}_z${tile.z}.${test.tileType}`;
25
27
  // Initiate test img buffer if not defined
26
28
  try {
27
29
  return await fs.promises.readFile(expectedFile);
@@ -33,7 +35,9 @@ export async function getTestBuffer(test: TestTile): Promise<Buffer> {
33
35
  }
34
36
 
35
37
  export async function updateExpectedTile(test: TestTile, newTileData: Buffer, difference: Buffer): Promise<void> {
36
- const expectedFileName = `static/expected_tile_${test.tileMatrix.identifier}_${test.x}_${test.y}_z${test.z}.${test.ext}`;
38
+ const tile = test.tile;
39
+
40
+ const expectedFileName = `static/expected_tile_${test.tileMatrix.identifier}_${tile.x}_${tile.y}_z${tile.z}.${test.tileType}`;
37
41
  await fs.promises.writeFile(expectedFileName, newTileData);
38
42
  const imgPng = await Sharp(difference, { raw: { width: TileSize, height: TileSize, channels: 4 } })
39
43
  .png()
@@ -48,12 +52,12 @@ export async function updateExpectedTile(test: TestTile, newTileData: Buffer, di
48
52
  *
49
53
  * @throws LambdaHttpResponse for failure health test
50
54
  */
51
- export async function Health(req: LambdaHttpRequest): Promise<LambdaHttpResponse> {
55
+ export async function healthGet(req: LambdaHttpRequest): Promise<LambdaHttpResponse> {
56
+ const tileSet = await Config.TileSet.get(Config.TileSet.id('health'));
57
+ if (tileSet == null) throw new LambdaHttpResponse(500, 'TileSet: "health" not found');
52
58
  for (const test of TestTiles) {
53
- const tileSet = await TileSets.get('health', test.tileMatrix);
54
- if (tileSet == null) throw new LambdaHttpResponse(500, 'TileSet: "health" not found');
55
59
  // Get the parse response tile to raw buffer
56
- const response = await tileSet.tile(req, test);
60
+ const response = await TileXyzRaster.tile(req, tileSet as ConfigTileSetRaster, test);
57
61
  if (response.status !== 200) return new LambdaHttpResponse(500, response.statusDescription);
58
62
  if (!Buffer.isBuffer(response._body)) throw new LambdaHttpResponse(500, 'Not a Buffer response content.');
59
63
  const resImgBuffer = await Sharp(response._body).raw().toBuffer();
@@ -68,10 +72,7 @@ export async function Health(req: LambdaHttpRequest): Promise<LambdaHttpResponse
68
72
  if (missMatchedPixels) {
69
73
  /** Uncomment this to overwite the expected files */
70
74
  // await updateExpectedTile(test, response._body as Buffer, outputBuffer);
71
- req.log.error(
72
- { missMatchedPixels, projection: test.tileMatrix.identifier, xyz: { x: test.x, y: test.y, z: test.z } },
73
- 'Health:MissMatch',
74
- );
75
+ req.log.error({ missMatchedPixels, projection: test.tileMatrix.identifier, xyz: test.tile }, 'Health:MissMatch');
75
76
  return new LambdaHttpResponse(500, 'TileSet does not match.');
76
77
  }
77
78
  }
@@ -1,12 +1,11 @@
1
1
  import { Config } from '@basemaps/config';
2
2
  import { fsa } from '@basemaps/shared';
3
3
  import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
4
- import { createHash } from 'crypto';
5
4
  import { promisify } from 'util';
6
5
  import { gzip } from 'zlib';
7
- import { Router } from '../router.js';
8
- import { NotModified } from './response.js';
9
- import { TileEtag } from './tile.etag.js';
6
+ import { isGzip } from '../util/cotar.serve.js';
7
+ import { Etag } from '../util/etag.js';
8
+ import { NotFound, NotModified } from '../util/response.js';
10
9
 
11
10
  const gzipP = promisify(gzip);
12
11
 
@@ -17,6 +16,10 @@ export function isAllowedFile(f: string): boolean {
17
16
  return false;
18
17
  }
19
18
 
19
+ interface ImageryGet {
20
+ Params: { imageryId: string; fileName: string };
21
+ }
22
+
20
23
  /**
21
24
  * Get metadata around the imagery such as the source bounding box or the bounding box of the COGS
22
25
  *
@@ -27,30 +30,30 @@ export function isAllowedFile(f: string): boolean {
27
30
  * - /v1/imagery/:imageryId/collection.json - STAC Collection
28
31
  * - /v1/imagery/:imageryId/15-32659-21603.json - STAC Item
29
32
  */
30
- export async function Imagery(req: LambdaHttpRequest): Promise<LambdaHttpResponse> {
31
- const { rest } = Router.action(req);
32
- const [imageryId, requestType] = rest;
33
- if (!isAllowedFile(requestType)) return new LambdaHttpResponse(404, 'Not found');
33
+ export async function imageryGet(req: LambdaHttpRequest<ImageryGet>): Promise<LambdaHttpResponse> {
34
+ const requestedFile = req.params.fileName;
35
+ if (!isAllowedFile(requestedFile)) return NotFound();
34
36
 
35
- const imagery = await Config.Imagery.get(Config.Imagery.id(imageryId));
36
- if (imagery == null) return new LambdaHttpResponse(404, 'Not found');
37
+ const imagery = await Config.Imagery.get(Config.Imagery.id(req.params.imageryId));
38
+ if (imagery == null) return NotFound();
37
39
 
38
- const targetPath = fsa.join(imagery.uri, requestType);
40
+ const targetPath = fsa.join(imagery.uri, requestedFile);
39
41
 
40
42
  try {
41
43
  const buf = await fsa.read(targetPath);
42
- const cacheKey = createHash('sha256').update(buf).digest('base64');
43
44
 
44
- if (TileEtag.isNotModified(req, cacheKey)) return NotModified;
45
+ const cacheKey = Etag.key(buf);
46
+ if (Etag.isNotModified(req, cacheKey)) return NotModified();
45
47
 
46
48
  const response = new LambdaHttpResponse(200, 'ok');
47
49
  response.header(HttpHeader.ETag, cacheKey);
48
50
  response.header(HttpHeader.ContentEncoding, 'gzip');
49
- response.buffer(await gzipP(buf), 'application/json');
51
+ response.header(HttpHeader.CacheControl, 'public, max-age=604800, stale-while-revalidate=86400');
52
+ response.buffer(isGzip(buf) ? buf : await gzipP(buf), 'application/json');
50
53
  req.set('bytes', buf.byteLength);
51
54
  return response;
52
55
  } catch (e) {
53
56
  req.log.warn({ targetPath }, 'ImageryMetadata:Failed');
54
- return new LambdaHttpResponse(404, 'Not found');
57
+ return NotFound();
55
58
  }
56
59
  }
@@ -0,0 +1,8 @@
1
+ import { LambdaHttpResponse, HttpHeader } from '@linzjs/lambda';
2
+
3
+ const OkResponse = new LambdaHttpResponse(200, 'ok');
4
+ OkResponse.header(HttpHeader.CacheControl, 'no-store');
5
+
6
+ export async function pingGet(): Promise<LambdaHttpResponse> {
7
+ return OkResponse;
8
+ }
@@ -1,8 +1,10 @@
1
1
  import { Env } from '@basemaps/shared';
2
2
  import { fsa } from '@chunkd/fs';
3
3
  import path from 'path';
4
- import { LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
5
- import { NotFound } from './response.js';
4
+ import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
5
+ import { NotFound, NotModified } from '../util/response.js';
6
+ import { isGzip, serveFromCotar } from '../util/cotar.serve.js';
7
+ import { Etag } from '../util/etag.js';
6
8
 
7
9
  interface SpriteGet {
8
10
  Params: {
@@ -14,36 +16,32 @@ const Extensions = new Map();
14
16
  Extensions.set('.png', 'image/png');
15
17
  Extensions.set('.json', 'application/json');
16
18
 
17
- /**
18
- * Does a buffer look like a gzipped document instead of raw json
19
- *
20
- * Determined by checking the first two bytes are the gzip magic bytes `0x1f 0x8b`
21
- *
22
- * @see https://en.wikipedia.org/wiki/Gzip
23
- *
24
- */
25
- function isGzip(b: Buffer): boolean {
26
- return b[0] === 0x1f && b[1] === 0x8b;
27
- }
28
-
29
19
  export async function spriteGet(req: LambdaHttpRequest<SpriteGet>): Promise<LambdaHttpResponse> {
30
- const spriteLocation = Env.get(Env.AssetLocation);
31
- if (spriteLocation == null) return NotFound;
20
+ const assetLocation = Env.get(Env.AssetLocation);
21
+ if (assetLocation == null) return NotFound();
32
22
 
33
23
  const extension = path.extname(req.params.spriteName);
34
24
  const mimeType = Extensions.get(extension);
35
- if (mimeType == null) return NotFound;
25
+ if (mimeType == null) return NotFound();
26
+
27
+ const targetFile = fsa.join('sprites', req.params.spriteName);
28
+ if (assetLocation.endsWith('.tar.co')) return serveFromCotar(req, assetLocation, targetFile, mimeType);
36
29
 
37
30
  try {
38
- const filePath = fsa.join(spriteLocation, fsa.join('/sprites', req.params.spriteName));
31
+ const filePath = fsa.join(assetLocation, targetFile);
39
32
  req.set('target', filePath);
40
33
 
41
34
  const buf = await fsa.read(filePath);
42
- const res = LambdaHttpResponse.ok().buffer(buf, mimeType);
43
- if (isGzip(buf)) res.header('content-encoding', 'gzip');
44
- return res;
35
+ const cacheKey = Etag.key(buf);
36
+ if (Etag.isNotModified(req, cacheKey)) return NotModified();
37
+
38
+ const response = LambdaHttpResponse.ok().buffer(buf, mimeType);
39
+ response.header(HttpHeader.ETag, cacheKey);
40
+ response.header(HttpHeader.CacheControl, 'public, max-age=604800, stale-while-revalidate=86400');
41
+ if (isGzip(buf)) response.header(HttpHeader.ContentEncoding, 'gzip');
42
+ return response;
45
43
  } catch (e: any) {
46
- if (e.code === 404) return NotFound;
44
+ if (e.code === 404) return NotFound();
47
45
  throw e;
48
46
  }
49
47
  }
@@ -1,36 +1,41 @@
1
1
  import { GoogleTms, TileJson, TileMatrixSet } from '@basemaps/geo';
2
- import { Env, extractTileMatrixSet } from '@basemaps/shared';
2
+ import { Config, Env } from '@basemaps/shared';
3
3
  import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
4
- import { Router } from '../router.js';
5
- import { TileSets } from '../tile.set.cache.js';
6
- import { getTileMatrixId } from '../wmts.capability.js';
7
- import { NotFound } from './response.js';
4
+ import { NotFound } from '../util/response.js';
5
+ import { Validate } from '../util/validate.js';
6
+
7
+ export interface TileJsonGet {
8
+ Params: {
9
+ tileSet: string;
10
+ tileMatrix: string;
11
+ };
12
+ }
8
13
 
9
- export async function tileJson(req: LambdaHttpRequest): Promise<LambdaHttpResponse> {
10
- const { version, rest, name } = Router.action(req);
11
- if (rest.length !== 3) return NotFound;
14
+ export async function tileJsonGet(req: LambdaHttpRequest<TileJsonGet>): Promise<LambdaHttpResponse> {
15
+ const tileMatrix = Validate.getTileMatrixSet(req.params.tileMatrix);
16
+ if (tileMatrix == null) return NotFound();
12
17
 
13
- const tileMatrix = extractTileMatrixSet(rest[1]);
14
- if (tileMatrix == null) return NotFound;
18
+ const apiKey = Validate.apiKey(req);
15
19
 
16
20
  req.timer.start('tileset:load');
17
- const tileSet = await TileSets.get(rest[0], tileMatrix);
21
+ const tileSet = await Config.TileSet.get(Config.TileSet.id(req.params.tileSet));
18
22
  req.timer.end('tileset:load');
19
- if (tileSet == null) return NotFound;
23
+ if (tileSet == null) return NotFound();
24
+
25
+ const format = Validate.getRequestedFormats(req) ?? [tileSet.format];
20
26
 
21
- const apiKey = Router.apiKey(req);
22
27
  const host = Env.get(Env.PublicUrlBase) ?? '';
23
28
 
24
29
  const tileUrl =
25
- [host, version, name, tileSet.fullName, getTileMatrixId(tileMatrix), '{z}', '{x}', '{y}'].join('/') +
26
- `.${tileSet.format}?api=${apiKey}`;
30
+ [host, 'v1', 'tiles', tileSet.name, tileMatrix.identifier, '{z}', '{x}', '{y}'].join('/') +
31
+ `.${format[0]}?api=${apiKey}`;
27
32
 
28
33
  const tileJson: TileJson = { tiles: [tileUrl], tilejson: '3.0.0' };
29
- const maxZoom = TileMatrixSet.convertZoomLevel(tileSet.tileSet.maxZoom ?? 30, GoogleTms, tileMatrix, true);
30
- const minZoom = TileMatrixSet.convertZoomLevel(tileSet.tileSet.minZoom ?? 0, GoogleTms, tileMatrix, true);
34
+ const maxZoom = TileMatrixSet.convertZoomLevel(tileSet.maxZoom ?? 30, GoogleTms, tileMatrix, true);
35
+ const minZoom = TileMatrixSet.convertZoomLevel(tileSet.minZoom ?? 0, GoogleTms, tileMatrix, true);
31
36
 
32
- if (tileSet.tileSet.maxZoom) tileJson.maxzoom = maxZoom;
33
- if (tileSet.tileSet.minZoom) tileJson.minzoom = minZoom;
37
+ if (tileSet.maxZoom) tileJson.maxzoom = maxZoom;
38
+ if (tileSet.minZoom) tileJson.minzoom = minZoom;
34
39
 
35
40
  const json = JSON.stringify(tileJson);
36
41
  const data = Buffer.from(json);