@basemaps/lambda-tiler 6.31.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 (271) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/build/__tests__/config.data.d.ts +6 -1
  3. package/build/__tests__/config.data.d.ts.map +1 -1
  4. package/build/__tests__/config.data.js +33 -2
  5. package/build/__tests__/config.data.js.map +1 -1
  6. package/build/__tests__/index.test.js +3 -13
  7. package/build/__tests__/index.test.js.map +1 -1
  8. package/build/__tests__/xyz.util.d.ts +7 -9
  9. package/build/__tests__/xyz.util.d.ts.map +1 -1
  10. package/build/__tests__/xyz.util.js +14 -20
  11. package/build/__tests__/xyz.util.js.map +1 -1
  12. package/build/index.d.ts +0 -2
  13. package/build/index.d.ts.map +1 -1
  14. package/build/index.js +57 -26
  15. package/build/index.js.map +1 -1
  16. package/build/routes/__tests__/attribution.test.js +350 -400
  17. package/build/routes/__tests__/attribution.test.js.map +1 -1
  18. package/build/routes/__tests__/fonts.test.js +11 -5
  19. package/build/routes/__tests__/fonts.test.js.map +1 -1
  20. package/build/routes/__tests__/health.test.js +16 -13
  21. package/build/routes/__tests__/health.test.js.map +1 -1
  22. package/build/routes/__tests__/sprites.test.js +6 -0
  23. package/build/routes/__tests__/sprites.test.js.map +1 -1
  24. package/build/routes/__tests__/tile.json.test.d.ts +2 -0
  25. package/build/routes/__tests__/tile.json.test.d.ts.map +1 -0
  26. package/build/routes/__tests__/tile.json.test.js +124 -0
  27. package/build/routes/__tests__/tile.json.test.js.map +1 -0
  28. package/build/routes/__tests__/tile.style.json.test.d.ts +2 -0
  29. package/build/routes/__tests__/tile.style.json.test.d.ts.map +1 -0
  30. package/build/routes/__tests__/tile.style.json.test.js +95 -0
  31. package/build/routes/__tests__/tile.style.json.test.js.map +1 -0
  32. package/build/routes/__tests__/wmts.test.js +5 -44
  33. package/build/routes/__tests__/wmts.test.js.map +1 -1
  34. package/build/{__tests__ → routes/__tests__}/xyz.test.d.ts +0 -0
  35. package/build/routes/__tests__/xyz.test.d.ts.map +1 -0
  36. package/build/routes/__tests__/xyz.test.js +99 -0
  37. package/build/routes/__tests__/xyz.test.js.map +1 -0
  38. package/build/routes/attribution.d.ts +7 -5
  39. package/build/routes/attribution.d.ts.map +1 -1
  40. package/build/routes/attribution.js +43 -81
  41. package/build/routes/attribution.js.map +1 -1
  42. package/build/routes/fonts.d.ts +1 -1
  43. package/build/routes/fonts.d.ts.map +1 -1
  44. package/build/routes/fonts.js +30 -15
  45. package/build/routes/fonts.js.map +1 -1
  46. package/build/routes/health.d.ts +3 -3
  47. package/build/routes/health.d.ts.map +1 -1
  48. package/build/routes/health.js +15 -13
  49. package/build/routes/health.js.map +1 -1
  50. package/build/routes/imagery.d.ts.map +1 -1
  51. package/build/routes/imagery.js +11 -10
  52. package/build/routes/imagery.js.map +1 -1
  53. package/build/routes/ping.d.ts +3 -0
  54. package/build/routes/ping.d.ts.map +1 -0
  55. package/build/routes/ping.js +7 -0
  56. package/build/routes/ping.js.map +1 -0
  57. package/build/routes/sprites.d.ts.map +1 -1
  58. package/build/routes/sprites.js +16 -10
  59. package/build/routes/sprites.js.map +1 -1
  60. package/build/routes/tile.json.d.ts +7 -1
  61. package/build/routes/tile.json.d.ts.map +1 -1
  62. package/build/routes/tile.json.js +18 -21
  63. package/build/routes/tile.json.js.map +1 -1
  64. package/build/routes/tile.style.json.d.ts +6 -1
  65. package/build/routes/tile.style.json.d.ts.map +1 -1
  66. package/build/routes/tile.style.json.js +10 -13
  67. package/build/routes/tile.style.json.js.map +1 -1
  68. package/build/routes/tile.wmts.d.ts +9 -3
  69. package/build/routes/tile.wmts.d.ts.map +1 -1
  70. package/build/routes/tile.wmts.js +26 -35
  71. package/build/routes/tile.wmts.js.map +1 -1
  72. package/build/routes/tile.xyz.d.ts +14 -4
  73. package/build/routes/tile.xyz.d.ts.map +1 -1
  74. package/build/routes/tile.xyz.js +21 -17
  75. package/build/routes/tile.xyz.js.map +1 -1
  76. package/build/routes/tile.xyz.raster.d.ts +11 -0
  77. package/build/routes/tile.xyz.raster.d.ts.map +1 -0
  78. package/build/routes/tile.xyz.raster.js +90 -0
  79. package/build/routes/tile.xyz.raster.js.map +1 -0
  80. package/build/routes/tile.xyz.vector.d.ts +8 -0
  81. package/build/routes/tile.xyz.vector.d.ts.map +1 -0
  82. package/build/routes/tile.xyz.vector.js +46 -0
  83. package/build/routes/tile.xyz.vector.js.map +1 -0
  84. package/build/routes/version.d.ts +3 -0
  85. package/build/routes/version.d.ts.map +1 -0
  86. package/build/routes/version.js +9 -0
  87. package/build/routes/version.js.map +1 -0
  88. package/build/util/__test__/validate.test.d.ts +2 -0
  89. package/build/util/__test__/validate.test.d.ts.map +1 -0
  90. package/build/util/__test__/validate.test.js +66 -0
  91. package/build/util/__test__/validate.test.js.map +1 -0
  92. package/build/{cotar.cache.d.ts → util/cotar.serve.d.ts} +3 -8
  93. package/build/util/cotar.serve.d.ts.map +1 -0
  94. package/build/util/cotar.serve.js +41 -0
  95. package/build/util/cotar.serve.js.map +1 -0
  96. package/build/util/etag.d.ts +6 -0
  97. package/build/util/etag.d.ts.map +1 -0
  98. package/build/util/etag.js +20 -0
  99. package/build/util/etag.js.map +1 -0
  100. package/build/util/response.d.ts +4 -0
  101. package/build/util/response.d.ts.map +1 -0
  102. package/build/util/response.js +4 -0
  103. package/build/util/response.js.map +1 -0
  104. package/build/util/source.cache.d.ts +28 -0
  105. package/build/util/source.cache.d.ts.map +1 -0
  106. package/build/util/source.cache.js +53 -0
  107. package/build/util/source.cache.js.map +1 -0
  108. package/build/{source.tracer.d.ts → util/source.tracer.d.ts} +1 -0
  109. package/build/util/source.tracer.d.ts.map +1 -0
  110. package/build/{source.tracer.js → util/source.tracer.js} +3 -0
  111. package/build/util/source.tracer.js.map +1 -0
  112. package/build/util/swapping.lru.d.ts +21 -0
  113. package/build/util/swapping.lru.d.ts.map +1 -0
  114. package/build/util/swapping.lru.js +56 -0
  115. package/build/util/swapping.lru.js.map +1 -0
  116. package/build/util/validate.d.ts +46 -0
  117. package/build/util/validate.d.ts.map +1 -0
  118. package/build/util/validate.js +107 -0
  119. package/build/util/validate.js.map +1 -0
  120. package/build/wmts.capability.d.ts +1 -1
  121. package/build/wmts.capability.d.ts.map +1 -1
  122. package/dist/index.js +62 -60
  123. package/dist/node_modules/.package-lock.json +1 -1
  124. package/dist/package-lock.json +2 -2
  125. package/dist/package.json +1 -1
  126. package/package.json +6 -6
  127. package/src/__tests__/config.data.ts +41 -3
  128. package/src/__tests__/index.test.ts +3 -19
  129. package/src/__tests__/xyz.util.ts +18 -21
  130. package/src/index.ts +66 -29
  131. package/src/routes/__tests__/attribution.test.ts +356 -403
  132. package/src/routes/__tests__/fonts.test.ts +11 -5
  133. package/src/routes/__tests__/health.test.ts +17 -13
  134. package/src/routes/__tests__/sprites.test.ts +6 -1
  135. package/src/routes/__tests__/tile.json.test.ts +145 -0
  136. package/src/routes/__tests__/tile.style.json.test.ts +105 -0
  137. package/src/routes/__tests__/wmts.test.ts +5 -55
  138. package/src/routes/__tests__/xyz.test.ts +119 -0
  139. package/src/routes/attribution.ts +53 -99
  140. package/src/routes/fonts.ts +29 -15
  141. package/src/routes/health.ts +17 -16
  142. package/src/routes/imagery.ts +10 -9
  143. package/src/routes/ping.ts +8 -0
  144. package/src/routes/sprites.ts +16 -10
  145. package/src/routes/tile.json.ts +24 -18
  146. package/src/routes/tile.style.json.ts +15 -12
  147. package/src/routes/tile.wmts.ts +30 -31
  148. package/src/routes/tile.xyz.raster.ts +106 -0
  149. package/src/routes/tile.xyz.ts +31 -16
  150. package/src/routes/tile.xyz.vector.ts +47 -0
  151. package/src/routes/version.ts +8 -0
  152. package/src/util/__test__/validate.test.ts +74 -0
  153. package/src/util/cotar.serve.ts +46 -0
  154. package/src/util/etag.ts +20 -0
  155. package/src/util/response.ts +4 -0
  156. package/src/util/source.cache.ts +71 -0
  157. package/src/{source.tracer.ts → util/source.tracer.ts} +4 -0
  158. package/src/util/swapping.lru.ts +63 -0
  159. package/src/util/validate.ts +126 -0
  160. package/src/wmts.capability.ts +1 -1
  161. package/tsconfig.tsbuildinfo +1 -1
  162. package/build/__tests__/route.test.d.ts +0 -2
  163. package/build/__tests__/route.test.d.ts.map +0 -1
  164. package/build/__tests__/route.test.js +0 -21
  165. package/build/__tests__/route.test.js.map +0 -1
  166. package/build/__tests__/tiff.cache.test.d.ts +0 -2
  167. package/build/__tests__/tiff.cache.test.d.ts.map +0 -1
  168. package/build/__tests__/tiff.cache.test.js +0 -59
  169. package/build/__tests__/tiff.cache.test.js.map +0 -1
  170. package/build/__tests__/tile.cache.key.test.d.ts +0 -2
  171. package/build/__tests__/tile.cache.key.test.d.ts.map +0 -1
  172. package/build/__tests__/tile.cache.key.test.js +0 -49
  173. package/build/__tests__/tile.cache.key.test.js.map +0 -1
  174. package/build/__tests__/tile.set.cache.test.d.ts +0 -2
  175. package/build/__tests__/tile.set.cache.test.d.ts.map +0 -1
  176. package/build/__tests__/tile.set.cache.test.js +0 -72
  177. package/build/__tests__/tile.set.cache.test.js.map +0 -1
  178. package/build/__tests__/tile.set.test.d.ts +0 -2
  179. package/build/__tests__/tile.set.test.d.ts.map +0 -1
  180. package/build/__tests__/tile.set.test.js +0 -12
  181. package/build/__tests__/tile.set.test.js.map +0 -1
  182. package/build/__tests__/xyz.test.d.ts.map +0 -1
  183. package/build/__tests__/xyz.test.js +0 -275
  184. package/build/__tests__/xyz.test.js.map +0 -1
  185. package/build/api.key.d.ts +0 -2
  186. package/build/api.key.d.ts.map +0 -1
  187. package/build/api.key.js +0 -24
  188. package/build/api.key.js.map +0 -1
  189. package/build/cli/dump.d.ts +0 -2
  190. package/build/cli/dump.d.ts.map +0 -1
  191. package/build/cli/dump.js +0 -48
  192. package/build/cli/dump.js.map +0 -1
  193. package/build/cli/tile.set.local.d.ts +0 -12
  194. package/build/cli/tile.set.local.d.ts.map +0 -1
  195. package/build/cli/tile.set.local.js +0 -40
  196. package/build/cli/tile.set.local.js.map +0 -1
  197. package/build/cotar.cache.d.ts.map +0 -1
  198. package/build/cotar.cache.js +0 -50
  199. package/build/cotar.cache.js.map +0 -1
  200. package/build/router.d.ts +0 -15
  201. package/build/router.d.ts.map +0 -1
  202. package/build/router.js +0 -50
  203. package/build/router.js.map +0 -1
  204. package/build/routes/api.d.ts +0 -4
  205. package/build/routes/api.d.ts.map +0 -1
  206. package/build/routes/api.js +0 -14
  207. package/build/routes/api.js.map +0 -1
  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 -88
  211. package/build/routes/esri/rest.js.map +0 -1
  212. package/build/routes/response.d.ts +0 -4
  213. package/build/routes/response.d.ts.map +0 -1
  214. package/build/routes/response.js +0 -4
  215. package/build/routes/response.js.map +0 -1
  216. package/build/routes/tile.d.ts +0 -3
  217. package/build/routes/tile.d.ts.map +0 -1
  218. package/build/routes/tile.etag.d.ts +0 -11
  219. package/build/routes/tile.etag.d.ts.map +0 -1
  220. package/build/routes/tile.etag.js +0 -30
  221. package/build/routes/tile.etag.js.map +0 -1
  222. package/build/routes/tile.js +0 -28
  223. package/build/routes/tile.js.map +0 -1
  224. package/build/source.tracer.d.ts.map +0 -1
  225. package/build/source.tracer.js.map +0 -1
  226. package/build/tiff.cache.d.ts +0 -17
  227. package/build/tiff.cache.d.ts.map +0 -1
  228. package/build/tiff.cache.js +0 -46
  229. package/build/tiff.cache.js.map +0 -1
  230. package/build/tile.set.cache.d.ts +0 -20
  231. package/build/tile.set.cache.d.ts.map +0 -1
  232. package/build/tile.set.cache.js +0 -72
  233. package/build/tile.set.cache.js.map +0 -1
  234. package/build/tile.set.d.ts +0 -4
  235. package/build/tile.set.d.ts.map +0 -1
  236. package/build/tile.set.js +0 -2
  237. package/build/tile.set.js.map +0 -1
  238. package/build/tile.set.raster.d.ts +0 -49
  239. package/build/tile.set.raster.d.ts.map +0 -1
  240. package/build/tile.set.raster.js +0 -189
  241. package/build/tile.set.raster.js.map +0 -1
  242. package/build/tile.set.vector.d.ts +0 -18
  243. package/build/tile.set.vector.d.ts.map +0 -1
  244. package/build/tile.set.vector.js +0 -54
  245. package/build/tile.set.vector.js.map +0 -1
  246. package/build/validate.d.ts +0 -16
  247. package/build/validate.d.ts.map +0 -1
  248. package/build/validate.js +0 -32
  249. package/build/validate.js.map +0 -1
  250. package/src/__tests__/route.test.ts +0 -24
  251. package/src/__tests__/tiff.cache.test.ts +0 -73
  252. package/src/__tests__/tile.cache.key.test.ts +0 -56
  253. package/src/__tests__/tile.set.cache.test.ts +0 -80
  254. package/src/__tests__/tile.set.test.ts +0 -12
  255. package/src/__tests__/xyz.test.ts +0 -318
  256. package/src/api.key.ts +0 -23
  257. package/src/cli/dump.ts +0 -61
  258. package/src/cli/tile.set.local.ts +0 -51
  259. package/src/cotar.cache.ts +0 -54
  260. package/src/router.ts +0 -58
  261. package/src/routes/api.ts +0 -15
  262. package/src/routes/esri/rest.ts +0 -90
  263. package/src/routes/response.ts +0 -4
  264. package/src/routes/tile.etag.ts +0 -36
  265. package/src/routes/tile.ts +0 -23
  266. package/src/tiff.cache.ts +0 -51
  267. package/src/tile.set.cache.ts +0 -84
  268. package/src/tile.set.raster.ts +0 -230
  269. package/src/tile.set.ts +0 -4
  270. package/src/tile.set.vector.ts +0 -61
  271. 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,75 +62,30 @@ 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
