@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.
- package/CHANGELOG.md +63 -0
- package/build/__tests__/config.data.d.ts +11 -0
- package/build/__tests__/config.data.d.ts.map +1 -0
- package/build/__tests__/config.data.js +112 -0
- package/build/__tests__/config.data.js.map +1 -0
- package/build/__tests__/index.test.js +5 -14
- package/build/__tests__/index.test.js.map +1 -0
- package/build/__tests__/tile.style.json.test.js +1 -0
- package/build/__tests__/tile.style.json.test.js.map +1 -0
- package/build/__tests__/wmts.capability.test.d.ts +1 -1
- package/build/__tests__/wmts.capability.test.d.ts.map +1 -1
- package/build/__tests__/wmts.capability.test.js +286 -125
- package/build/__tests__/wmts.capability.test.js.map +1 -0
- package/build/__tests__/xyz.util.d.ts +7 -11
- package/build/__tests__/xyz.util.d.ts.map +1 -1
- package/build/__tests__/xyz.util.js +14 -42
- package/build/__tests__/xyz.util.js.map +1 -0
- package/build/index.d.ts +0 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +68 -41
- package/build/index.js.map +1 -0
- package/build/routes/__tests__/attribution.test.js +351 -399
- package/build/routes/__tests__/attribution.test.js.map +1 -0
- package/build/routes/__tests__/fonts.test.js +17 -3
- package/build/routes/__tests__/fonts.test.js.map +1 -0
- package/build/routes/__tests__/health.test.js +17 -13
- package/build/routes/__tests__/health.test.js.map +1 -0
- package/build/routes/__tests__/imagery.test.js +1 -0
- package/build/routes/__tests__/imagery.test.js.map +1 -0
- package/build/routes/__tests__/memory.fs.js +1 -0
- package/build/routes/__tests__/memory.fs.js.map +1 -0
- package/build/routes/__tests__/sprites.test.js +7 -0
- package/build/routes/__tests__/sprites.test.js.map +1 -0
- package/build/routes/__tests__/tile.json.test.d.ts +2 -0
- package/build/routes/__tests__/tile.json.test.d.ts.map +1 -0
- package/build/routes/__tests__/tile.json.test.js +124 -0
- package/build/routes/__tests__/tile.json.test.js.map +1 -0
- package/build/routes/__tests__/tile.style.json.test.d.ts +2 -0
- package/build/routes/__tests__/tile.style.json.test.d.ts.map +1 -0
- package/build/routes/__tests__/tile.style.json.test.js +95 -0
- package/build/routes/__tests__/tile.style.json.test.js.map +1 -0
- package/build/routes/__tests__/wmts.test.js +37 -27
- package/build/routes/__tests__/wmts.test.js.map +1 -0
- package/build/{__tests__ → routes/__tests__}/xyz.test.d.ts +0 -0
- package/build/routes/__tests__/xyz.test.d.ts.map +1 -0
- package/build/routes/__tests__/xyz.test.js +99 -0
- package/build/routes/__tests__/xyz.test.js.map +1 -0
- package/build/routes/attribution.d.ts +7 -5
- package/build/routes/attribution.d.ts.map +1 -1
- package/build/routes/attribution.js +50 -91
- package/build/routes/attribution.js.map +1 -0
- package/build/routes/fonts.d.ts +1 -1
- package/build/routes/fonts.d.ts.map +1 -1
- package/build/routes/fonts.js +33 -10
- package/build/routes/fonts.js.map +1 -0
- package/build/routes/health.d.ts +3 -3
- package/build/routes/health.d.ts.map +1 -1
- package/build/routes/health.js +16 -13
- package/build/routes/health.js.map +1 -0
- package/build/routes/imagery.d.ts +8 -1
- package/build/routes/imagery.d.ts.map +1 -1
- package/build/routes/imagery.js +17 -17
- package/build/routes/imagery.js.map +1 -0
- package/build/routes/ping.d.ts +3 -0
- package/build/routes/ping.d.ts.map +1 -0
- package/build/routes/ping.js +7 -0
- package/build/routes/ping.js.map +1 -0
- package/build/routes/sprites.d.ts.map +1 -1
- package/build/routes/sprites.js +22 -22
- package/build/routes/sprites.js.map +1 -0
- package/build/routes/tile.json.d.ts +7 -1
- package/build/routes/tile.json.d.ts.map +1 -1
- package/build/routes/tile.json.js +19 -22
- package/build/routes/tile.json.js.map +1 -0
- package/build/routes/tile.style.json.d.ts +6 -1
- package/build/routes/tile.style.json.d.ts.map +1 -1
- package/build/routes/tile.style.json.js +11 -13
- package/build/routes/tile.style.json.js.map +1 -0
- package/build/routes/tile.wmts.d.ts +9 -3
- package/build/routes/tile.wmts.d.ts.map +1 -1
- package/build/routes/tile.wmts.js +37 -50
- package/build/routes/tile.wmts.js.map +1 -0
- package/build/routes/tile.xyz.d.ts +14 -4
- package/build/routes/tile.xyz.d.ts.map +1 -1
- package/build/routes/tile.xyz.js +22 -17
- package/build/routes/tile.xyz.js.map +1 -0
- package/build/routes/tile.xyz.raster.d.ts +11 -0
- package/build/routes/tile.xyz.raster.d.ts.map +1 -0
- package/build/routes/tile.xyz.raster.js +90 -0
- package/build/routes/tile.xyz.raster.js.map +1 -0
- package/build/routes/tile.xyz.vector.d.ts +8 -0
- package/build/routes/tile.xyz.vector.d.ts.map +1 -0
- package/build/routes/tile.xyz.vector.js +46 -0
- package/build/routes/tile.xyz.vector.js.map +1 -0
- package/build/routes/version.d.ts +3 -0
- package/build/routes/version.d.ts.map +1 -0
- package/build/routes/version.js +9 -0
- package/build/routes/version.js.map +1 -0
- package/build/util/__test__/validate.test.d.ts +2 -0
- package/build/util/__test__/validate.test.d.ts.map +1 -0
- package/build/util/__test__/validate.test.js +66 -0
- package/build/util/__test__/validate.test.js.map +1 -0
- package/build/util/cotar.serve.d.ts +20 -0
- package/build/util/cotar.serve.d.ts.map +1 -0
- package/build/util/cotar.serve.js +41 -0
- package/build/util/cotar.serve.js.map +1 -0
- package/build/util/etag.d.ts +6 -0
- package/build/util/etag.d.ts.map +1 -0
- package/build/util/etag.js +20 -0
- package/build/util/etag.js.map +1 -0
- package/build/util/response.d.ts +4 -0
- package/build/util/response.d.ts.map +1 -0
- package/build/util/response.js +4 -0
- package/build/util/response.js.map +1 -0
- package/build/util/source.cache.d.ts +28 -0
- package/build/util/source.cache.d.ts.map +1 -0
- package/build/util/source.cache.js +53 -0
- package/build/util/source.cache.js.map +1 -0
- package/build/{source.tracer.d.ts → util/source.tracer.d.ts} +1 -0
- package/build/util/source.tracer.d.ts.map +1 -0
- package/build/{source.tracer.js → util/source.tracer.js} +4 -0
- package/build/util/source.tracer.js.map +1 -0
- package/build/util/swapping.lru.d.ts +21 -0
- package/build/util/swapping.lru.d.ts.map +1 -0
- package/build/util/swapping.lru.js +56 -0
- package/build/util/swapping.lru.js.map +1 -0
- package/build/util/validate.d.ts +46 -0
- package/build/util/validate.d.ts.map +1 -0
- package/build/util/validate.js +107 -0
- package/build/util/validate.js.map +1 -0
- package/build/wmts.capability.d.ts +27 -13
- package/build/wmts.capability.d.ts.map +1 -1
- package/build/wmts.capability.js +156 -55
- package/build/wmts.capability.js.map +1 -0
- package/dist/index.js +89 -73
- package/dist/node_modules/.package-lock.json +1 -1
- package/dist/package-lock.json +2 -2
- package/dist/package.json +1 -1
- package/package.json +10 -10
- package/src/__tests__/config.data.ts +120 -0
- package/src/__tests__/index.test.ts +4 -20
- package/src/__tests__/wmts.capability.test.ts +312 -139
- package/src/__tests__/xyz.util.ts +17 -45
- package/src/index.ts +75 -41
- package/src/routes/__tests__/attribution.test.ts +356 -403
- package/src/routes/__tests__/fonts.test.ts +18 -3
- package/src/routes/__tests__/health.test.ts +17 -13
- package/src/routes/__tests__/sprites.test.ts +6 -1
- package/src/routes/__tests__/tile.json.test.ts +145 -0
- package/src/routes/__tests__/tile.style.json.test.ts +105 -0
- package/src/routes/__tests__/wmts.test.ts +44 -34
- package/src/routes/__tests__/xyz.test.ts +119 -0
- package/src/routes/attribution.ts +59 -111
- package/src/routes/fonts.ts +32 -10
- package/src/routes/health.ts +17 -16
- package/src/routes/imagery.ts +18 -15
- package/src/routes/ping.ts +8 -0
- package/src/routes/sprites.ts +20 -22
- package/src/routes/tile.json.ts +24 -19
- package/src/routes/tile.style.json.ts +15 -12
- package/src/routes/tile.wmts.ts +41 -44
- package/src/routes/tile.xyz.raster.ts +106 -0
- package/src/routes/tile.xyz.ts +31 -16
- package/src/routes/tile.xyz.vector.ts +47 -0
- package/src/routes/version.ts +8 -0
- package/src/util/__test__/validate.test.ts +74 -0
- package/src/util/cotar.serve.ts +46 -0
- package/src/util/etag.ts +20 -0
- package/src/util/response.ts +4 -0
- package/src/util/source.cache.ts +71 -0
- package/src/{source.tracer.ts → util/source.tracer.ts} +4 -0
- package/src/util/swapping.lru.ts +63 -0
- package/src/util/validate.ts +126 -0
- package/src/wmts.capability.ts +170 -68
- package/tsconfig.tsbuildinfo +1 -1
- package/build/__tests__/route.test.d.ts +0 -2
- package/build/__tests__/route.test.d.ts.map +0 -1
- package/build/__tests__/route.test.js +0 -20
- package/build/__tests__/tiff.cache.test.d.ts +0 -2
- package/build/__tests__/tiff.cache.test.d.ts.map +0 -1
- package/build/__tests__/tiff.cache.test.js +0 -58
- package/build/__tests__/tile.cache.key.test.d.ts +0 -2
- package/build/__tests__/tile.cache.key.test.d.ts.map +0 -1
- package/build/__tests__/tile.cache.key.test.js +0 -48
- package/build/__tests__/tile.set.cache.test.d.ts +0 -2
- package/build/__tests__/tile.set.cache.test.d.ts.map +0 -1
- package/build/__tests__/tile.set.cache.test.js +0 -123
- package/build/__tests__/tile.set.test.d.ts +0 -2
- package/build/__tests__/tile.set.test.d.ts.map +0 -1
- package/build/__tests__/tile.set.test.js +0 -11
- package/build/__tests__/xyz.test.d.ts.map +0 -1
- package/build/__tests__/xyz.test.js +0 -306
- package/build/api.key.d.ts +0 -2
- package/build/api.key.d.ts.map +0 -1
- package/build/api.key.js +0 -23
- package/build/cli/dump.d.ts +0 -2
- package/build/cli/dump.d.ts.map +0 -1
- package/build/cli/dump.js +0 -47
- package/build/cli/tile.set.local.d.ts +0 -12
- package/build/cli/tile.set.local.d.ts.map +0 -1
- package/build/cli/tile.set.local.js +0 -39
- package/build/router.d.ts +0 -15
- package/build/router.d.ts.map +0 -1
- package/build/router.js +0 -49
- package/build/routes/api.d.ts +0 -5
- package/build/routes/api.d.ts.map +0 -1
- package/build/routes/api.js +0 -16
- package/build/routes/esri/rest.d.ts +0 -10
- package/build/routes/esri/rest.d.ts.map +0 -1
- package/build/routes/esri/rest.js +0 -87
- package/build/routes/response.d.ts +0 -4
- package/build/routes/response.d.ts.map +0 -1
- package/build/routes/response.js +0 -3
- package/build/routes/tile.d.ts +0 -3
- package/build/routes/tile.d.ts.map +0 -1
- package/build/routes/tile.etag.d.ts +0 -11
- package/build/routes/tile.etag.d.ts.map +0 -1
- package/build/routes/tile.etag.js +0 -29
- package/build/routes/tile.js +0 -27
- package/build/source.tracer.d.ts.map +0 -1
- package/build/tiff.cache.d.ts +0 -17
- package/build/tiff.cache.d.ts.map +0 -1
- package/build/tiff.cache.js +0 -45
- package/build/tile.set.cache.d.ts +0 -21
- package/build/tile.set.cache.d.ts.map +0 -1
- package/build/tile.set.cache.js +0 -100
- package/build/tile.set.d.ts +0 -4
- package/build/tile.set.d.ts.map +0 -1
- package/build/tile.set.js +0 -1
- package/build/tile.set.raster.d.ts +0 -49
- package/build/tile.set.raster.d.ts.map +0 -1
- package/build/tile.set.raster.js +0 -186
- package/build/tile.set.vector.d.ts +0 -25
- package/build/tile.set.vector.d.ts.map +0 -1
- package/build/tile.set.vector.js +0 -71
- package/build/validate.d.ts +0 -16
- package/build/validate.d.ts.map +0 -1
- package/build/validate.js +0 -31
- package/src/__tests__/route.test.ts +0 -24
- package/src/__tests__/tiff.cache.test.ts +0 -73
- package/src/__tests__/tile.cache.key.test.ts +0 -56
- package/src/__tests__/tile.set.cache.test.ts +0 -146
- package/src/__tests__/tile.set.test.ts +0 -12
- package/src/__tests__/xyz.test.ts +0 -362
- package/src/api.key.ts +0 -23
- package/src/cli/dump.ts +0 -61
- package/src/cli/tile.set.local.ts +0 -51
- package/src/router.ts +0 -58
- package/src/routes/api.ts +0 -19
- package/src/routes/esri/rest.ts +0 -90
- package/src/routes/response.ts +0 -4
- package/src/routes/tile.etag.ts +0 -36
- package/src/routes/tile.ts +0 -23
- package/src/tiff.cache.ts +0 -51
- package/src/tile.set.cache.ts +0 -111
- package/src/tile.set.raster.ts +0 -228
- package/src/tile.set.ts +0 -4
- package/src/tile.set.vector.ts +0 -79
- package/src/validate.ts +0 -32
|
@@ -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 {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
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
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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 =
|
|
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);
|
package/src/routes/tile.wmts.ts
CHANGED
|
@@ -1,79 +1,76 @@
|
|
|
1
1
|
import { Config, TileSetType } from '@basemaps/config';
|
|
2
|
-
import {
|
|
3
|
-
import { Env
|
|
4
|
-
import { getImageFormat } from '@basemaps/tiler';
|
|
2
|
+
import { GoogleTms, Nztm2000QuadTms, TileMatrixSet } from '@basemaps/geo';
|
|
3
|
+
import { Env } from '@basemaps/shared';
|
|
5
4
|
import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
|
|
6
5
|
import { createHash } from 'crypto';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { TileSetRaster } from '../tile.set.raster.js';
|
|
6
|
+
import { NotFound, NotModified } from '../util/response.js';
|
|
7
|
+
import { Validate } from '../util/validate.js';
|
|
10
8
|
import { WmtsCapabilities } from '../wmts.capability.js';
|
|
11
|
-
import {
|
|
12
|
-
import { TileEtag } from './tile.etag.js';
|
|
9
|
+
import { Etag } from '../util/etag.js';
|
|
13
10
|
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
for (const fmt of formats) {
|
|
20
|
-
const parsed = getImageFormat(fmt);
|
|
21
|
-
if (parsed == null) continue;
|
|
22
|
-
output.add(parsed);
|
|
23
|
-
}
|
|
24
|
-
if (output.size === 0) return undefined;
|
|
25
|
-
return [...output.values()];
|
|
11
|
+
export interface WmtsCapabilitiesGet {
|
|
12
|
+
Params: {
|
|
13
|
+
tileSet?: string;
|
|
14
|
+
tileMatrix?: string;
|
|
15
|
+
};
|
|
26
16
|
}
|
|
27
17
|
|
|
18
|
+
export function getWmtsTileMatrix(tileMatrixParam?: string): TileMatrixSet[] | null {
|
|
19
|
+
if (tileMatrixParam == null) return [GoogleTms, Nztm2000QuadTms];
|
|
20
|
+
const tileMatrix = Validate.getTileMatrixSet(tileMatrixParam);
|
|
21
|
+
if (tileMatrix == null) return null;
|
|
22
|
+
return [tileMatrix];
|
|
23
|
+
}
|
|
28
24
|
/**
|
|
29
25
|
* Serve a WMTS request
|
|
30
26
|
*
|
|
31
27
|
* /v1/tiles/:tileSet/:tileMatrixSet/WMTSCapabilities.xml
|
|
32
28
|
* @example `/v1/tiles/aerial/NZTM2000Quad/WMTSCapabilities.xml`
|
|
33
29
|
*/
|
|
34
|
-
export async function
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
export async function wmtsCapabilitiesGet(req: LambdaHttpRequest<WmtsCapabilitiesGet>): Promise<LambdaHttpResponse> {
|
|
31
|
+
const apiKey = Validate.apiKey(req);
|
|
32
|
+
|
|
33
|
+
const tileSetName = req.params.tileSet ?? 'aerial';
|
|
34
|
+
const tileMatrix = getWmtsTileMatrix(req.params.tileMatrix);
|
|
35
|
+
if (tileMatrix == null) return NotFound();
|
|
36
|
+
|
|
38
37
|
const host = Env.get(Env.PublicUrlBase) ?? '';
|
|
39
38
|
|
|
40
39
|
req.timer.start('tileset:load');
|
|
41
|
-
const
|
|
40
|
+
const tileSet = await Config.TileSet.get(Config.TileSet.id(tileSetName ?? 'aerial'));
|
|
42
41
|
req.timer.end('tileset:load');
|
|
43
|
-
if (
|
|
42
|
+
if (tileSet == null || tileSet.type !== TileSetType.Raster) return NotFound();
|
|
44
43
|
|
|
45
|
-
const
|
|
46
|
-
|
|
44
|
+
const provider = await Config.Provider.get(Config.Provider.id('linz'));
|
|
45
|
+
|
|
46
|
+
req.timer.start('imagery:load');
|
|
47
|
+
const imagery = await Config.getAllImagery(
|
|
48
|
+
tileSet.layers,
|
|
49
|
+
tileMatrix.map((tms) => tms.projection),
|
|
50
|
+
);
|
|
51
|
+
req.timer.end('imagery:load');
|
|
47
52
|
|
|
48
|
-
const apiKey = Router.apiKey(req);
|
|
49
53
|
const xml = new WmtsCapabilities({
|
|
50
54
|
httpBase: host,
|
|
51
55
|
provider: provider ?? undefined,
|
|
52
|
-
|
|
56
|
+
tileSet,
|
|
57
|
+
tileMatrix,
|
|
58
|
+
isIndividualLayers: req.params.tileMatrix == null,
|
|
59
|
+
imagery,
|
|
53
60
|
apiKey,
|
|
54
|
-
formats:
|
|
61
|
+
formats: Validate.getRequestedFormats(req),
|
|
55
62
|
}).toXml();
|
|
56
|
-
if (xml == null) return NotFound;
|
|
63
|
+
if (xml == null) return NotFound();
|
|
57
64
|
|
|
58
65
|
const data = Buffer.from(xml);
|
|
59
66
|
|
|
60
67
|
const cacheKey = createHash('sha256').update(data).digest('base64');
|
|
61
|
-
if (
|
|
68
|
+
if (Etag.isNotModified(req, cacheKey)) return NotModified();
|
|
62
69
|
|
|
63
70
|
const response = new LambdaHttpResponse(200, 'ok');
|
|
64
71
|
response.header(HttpHeader.ETag, cacheKey);
|
|
65
|
-
response.header(HttpHeader.CacheControl, '
|
|
72
|
+
response.header(HttpHeader.CacheControl, 'no-store');
|
|
66
73
|
response.buffer(data, 'text/xml');
|
|
67
74
|
req.set('bytes', data.byteLength);
|
|
68
75
|
return response;
|
|
69
76
|
}
|
|
70
|
-
|
|
71
|
-
async function wmtsLoadTileSets(name: string, tileMatrix: TileMatrixSet | null): Promise<TileSetRaster[]> {
|
|
72
|
-
if (tileMatrix != null) {
|
|
73
|
-
const ts = await TileSets.get(name, tileMatrix);
|
|
74
|
-
if (ts == null || ts.type === TileSetType.Vector) return [];
|
|
75
|
-
return [ts];
|
|
76
|
-
}
|
|
77
|
-
if (name === '') name = TileSetName.aerial;
|
|
78
|
-
return (await TileSets.getAll(name, tileMatrix)).filter((f) => f.type === 'raster') as TileSetRaster[];
|
|
79
|
-
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Config, ConfigTileSetRaster } from '@basemaps/config';
|
|
2
|
+
import { Bounds, Epsg, TileMatrixSet, TileMatrixSets, VectorFormat } from '@basemaps/geo';
|
|
3
|
+
import { Env, fsa } from '@basemaps/shared';
|
|
4
|
+
import { Tiler } from '@basemaps/tiler';
|
|
5
|
+
import { TileMakerSharp } from '@basemaps/tiler-sharp';
|
|
6
|
+
import { CogTiff } from '@cogeotiff/core';
|
|
7
|
+
import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
|
|
8
|
+
import pLimit from 'p-limit';
|
|
9
|
+
import { Etag } from '../util/etag.js';
|
|
10
|
+
import { NotFound, NotModified } from '../util/response.js';
|
|
11
|
+
import { CoSources } from '../util/source.cache.js';
|
|
12
|
+
import { TileXyz } from '../util/validate.js';
|
|
13
|
+
|
|
14
|
+
const LoadingQueue = pLimit(Env.getNumber(Env.TiffConcurrency, 25));
|
|
15
|
+
|
|
16
|
+
export function getTiffName(name: string): string {
|
|
17
|
+
const lowerName = name.toLowerCase();
|
|
18
|
+
if (lowerName.endsWith('.tif') || lowerName.endsWith('.tiff')) return name;
|
|
19
|
+
return `${name}.tiff`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const TileComposer = new TileMakerSharp(256);
|
|
23
|
+
|
|
24
|
+
const DefaultResizeKernel = { in: 'lanczos3', out: 'lanczos3' } as const;
|
|
25
|
+
const DefaultBackground = { r: 0, g: 0, b: 0, alpha: 0 };
|
|
26
|
+
|
|
27
|
+
export const TileXyzRaster = {
|
|
28
|
+
async getTiffsForTile(req: LambdaHttpRequest, tileSet: ConfigTileSetRaster, xyz: TileXyz): Promise<string[]> {
|
|
29
|
+
const imagery = await Config.getAllImagery(tileSet.layers, [xyz.tileMatrix.projection]);
|
|
30
|
+
|
|
31
|
+
const output: string[] = [];
|
|
32
|
+
const tileBounds = xyz.tileMatrix.tileToSourceBounds(xyz.tile);
|
|
33
|
+
|
|
34
|
+
// All zoom level config is stored as Google zoom levels
|
|
35
|
+
const filterZoom = TileMatrixSet.convertZoomLevel(xyz.tile.z, xyz.tileMatrix, TileMatrixSets.get(Epsg.Google));
|
|
36
|
+
for (const layer of tileSet.layers) {
|
|
37
|
+
if (layer.maxZoom != null && filterZoom > layer.maxZoom) continue;
|
|
38
|
+
if (layer.minZoom != null && filterZoom < layer.minZoom) continue;
|
|
39
|
+
|
|
40
|
+
const imgId = layer[xyz.tileMatrix.projection.code];
|
|
41
|
+
if (imgId == null) {
|
|
42
|
+
req.log.warn({ layer: layer.name, projection: xyz.tileMatrix.projection.code }, 'Failed to lookup imagery');
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const img = imagery.get(imgId);
|
|
47
|
+
if (img == null) {
|
|
48
|
+
req.log.warn(
|
|
49
|
+
{ layer: layer.name, projection: xyz.tileMatrix.projection.code, imgId },
|
|
50
|
+
'Failed to lookup imagery',
|
|
51
|
+
);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (!tileBounds.intersects(Bounds.fromJson(img.bounds))) continue;
|
|
55
|
+
|
|
56
|
+
for (const c of img.files) {
|
|
57
|
+
if (!tileBounds.intersects(Bounds.fromJson(c))) continue;
|
|
58
|
+
const tiffPath = fsa.join(img.uri, getTiffName(c.name));
|
|
59
|
+
output.push(tiffPath);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return output;
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
async tile(req: LambdaHttpRequest, tileSet: ConfigTileSetRaster, xyz: TileXyz): Promise<LambdaHttpResponse> {
|
|
66
|
+
if (xyz.tileType === VectorFormat.MapboxVectorTiles) return NotFound();
|
|
67
|
+
|
|
68
|
+
const tiffPaths = await this.getTiffsForTile(req, tileSet, xyz);
|
|
69
|
+
const cacheKey = Etag.key(tiffPaths);
|
|
70
|
+
if (Etag.isNotModified(req, cacheKey)) return NotModified();
|
|
71
|
+
|
|
72
|
+
const toLoad: Promise<CogTiff | null>[] = [];
|
|
73
|
+
for (const tiffPath of tiffPaths) {
|
|
74
|
+
toLoad.push(
|
|
75
|
+
LoadingQueue(() => {
|
|
76
|
+
return CoSources.getCog(tiffPath).catch((error) => {
|
|
77
|
+
req.log.warn({ error, tiff: tiffPath }, 'TiffLoadFailed');
|
|
78
|
+
return null;
|
|
79
|
+
});
|
|
80
|
+
}),
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const tiffs = (await Promise.all(toLoad)).filter((f) => f != null) as CogTiff[];
|
|
85
|
+
|
|
86
|
+
const tiler = new Tiler(xyz.tileMatrix);
|
|
87
|
+
const layers = await tiler.tile(tiffs, xyz.tile.x, xyz.tile.y, xyz.tile.z);
|
|
88
|
+
|
|
89
|
+
const res = await TileComposer.compose({
|
|
90
|
+
layers,
|
|
91
|
+
format: xyz.tileType,
|
|
92
|
+
background: tileSet.background ?? DefaultBackground,
|
|
93
|
+
resizeKernel: tileSet.resizeKernel ?? DefaultResizeKernel,
|
|
94
|
+
metrics: req.timer,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
req.set('layersUsed', res.layers);
|
|
98
|
+
req.set('bytes', res.buffer.byteLength);
|
|
99
|
+
|
|
100
|
+
const response = new LambdaHttpResponse(200, 'ok');
|
|
101
|
+
response.header(HttpHeader.ETag, cacheKey);
|
|
102
|
+
response.header(HttpHeader.CacheControl, 'public, max-age=604800, stale-while-revalidate=86400');
|
|
103
|
+
response.buffer(res.buffer, 'image/' + xyz.tileType);
|
|
104
|
+
return response;
|
|
105
|
+
},
|
|
106
|
+
};
|
package/src/routes/tile.xyz.ts
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Config, TileSetType } from '@basemaps/config';
|
|
2
2
|
import { LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
3
|
+
import { NotFound } from '../util/response.js';
|
|
4
|
+
import { Validate } from '../util/validate.js';
|
|
5
|
+
import { TileXyzRaster } from './tile.xyz.raster.js';
|
|
6
|
+
import { tileXyzVector } from './tile.xyz.vector.js';
|
|
7
|
+
|
|
8
|
+
export interface TileXyzGet {
|
|
9
|
+
Params: {
|
|
10
|
+
tileSet: string;
|
|
11
|
+
tileMatrix: string;
|
|
12
|
+
z: string;
|
|
13
|
+
x: string;
|
|
14
|
+
y: string;
|
|
15
|
+
tileType: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
7
18
|
|
|
8
19
|
/**
|
|
9
20
|
* Serve a tile
|
|
@@ -11,20 +22,24 @@ import { NotFound } from './response.js';
|
|
|
11
22
|
* /v1/tiles/:tileSet/:tileMatrixSet/:z/:x/:y.:tileType
|
|
12
23
|
*
|
|
13
24
|
* @example
|
|
14
|
-
* Vector Tile `/v1/tiles/topographic/
|
|
15
|
-
* Raster Tile `/v1/tiles/aerial/
|
|
16
|
-
*
|
|
25
|
+
* Vector Tile `/v1/tiles/topographic/WebMercatorQuad/2/1/1.pbf`
|
|
26
|
+
* Raster Tile `/v1/tiles/aerial/WebMercatorQuad/6/0/38.webp`
|
|
27
|
+
*
|
|
17
28
|
*/
|
|
18
|
-
export async function
|
|
19
|
-
const
|
|
20
|
-
const xyzData = tileXyzFromPath(action.rest);
|
|
21
|
-
if (xyzData == null) return NotFound;
|
|
22
|
-
ValidateTilePath.validate(req, xyzData);
|
|
29
|
+
export async function tileXyzGet(req: LambdaHttpRequest<TileXyzGet>): Promise<LambdaHttpResponse> {
|
|
30
|
+
const xyzData = Validate.xyz(req);
|
|
23
31
|
|
|
24
32
|
req.timer.start('tileset:load');
|
|
25
|
-
const tileSet = await
|
|
33
|
+
const tileSet = await Config.TileSet.get(Config.TileSet.id(xyzData.tileSet));
|
|
26
34
|
req.timer.end('tileset:load');
|
|
27
|
-
if (tileSet == null) return NotFound;
|
|
35
|
+
if (tileSet == null) return NotFound();
|
|
28
36
|
|
|
29
|
-
|
|
37
|
+
switch (tileSet.type) {
|
|
38
|
+
case TileSetType.Vector:
|
|
39
|
+
return tileXyzVector.tile(req, tileSet, xyzData);
|
|
40
|
+
case TileSetType.Raster:
|
|
41
|
+
return TileXyzRaster.tile(req, tileSet, xyzData);
|
|
42
|
+
default:
|
|
43
|
+
return new LambdaHttpResponse(400, 'Invalid tileset');
|
|
44
|
+
}
|
|
30
45
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ConfigTileSetVector } from '@basemaps/config';
|
|
2
|
+
import { GoogleTms, VectorFormat } from '@basemaps/geo';
|
|
3
|
+
import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
|
|
4
|
+
import { isGzip } from '../util/cotar.serve.js';
|
|
5
|
+
import { Etag } from '../util/etag.js';
|
|
6
|
+
import { NotFound, NotModified } from '../util/response.js';
|
|
7
|
+
import { CoSources } from '../util/source.cache.js';
|
|
8
|
+
import { TileXyz } from '../util/validate.js';
|
|
9
|
+
|
|
10
|
+
export const tileXyzVector = {
|
|
11
|
+
/** Serve a MVT vector tile */
|
|
12
|
+
async tile(req: LambdaHttpRequest, tileSet: ConfigTileSetVector, xyz: TileXyz): Promise<LambdaHttpResponse> {
|
|
13
|
+
if (xyz.tileType !== VectorFormat.MapboxVectorTiles) return NotFound();
|
|
14
|
+
if (xyz.tileMatrix.identifier !== GoogleTms.identifier) return NotFound();
|
|
15
|
+
|
|
16
|
+
if (tileSet.layers.length > 1) return new LambdaHttpResponse(500, 'Too many layers in tileset');
|
|
17
|
+
const [layer] = tileSet.layers;
|
|
18
|
+
const layerId = layer[3857];
|
|
19
|
+
if (layerId == null) return new LambdaHttpResponse(500, 'Layer url not found from tileset Config');
|
|
20
|
+
|
|
21
|
+
// Flip Y coordinate because MBTiles files are TMS.
|
|
22
|
+
const y = (1 << xyz.tile.z) - 1 - xyz.tile.y;
|
|
23
|
+
|
|
24
|
+
const tilePath = `tiles/${xyz.tile.z}/${xyz.tile.x}/${y}.pbf.gz`;
|
|
25
|
+
const tileId = `${layerId}#${tilePath}`;
|
|
26
|
+
|
|
27
|
+
const cacheKey = Etag.key(tileId);
|
|
28
|
+
if (Etag.isNotModified(req, cacheKey)) return NotModified();
|
|
29
|
+
|
|
30
|
+
req.timer.start('cotar:load');
|
|
31
|
+
const cotar = await CoSources.getCotar(layerId);
|
|
32
|
+
if (cotar == null) return new LambdaHttpResponse(500, 'Failed to load VectorTiles');
|
|
33
|
+
req.timer.end('cotar:load');
|
|
34
|
+
|
|
35
|
+
req.timer.start('cotar:tile');
|
|
36
|
+
const tile = await cotar.get(tilePath);
|
|
37
|
+
if (tile == null) return NotFound();
|
|
38
|
+
req.timer.end('cotar:tile');
|
|
39
|
+
|
|
40
|
+
const tileBuffer = Buffer.from(tile);
|
|
41
|
+
const response = LambdaHttpResponse.ok().buffer(tileBuffer, 'application/x-protobuf');
|
|
42
|
+
response.header(HttpHeader.ETag, cacheKey);
|
|
43
|
+
response.header(HttpHeader.CacheControl, 'public, max-age=604800, stale-while-revalidate=86400');
|
|
44
|
+
if (isGzip(tileBuffer)) response.header(HttpHeader.ContentEncoding, 'gzip');
|
|
45
|
+
return response;
|
|
46
|
+
},
|
|
47
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { HttpHeader, LambdaHttpResponse } from '@linzjs/lambda';
|
|
2
|
+
|
|
3
|
+
export async function versionGet(): Promise<LambdaHttpResponse> {
|
|
4
|
+
const response = new LambdaHttpResponse(200, 'ok');
|
|
5
|
+
response.header(HttpHeader.CacheControl, 'no-store');
|
|
6
|
+
response.json({ version: process.env.GIT_VERSION ?? 'dev', hash: process.env.GIT_HASH });
|
|
7
|
+
return response;
|
|
8
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { GoogleTms, ImageFormat, Nztm2000QuadTms, Nztm2000Tms, VectorFormat } from '@basemaps/geo';
|
|
2
|
+
import o from 'ospec';
|
|
3
|
+
import { mockUrlRequest } from '../../__tests__/xyz.util.js';
|
|
4
|
+
import { Validate } from '../validate.js';
|
|
5
|
+
|
|
6
|
+
o.spec('GetImageFormats', () => {
|
|
7
|
+
o('should parse all formats', () => {
|
|
8
|
+
const req = mockUrlRequest('/v1/blank', 'format=png&format=jpeg');
|
|
9
|
+
const formats = Validate.getRequestedFormats(req);
|
|
10
|
+
o(formats).deepEquals([ImageFormat.Png, ImageFormat.Jpeg]);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
o('should ignore bad formats', () => {
|
|
14
|
+
const req = mockUrlRequest('/v1/blank', 'format=fake&format=mvt');
|
|
15
|
+
const formats = Validate.getRequestedFormats(req);
|
|
16
|
+
o(formats).equals(null);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
o('should de-dupe formats', () => {
|
|
20
|
+
const req = mockUrlRequest('/v1/blank', 'format=png&format=jpeg&format=png&format=jpeg&format=png&format=jpeg');
|
|
21
|
+
const formats = Validate.getRequestedFormats(req);
|
|
22
|
+
o(formats).deepEquals([ImageFormat.Png, ImageFormat.Jpeg]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
o('should support "tileFormat" Alias all formats', () => {
|
|
26
|
+
const req = mockUrlRequest('/v1/blank', 'tileFormat=png&format=jpeg');
|
|
27
|
+
const formats = Validate.getRequestedFormats(req);
|
|
28
|
+
o(formats).deepEquals([ImageFormat.Jpeg, ImageFormat.Png]);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
o('should not duplicate "tileFormat" alias all formats', () => {
|
|
32
|
+
const req = mockUrlRequest('/v1/blank', 'tileFormat=jpeg&format=jpeg');
|
|
33
|
+
const formats = Validate.getRequestedFormats(req);
|
|
34
|
+
o(formats).deepEquals([ImageFormat.Jpeg]);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
o.spec('getTileMatrixSet', () => {
|
|
39
|
+
o('should lookup epsg codes', () => {
|
|
40
|
+
o(Validate.getTileMatrixSet('EPSG:3857')?.identifier).equals(GoogleTms.identifier);
|
|
41
|
+
o(Validate.getTileMatrixSet('EPSG:2193')?.identifier).equals(Nztm2000Tms.identifier);
|
|
42
|
+
|
|
43
|
+
o(Validate.getTileMatrixSet('3857')?.identifier).equals(GoogleTms.identifier);
|
|
44
|
+
o(Validate.getTileMatrixSet('2193')?.identifier).equals(Nztm2000Tms.identifier);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
o('should lookup by identifier', () => {
|
|
48
|
+
o(Validate.getTileMatrixSet('WebMercatorQuad')?.identifier).equals(GoogleTms.identifier);
|
|
49
|
+
o(Validate.getTileMatrixSet('NZTM2000Quad')?.identifier).equals(Nztm2000QuadTms.identifier);
|
|
50
|
+
o(Validate.getTileMatrixSet('Nztm2000')?.identifier).equals(Nztm2000Tms.identifier);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
o('should be case sensitive', () => {
|
|
54
|
+
o(Validate.getTileMatrixSet('Nztm2000Quad')?.identifier).equals(undefined);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
o.spec('getTileFormat', () => {
|
|
59
|
+
for (const ext of Object.values(ImageFormat)) {
|
|
60
|
+
o('should support image format:' + ext, () => {
|
|
61
|
+
o(Validate.getTileFormat(ext)).equals(ext);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
o('should support vector format: mvt', () => {
|
|
66
|
+
o(Validate.getTileFormat('pbf')).equals(VectorFormat.MapboxVectorTiles);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
for (const fmt of ['FAKE', /* 'JPEG' // TODO should this be case sensitive ,*/ 'mvt', 'json']) {
|
|
70
|
+
o('should not support format:' + fmt, () => {
|
|
71
|
+
o(Validate.getTileFormat(fmt)).equals(null);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
|
|
2
|
+
import { Etag } from './etag.js';
|
|
3
|
+
import { NotFound, NotModified } from './response.js';
|
|
4
|
+
import { CoSources } from './source.cache.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Load a cotar and look for a file inside the cotar returning the file back as a LambdaResponse
|
|
8
|
+
*
|
|
9
|
+
* This will also set two headers
|
|
10
|
+
* - Content-Encoding if the file starts with gzip magic
|
|
11
|
+
* - Content-Type from the parameter contentType
|
|
12
|
+
*/
|
|
13
|
+
export async function serveFromCotar(
|
|
14
|
+
req: LambdaHttpRequest,
|
|
15
|
+
cotarPath: string,
|
|
16
|
+
assetPath: string,
|
|
17
|
+
contentType: string,
|
|
18
|
+
): Promise<LambdaHttpResponse> {
|
|
19
|
+
const cotar = await CoSources.getCotar(cotarPath);
|
|
20
|
+
if (cotar == null) return NotFound();
|
|
21
|
+
const fileData = await cotar.get(assetPath);
|
|
22
|
+
if (fileData == null) return NotFound();
|
|
23
|
+
|
|
24
|
+
const buf = Buffer.from(fileData);
|
|
25
|
+
|
|
26
|
+
const cacheKey = Etag.key(buf);
|
|
27
|
+
if (Etag.isNotModified(req, cacheKey)) return NotModified();
|
|
28
|
+
|
|
29
|
+
const response = LambdaHttpResponse.ok().buffer(buf, contentType);
|
|
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;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Does a buffer look like a gzipped document instead of raw json
|
|
38
|
+
*
|
|
39
|
+
* Determined by checking the first two bytes are the gzip magic bytes `0x1f 0x8b`
|
|
40
|
+
*
|
|
41
|
+
* @see https://en.wikipedia.org/wiki/Gzip
|
|
42
|
+
*
|
|
43
|
+
*/
|
|
44
|
+
export function isGzip(b: Buffer): boolean {
|
|
45
|
+
return b[0] === 0x1f && b[1] === 0x8b;
|
|
46
|
+
}
|
package/src/util/etag.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { sha256base58 } from '@basemaps/config';
|
|
2
|
+
import { HttpHeader, LambdaHttpRequest } from '@linzjs/lambda';
|
|
3
|
+
|
|
4
|
+
export const Etag = {
|
|
5
|
+
key(obj: unknown): string {
|
|
6
|
+
if (Buffer.isBuffer(obj) || typeof obj === 'string') return sha256base58(obj);
|
|
7
|
+
return sha256base58(JSON.stringify(obj));
|
|
8
|
+
},
|
|
9
|
+
|
|
10
|
+
isNotModified(req: LambdaHttpRequest, cacheKey: string): boolean {
|
|
11
|
+
// If the user has supplied a IfNoneMatch Header and it contains the full sha256 sum for our
|
|
12
|
+
// etag this tile has not been modified.
|
|
13
|
+
const ifNoneMatch = req.header(HttpHeader.IfNoneMatch);
|
|
14
|
+
if (ifNoneMatch != null && ifNoneMatch.indexOf(cacheKey) > -1) {
|
|
15
|
+
req.set('cache', { hit: true, match: ifNoneMatch });
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
},
|
|
20
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { fsa } from '@basemaps/shared';
|
|
2
|
+
import { ChunkSourceBase } from '@chunkd/core';
|
|
3
|
+
import { CogTiff } from '@cogeotiff/core';
|
|
4
|
+
import { Cotar } from '@cotar/core';
|
|
5
|
+
import { St } from './source.tracer.js';
|
|
6
|
+
import { SwappingLru } from './swapping.lru.js';
|
|
7
|
+
|
|
8
|
+
export type LruStrut = LruStrutCotar | LruStrutCog;
|
|
9
|
+
|
|
10
|
+
export interface LruStrutCotar {
|
|
11
|
+
type: 'cotar';
|
|
12
|
+
value: Promise<Cotar>;
|
|
13
|
+
_value?: Cotar;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface LruStrutCog {
|
|
17
|
+
type: 'cog';
|
|
18
|
+
value: Promise<CogTiff>;
|
|
19
|
+
_value?: CogTiff;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
class LruStrutObj<T extends LruStrut> {
|
|
23
|
+
ob: T;
|
|
24
|
+
constructor(ob: T) {
|
|
25
|
+
this.ob = ob;
|
|
26
|
+
if (this.ob._value == null) this.ob.value.then((c) => (this.ob._value = c));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get size(): number {
|
|
30
|
+
const val = this.ob._value;
|
|
31
|
+
if (val == null) return 0;
|
|
32
|
+
return val.source.chunkSize * (val.source as ChunkSourceBase).chunks.size;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class SourceCache {
|
|
37
|
+
cache: SwappingLru<LruStrutObj<LruStrutCotar | LruStrutCog>>;
|
|
38
|
+
constructor(maxSize: number) {
|
|
39
|
+
this.cache = new SwappingLru<LruStrutObj<LruStrut>>(maxSize);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getCog(location: string): Promise<CogTiff> {
|
|
43
|
+
const existing = this.cache.get(location)?.ob;
|
|
44
|
+
|
|
45
|
+
if (existing != null) {
|
|
46
|
+
if (existing.type === 'cog') return existing.value;
|
|
47
|
+
throw new Error(`Existing object of type: ${existing.type} made for location: ${location}`);
|
|
48
|
+
}
|
|
49
|
+
const source = fsa.source(location);
|
|
50
|
+
St.trace(source);
|
|
51
|
+
const value = CogTiff.create(source);
|
|
52
|
+
this.cache.set(location, new LruStrutObj({ type: 'cog', value }));
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
getCotar(location: string): Promise<Cotar> {
|
|
57
|
+
const existing = this.cache.get(location)?.ob;
|
|
58
|
+
|
|
59
|
+
if (existing != null) {
|
|
60
|
+
if (existing.type === 'cotar') return existing.value as Promise<Cotar>;
|
|
61
|
+
throw new Error(`Existing object of type: ${existing.type} made for location: ${location}`);
|
|
62
|
+
}
|
|
63
|
+
const source = fsa.source(location);
|
|
64
|
+
St.trace(source);
|
|
65
|
+
const value = Cotar.fromTar(source);
|
|
66
|
+
this.cache.set(location, new LruStrutObj({ type: 'cotar', value }));
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const CoSources = new SourceCache(256 * 1024 * 1024);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { sha256base58 } from '@basemaps/config';
|
|
1
2
|
import { ChunkSource } from '@chunkd/core';
|
|
2
3
|
|
|
3
4
|
interface SourceRequest {
|
|
5
|
+
id?: string;
|
|
4
6
|
offset: number;
|
|
5
7
|
length?: number;
|
|
6
8
|
source: string;
|
|
@@ -20,6 +22,8 @@ export class SourceTracer {
|
|
|
20
22
|
const originFetch = source.fetchBytes;
|
|
21
23
|
source.fetchBytes = async (offset: number, length?: number): Promise<ArrayBuffer> => {
|
|
22
24
|
const request: SourceRequest = { source: source.uri, offset, length };
|
|
25
|
+
const traceId = sha256base58(`${request.source}:${request.offset}:${request.length}`);
|
|
26
|
+
request.id = traceId;
|
|
23
27
|
this.requests.push(request);
|
|
24
28
|
const startTime = Date.now();
|
|
25
29
|
|