- imagery: ConfigImagery,
91
- layer: ConfigLayer,
92
- host: ConfigProvider,
93
- extent: StacExtent,
94
- ): AttributionCollection {
95
- const tileMatrix = tileSet.tileMatrix;
96
- return {
97
- stac_version: Stac.Version,
98
- license: Stac.License,
99
- id: imagery.id,
100
- providers: [{ name: host.serviceProvider.name, url: host.serviceProvider.site, roles: ['host'] }],
101
- title: imagery.title ?? titleizeImageryName(imagery.name),
102
- description: 'No description',
103
- extent,
104
- links: [],
105
- summaries: {
106
- 'linz:category': imagery.category,
107
- 'linz:zoom': {
108
- min: TileMatrixSet.convertZoomLevel(layer.minZoom ? layer.minZoom : 0, GoogleTms, tileMatrix, true),
109
- max: TileMatrixSet.convertZoomLevel(layer.maxZoom ? layer.maxZoom : 32, GoogleTms, tileMatrix, true),
110
- },
111
- 'linz:priority': [1000 + tileSet.tileSet.layers.indexOf(layer)],
112
- },
113
- };
114
- }
115
-
116
65
  /**
117
66
  * Build a Single File STAC for the given TileSet.
118
67
  *
119
68
  * For now this is the minimal set required for attribution. This can be embellished later with
120
69
  * links and assets for a more comprehensive STAC file.
121
70
  */
122
- async function tileSetAttribution(tileSet: TileSetRaster): Promise<AttributionStac | null> {
123
- const proj = Projection.get(tileSet.tileMatrix);
124
- 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);
125
77
  const cols: AttributionCollection[] = [];
126
78
  const items: AttributionItem[] = [];
127
79
 
128
- // read all stac files in parallel
129
- for (const layer of tileSet.tileSet.layers) {
130
- const imgId = layer[proj.epsg.code];
131
- if (imgId == null) continue;
132
- const im = tileSet.imagery.get(imgId);
133
- if (im == null) continue;
134
- if (stacFiles.get(im.uri) == null) {
135
- stacFiles.set(im.uri, readStac(fsa.join(im.uri, 'collection.json')));
136
- }
137
- }
80
+ const imagery = await Config.getAllImagery(tileSet.layers, [tileMatrix.projection]);
138
81
 
139
82
  const host = await Config.Provider.get(Config.Provider.id('linz'));
140
83
  if (host == null) return null;
141
84
 
142
- for (const layer of tileSet.tileSet.layers) {
85
+ for (const layer of tileSet.layers) {
143
86
  const imgId = layer[proj.epsg.code];
144
87
  if (imgId == null) continue;
145
- const im = tileSet.imagery.get(imgId);
88
+ const im = imagery.get(imgId);
146
89
  if (im == null) continue;
147
90
 
148
91
  const bbox = proj.boundsToWgs84BoundingBox(im.bounds).map(roundNumber) as BBox;
@@ -161,10 +104,7 @@ async function tileSetAttribution(tileSet: TileSetRaster): Promise<AttributionSt
161
104
  assets: {},
162
105
  links: [],
163
106
  bbox,
164
- geometry: {
165
- type: 'MultiPolygon',
166
- coordinates: createCoordinates(bbox, im.files, proj),
167
- },
107
+ geometry: { type: 'MultiPolygon', coordinates: createCoordinates(bbox, im.files, proj) },
168
108
  properties: {
169
109
  title: im.title ?? titleizeImageryName(im.name),
170
110
  category: im.category,
@@ -174,56 +114,70 @@ async function tileSetAttribution(tileSet: TileSetRaster): Promise<AttributionSt
174
114
  },
175
115
  });
176
116
 
177
- cols.push(createAttributionCollection(tileSet, 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
+ });
178
134
  }
179
135
  return {
180
136
  id: tileSet.id,
181
137
  type: 'FeatureCollection',
182
138
  stac_version: Stac.Version,
183
139
  stac_extensions: ['single-file-stac'],
184
- title: tileSet.title,
185
- description: tileSet.description,
140
+ title: tileSet.title ?? 'No title',
141
+ description: tileSet.description ?? 'No Description',
186
142
  features: items,
187
143
  collections: cols,
188
144
  links: [],
189
145
  };
190
146
  }
191
147
 
148
+ export interface TileAttributionGet {
149
+ Params: {
150
+ tileSet: string;
151
+ tileMatrix: string;
152
+ };
153
+ }
154
+
192
155
  /**
193
156
  * Create a LambdaHttpResponse for a attribution request
194
157
  */
195
- export async function attribution(req: LambdaHttpRequest): Promise<LambdaHttpResponse> {
196
- const action = Router.action(req);
197
- const data = tileAttributionFromPath(action.rest);
198
- if (data == null) return NotFound;
199
- 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');
200
161
 
201
162
  req.timer.start('tileset:load');
202
- const tileSet = await TileSets.get(data.name, data.tileMatrix);
163
+ const tileSet = await Config.TileSet.get(Config.TileSet.id(req.params.tileSet));
203
164
  req.timer.end('tileset:load');
204
- if (tileSet == null || tileSet.type === TileSetType.Vector) return NotFound;
165
+ if (tileSet == null || tileSet.type === TileSetType.Vector) return NotFound();
205
166
 
206
- const cacheKey = createHash('sha256').update(JSON.stringify(tileSet.tileSet)).digest('base64');
207
-
208
- const ifNoneMatch = req.header(HttpHeader.IfNoneMatch);
209
- if (ifNoneMatch != null && ifNoneMatch.indexOf(cacheKey) > -1) {
210
- req.set('cache', { key: cacheKey, hit: true, match: ifNoneMatch });
211
- return new LambdaHttpResponse(304, 'Not modified');
212
- }
167
+ const cacheKey = Etag.key(tileSet);
168
+ if (Etag.isNotModified(req, cacheKey)) return NotModified();
213
169
 
214
170
  req.timer.start('stac:load');
215
- const attributions = await tileSetAttribution(tileSet);
171
+ const attributions = await tileSetAttribution(req, tileSet, tileMatrix);
216
172
  req.timer.end('stac:load');
217
173
 
218
- if (attributions == null) return NotFound;
174
+ if (attributions == null) return NotFound();
219
175
 
220
176
  const response = new LambdaHttpResponse(200, 'ok');
221
177
 
222
178
  response.header(HttpHeader.ETag, cacheKey);
223
- // Keep fresh for one day; otherwise use cache but refresh cache for next time
224
- response.header(HttpHeader.CacheControl, 'public, max-age=86400, stale-while-revalidate=604800');
225
-
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');
226
181
  response.json(attributions);
227
-
228
182
  return response;
229
183
  }
@@ -1,9 +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 { serveFromCotar } from '../cotar.cache.js';
6
- 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';
7
8
 
8
9
  interface FontGet {
9
10
  Params: { fontStack: string; range: string };
@@ -11,20 +12,27 @@ interface FontGet {
11
12
 
12
13
  export async function fontGet(req: LambdaHttpRequest<FontGet>): Promise<LambdaHttpResponse> {
13
14
  const assetLocation = Env.get(Env.AssetLocation);
14
- if (assetLocation == null) return NotFound;
15
+ if (assetLocation == null) return NotFound();
15
16
 
16
- const fontStack = decodeURIComponent(req.params.fontStack);
17
- const targetFile = path.join('fonts', fontStack, req.params.range) + '.pbf';
18
-
19
- if (assetLocation.endsWith('.tar.co')) return serveFromCotar(assetLocation, targetFile, 'application/x-protobuf');
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
+ }
20
21
 
21
22
  try {
22
23
  const filePath = fsa.join(assetLocation, targetFile);
23
24
  const buf = await fsa.read(filePath);
24
25
 
25
- 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;
26
34
  } catch (e: any) {
27
- if (e.code === 404) return NotFound;
35
+ if (e.code === 404) return NotFound();
28
36
  throw e;
29
37
  }
30
38
  }
@@ -44,19 +52,25 @@ export async function getFonts(fontPath: string): Promise<string[]> {
44
52
  return [...fonts].sort();
45
53
  }
46
54
 
47
- export async function fontList(): Promise<LambdaHttpResponse> {
55
+ export async function fontList(req: LambdaHttpRequest): Promise<LambdaHttpResponse> {
48
56
  const assetLocation = Env.get(Env.AssetLocation);
49
- if (assetLocation == null) return NotFound;
57
+ if (assetLocation == null) return NotFound();
50
58
 
51
- if (assetLocation.endsWith('.tar.co')) return serveFromCotar(assetLocation, 'fonts.json', 'application/json');
59
+ if (assetLocation.endsWith('.tar.co')) return serveFromCotar(req, assetLocation, 'fonts.json', 'application/json');
52
60
 
53
61
  try {
54
62
  const filePath = fsa.join(assetLocation, '/fonts');
55
63
  const fonts = await getFonts(filePath);
56
64
 
57
- 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;
58
72
  } catch (e: any) {
59
- if (e.code === 404) return NotFound;
73
+ if (e.code === 404) return NotFound();
60
74
  throw e;
61
75
  }
62
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,11 +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 { NotModified } from './response.js';
8
- 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';
9
9
 
10
10
  const gzipP = promisify(gzip);
11
11
 
@@ -32,27 +32,28 @@ interface ImageryGet {
32
32
  */
33
33
  export async function imageryGet(req: LambdaHttpRequest<ImageryGet>): Promise<LambdaHttpResponse> {
34
34
  const requestedFile = req.params.fileName;
35
- if (!isAllowedFile(requestedFile)) return new LambdaHttpResponse(404, 'Not found');
35
+ if (!isAllowedFile(requestedFile)) return NotFound();
36
36
 
37
37
  const imagery = await Config.Imagery.get(Config.Imagery.id(req.params.imageryId));
38
- if (imagery == null) return new LambdaHttpResponse(404, 'Not found');
38
+ if (imagery == null) return NotFound();
39
39
 
40
40
  const targetPath = fsa.join(imagery.uri, requestedFile);
41
41
 
42
42
  try {
43
43
  const buf = await fsa.read(targetPath);
44
- const cacheKey = createHash('sha256').update(buf).digest('base64');
45
44
 
46
- if (TileEtag.isNotModified(req, cacheKey)) return NotModified;
45
+ const cacheKey = Etag.key(buf);
46
+ if (Etag.isNotModified(req, cacheKey)) return NotModified();
47
47
 
48
48
  const response = new LambdaHttpResponse(200, 'ok');
49
49
  response.header(HttpHeader.ETag, cacheKey);
50
50
  response.header(HttpHeader.ContentEncoding, 'gzip');
51
- 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');
52
53
  req.set('bytes', buf.byteLength);
53
54
  return response;
54
55
  } catch (e) {
55
56
  req.log.warn({ targetPath }, 'ImageryMetadata:Failed');
56
- return new LambdaHttpResponse(404, 'Not found');
57
+ return NotFound();
57
58
  }
58
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,9 +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';
6
- import { isGzip, serveFromCotar } from '../cotar.cache.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';
7
8
 
8
9
  interface SpriteGet {
9
10
  Params: {
@@ -17,25 +18,30 @@ Extensions.set('.json', 'application/json');
17
18
 
18
19
  export async function spriteGet(req: LambdaHttpRequest<SpriteGet>): Promise<LambdaHttpResponse> {
19
20
  const assetLocation = Env.get(Env.AssetLocation);
20
- if (assetLocation == null) return NotFound;
21
+ if (assetLocation == null) return NotFound();
21
22
 
22
23
  const extension = path.extname(req.params.spriteName);
23
24
  const mimeType = Extensions.get(extension);
24
- if (mimeType == null) return NotFound;
25
+ if (mimeType == null) return NotFound();
25
26
 
26
27
  const targetFile = fsa.join('sprites', req.params.spriteName);
27
- if (assetLocation.endsWith('.tar.co')) return serveFromCotar(assetLocation, targetFile, mimeType);
28
+ if (assetLocation.endsWith('.tar.co')) return serveFromCotar(req, assetLocation, targetFile, mimeType);
28
29
 
29
30
  try {
30
31
  const filePath = fsa.join(assetLocation, targetFile);
31
32
  req.set('target', filePath);
32
33
 
33
34
  const buf = await fsa.read(filePath);
34
- const res = LambdaHttpResponse.ok().buffer(buf, mimeType);
35
- if (isGzip(buf)) res.header('content-encoding', 'gzip');
36
- 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;
37
43
  } catch (e: any) {
38
- if (e.code === 404) return NotFound;
44
+ if (e.code === 404) return NotFound();
39
45
  throw e;
40
46
  }
41
47
  }
@@ -1,35 +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 { 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
+ }
7
13
 
8
- export async function tileJson(req: LambdaHttpRequest): Promise<LambdaHttpResponse> {
9
- const { version, rest, name } = Router.action(req);
10
- 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();
11
17
 
12
- const tileMatrix = extractTileMatrixSet(rest[1]);
13
- if (tileMatrix == null) return NotFound;
18
+ const apiKey = Validate.apiKey(req);
14
19
 
15
20
  req.timer.start('tileset:load');
16
- const tileSet = await TileSets.get(rest[0], tileMatrix);
21
+ const tileSet = await Config.TileSet.get(Config.TileSet.id(req.params.tileSet));
17
22
  req.timer.end('tileset:load');
18
- if (tileSet == null) return NotFound;
23
+ if (tileSet == null) return NotFound();
24
+
25
+ const format = Validate.getRequestedFormats(req) ?? [tileSet.format];
19
26
 
20
- const apiKey = Router.apiKey(req);
21
27
  const host = Env.get(Env.PublicUrlBase) ?? '';
22
28
 
23
29
  const tileUrl =
24
- [host, version, name, tileSet.fullName, tileMatrix.identifier, '{z}', '{x}', '{y}'].join('/') +
25
- `.${tileSet.format}?api=${apiKey}`;
30
+ [host, 'v1', 'tiles', tileSet.name, tileMatrix.identifier, '{z}', '{x}', '{y}'].join('/') +
31
+ `.${format[0]}?api=${apiKey}`;
26
32
 
27
33
  const tileJson: TileJson = { tiles: [tileUrl], tilejson: '3.0.0' };
28
- const maxZoom = TileMatrixSet.convertZoomLevel(tileSet.tileSet.maxZoom ?? 30, GoogleTms, tileMatrix, true);
29
- 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);
30
36
 
31
- if (tileSet.tileSet.maxZoom) tileJson.maxzoom = maxZoom;
32
- if (tileSet.tileSet.minZoom) tileJson.minzoom = minZoom;
37
+ if (tileSet.maxZoom) tileJson.maxzoom = maxZoom;
38
+ if (tileSet.minZoom) tileJson.minzoom = minZoom;
33
39
 
34
40
  const json = JSON.stringify(tileJson);
35
41
  const data = Buffer.from(json);
@@ -2,11 +2,10 @@ import { Sources, StyleJson } from '@basemaps/config';
2
2
  import { Config, Env } from '@basemaps/shared';
3
3
  import { fsa } from '@chunkd/fs';
4
4
  import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
5
- import { createHash } from 'crypto';
6
5
  import { URL } from 'url';
7
- import { Router } from '../router.js';
8
- import { NotFound, NotModified } from './response.js';
9
- import { TileEtag } from './tile.etag.js';
6
+ import { NotFound, NotModified } from '../util/response.js';
7
+ import { Validate } from '../util/validate.js';
8
+ import { Etag } from '../util/etag.js';
10
9
 
11
10
  /**
12
11
  * Convert relative URLS into a full hostname url
@@ -54,23 +53,27 @@ export function convertStyleJson(style: StyleJson, apiKey: string): StyleJson {
54
53
  } as StyleJson;
55
54
  }
56
55
 
57
- export async function styleJson(req: LambdaHttpRequest, fileName: string): Promise<LambdaHttpResponse> {
58
- const apiKey = Router.apiKey(req);
59
- if (apiKey == null) return new LambdaHttpResponse(400, 'Invalid API Key.');
60
- const styleName = fileName.split('.json')[0];
56
+ export interface StyleGet {
57
+ Params: {
58
+ styleName: string;
59
+ };
60
+ }
61
+
62
+ export async function styleJsonGet(req: LambdaHttpRequest<StyleGet>): Promise<LambdaHttpResponse> {
63
+ const apiKey = Validate.apiKey(req);
64
+ const styleName = req.params.styleName;
61
65
 
62
66
  // Get style Config from db
63
67
  const dbId = Config.Style.id(styleName);
64
68
  const styleConfig = await Config.Style.get(dbId);
65
- if (styleConfig == null) return NotFound;
69
+ if (styleConfig == null) return NotFound();
66
70
 
67
71
  // Prepare sources and add linz source
68
72
  const style = convertStyleJson(styleConfig.style, apiKey);
69
73
  const data = Buffer.from(JSON.stringify(style));
70
74
 
71
- const cacheKey = createHash('sha256').update(data).digest('base64');
72
-
73
- if (TileEtag.isNotModified(req, cacheKey)) return NotModified;
75
+ const cacheKey = Etag.key(data);
76
+ if (Etag.isNotModified(req, cacheKey)) return NotModified();
74
77
 
75
78
  const response = new LambdaHttpResponse(200, 'ok');
76
79
  response.header(HttpHeader.ETag, cacheKey);