@basemaps/lambda-tiler 6.43.0 → 6.44.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 +14 -0
- package/build/cli/render.preview.d.ts +2 -0
- package/build/cli/render.preview.d.ts.map +1 -0
- package/build/cli/render.preview.js +38 -0
- package/build/cli/render.preview.js.map +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +6 -0
- package/build/index.js.map +1 -1
- package/build/routes/__tests__/preview.index.test.d.ts +2 -0
- package/build/routes/__tests__/preview.index.test.d.ts.map +1 -0
- package/build/routes/__tests__/preview.index.test.js +82 -0
- package/build/routes/__tests__/preview.index.test.js.map +1 -0
- package/build/routes/preview.d.ts +58 -0
- package/build/routes/preview.d.ts.map +1 -0
- package/build/routes/preview.index.d.ts +23 -0
- package/build/routes/preview.index.d.ts.map +1 -0
- package/build/routes/preview.index.js +98 -0
- package/build/routes/preview.index.js.map +1 -0
- package/build/routes/preview.js +159 -0
- package/build/routes/preview.js.map +1 -0
- package/build/routes/tile.xyz.raster.d.ts +15 -0
- package/build/routes/tile.xyz.raster.d.ts.map +1 -1
- package/build/routes/tile.xyz.raster.js +45 -24
- package/build/routes/tile.xyz.raster.js.map +1 -1
- package/build/util/validate.d.ts +3 -1
- package/build/util/validate.d.ts.map +1 -1
- package/build/util/validate.js +10 -0
- package/build/util/validate.js.map +1 -1
- package/bundle.sh +1 -1
- package/package.json +8 -8
- package/src/cli/render.preview.ts +44 -0
- package/src/index.ts +7 -0
- package/src/routes/__tests__/preview.index.test.ts +94 -0
- package/src/routes/preview.index.ts +119 -0
- package/src/routes/preview.ts +234 -0
- package/src/routes/tile.xyz.raster.ts +53 -28
- package/src/util/validate.ts +10 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { ConfigTileSetRaster } from '@basemaps/config';
|
|
2
|
+
import { Bounds, ImageFormat, LatLon, Projection, TileMatrixSet } from '@basemaps/geo';
|
|
3
|
+
import { CompositionTiff, Tiler } from '@basemaps/tiler';
|
|
4
|
+
import { SharpOverlay, TileMakerSharp } from '@basemaps/tiler-sharp';
|
|
5
|
+
import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
|
|
6
|
+
import { ConfigLoader } from '../util/config.loader.js';
|
|
7
|
+
import { Etag } from '../util/etag.js';
|
|
8
|
+
import { NotModified } from '../util/response.js';
|
|
9
|
+
import { Validate } from '../util/validate.js';
|
|
10
|
+
import { DefaultResizeKernel, TileXyzRaster, isArchiveTiff } from './tile.xyz.raster.js';
|
|
11
|
+
import sharp from 'sharp';
|
|
12
|
+
|
|
13
|
+
export interface PreviewGet {
|
|
14
|
+
Params: {
|
|
15
|
+
tileSet: string;
|
|
16
|
+
tileMatrix: string;
|
|
17
|
+
lat: string;
|
|
18
|
+
lon: string;
|
|
19
|
+
z: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const PreviewSize = { width: 1200, height: 630 };
|
|
24
|
+
const TilerSharp = new TileMakerSharp(PreviewSize.width, PreviewSize.height);
|
|
25
|
+
|
|
26
|
+
const OutputFormat = ImageFormat.Webp;
|
|
27
|
+
/** Slightly grey color for the checker background */
|
|
28
|
+
const PreviewBackgroundFillColor = 0xef;
|
|
29
|
+
/** Make th e checkered background 30x30px */
|
|
30
|
+
const PreviewBackgroundSizePx = 30;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Serve a preview of a imagery set
|
|
34
|
+
*
|
|
35
|
+
* /v1/preview/:tileSet/:tileMatrixSet/:z/:lon/:lat
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* Raster Tile `/v1/preview/aerial/WebMercatorQuad/12/177.3998405/-39.0852555`
|
|
39
|
+
*
|
|
40
|
+
*/
|
|
41
|
+
export async function tilePreviewGet(req: LambdaHttpRequest<PreviewGet>): Promise<LambdaHttpResponse> {
|
|
42
|
+
const tileMatrix = Validate.getTileMatrixSet(req.params.tileMatrix);
|
|
43
|
+
if (tileMatrix == null) return new LambdaHttpResponse(404, 'Tile Matrix not found');
|
|
44
|
+
|
|
45
|
+
req.set('tileMatrix', tileMatrix.identifier);
|
|
46
|
+
req.set('projection', tileMatrix.projection.code);
|
|
47
|
+
|
|
48
|
+
// TODO we should detect the format based off the "Accept" header and maybe default back to webp
|
|
49
|
+
req.set('extension', OutputFormat);
|
|
50
|
+
|
|
51
|
+
const location = Validate.getLocation(req.params.lon, req.params.lat);
|
|
52
|
+
if (location == null) return new LambdaHttpResponse(404, 'Preview location not found');
|
|
53
|
+
req.set('location', location);
|
|
54
|
+
|
|
55
|
+
const z = Math.ceil(parseFloat(req.params.z));
|
|
56
|
+
if (isNaN(z) || z < 0 || z > tileMatrix.maxZoom) return new LambdaHttpResponse(404, 'Preview zoom invalid');
|
|
57
|
+
|
|
58
|
+
const config = await ConfigLoader.load(req);
|
|
59
|
+
|
|
60
|
+
req.timer.start('tileset:load');
|
|
61
|
+
const tileSet = await config.TileSet.get(config.TileSet.id(req.params.tileSet));
|
|
62
|
+
req.timer.end('tileset:load');
|
|
63
|
+
if (tileSet == null) return new LambdaHttpResponse(404, 'Tileset not found');
|
|
64
|
+
// Only raster previews are supported
|
|
65
|
+
if (tileSet.type !== 'raster') return new LambdaHttpResponse(404, 'Preview invalid tile set type');
|
|
66
|
+
|
|
67
|
+
return renderPreview(req, { tileSet, tileMatrix, location, outputFormat: OutputFormat, z });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface PreviewRenderContext {
|
|
71
|
+
/** Imagery to use */
|
|
72
|
+
tileSet: ConfigTileSetRaster;
|
|
73
|
+
/** output tilematrix to use */
|
|
74
|
+
tileMatrix: TileMatrixSet;
|
|
75
|
+
/** Center point of the preview */
|
|
76
|
+
location: LatLon;
|
|
77
|
+
/** Iamge format to render the preview as */
|
|
78
|
+
outputFormat: ImageFormat;
|
|
79
|
+
/** Zom level to be use, must be a integer */
|
|
80
|
+
z: number;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Render the preview!
|
|
84
|
+
*
|
|
85
|
+
* All the parameter validation is done in {@link tilePreviewGet} this function expects everything to align
|
|
86
|
+
*
|
|
87
|
+
* @returns 304 not modified if the ETag matches or 200 ok with the content of the image
|
|
88
|
+
*/
|
|
89
|
+
export async function renderPreview(req: LambdaHttpRequest, ctx: PreviewRenderContext): Promise<LambdaHttpResponse> {
|
|
90
|
+
const tileMatrix = ctx.tileMatrix;
|
|
91
|
+
// Convert the input lat/lon into the projected coordinates to make it easier to do math with
|
|
92
|
+
const coords = Projection.get(tileMatrix).fromWgs84([ctx.location.lon, ctx.location.lat]);
|
|
93
|
+
|
|
94
|
+
// use the input as the center point, but round it to the closest pixel to make it easier to do math
|
|
95
|
+
const point = tileMatrix.sourceToPixels(coords[0], coords[1], ctx.z);
|
|
96
|
+
const pointCenter = { x: Math.round(point.x), y: Math.round(point.y) };
|
|
97
|
+
|
|
98
|
+
// position of the preview in relation to the output screen
|
|
99
|
+
const screenBounds = new Bounds(
|
|
100
|
+
pointCenter.x - PreviewSize.width / 2,
|
|
101
|
+
pointCenter.y - PreviewSize.height / 2,
|
|
102
|
+
PreviewSize.width,
|
|
103
|
+
PreviewSize.height,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// Convert the screen bounds back into the source to find the assets we need to render the preview
|
|
107
|
+
const topLeft = tileMatrix.pixelsToSource(screenBounds.x, screenBounds.y, ctx.z);
|
|
108
|
+
const bottomRight = tileMatrix.pixelsToSource(screenBounds.right, screenBounds.bottom, ctx.z);
|
|
109
|
+
const sourceBounds = Bounds.fromBbox([topLeft.x, topLeft.y, bottomRight.x, bottomRight.y]);
|
|
110
|
+
|
|
111
|
+
const assetLocations = await TileXyzRaster.getAssetsForBounds(
|
|
112
|
+
req,
|
|
113
|
+
ctx.tileSet,
|
|
114
|
+
tileMatrix,
|
|
115
|
+
sourceBounds,
|
|
116
|
+
ctx.z,
|
|
117
|
+
true,
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const cacheKey = Etag.key(assetLocations);
|
|
121
|
+
if (Etag.isNotModified(req, cacheKey)) return NotModified();
|
|
122
|
+
|
|
123
|
+
const assets = await TileXyzRaster.loadAssets(req, assetLocations);
|
|
124
|
+
const tiler = new Tiler(tileMatrix);
|
|
125
|
+
|
|
126
|
+
// Figure out what tiffs and tiles need to be read and where they are placed on the output image
|
|
127
|
+
const compositions: CompositionTiff[] = [];
|
|
128
|
+
for (const asset of assets) {
|
|
129
|
+
// there shouldn't be any Cotar archives in previews but ignore them to be safe
|
|
130
|
+
if (!isArchiveTiff(asset)) continue;
|
|
131
|
+
const result = tiler.getTiles(asset, screenBounds, ctx.z);
|
|
132
|
+
if (result == null) continue;
|
|
133
|
+
compositions.push(...result);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Load all the tiff tiles and resize/them into the correct locations
|
|
137
|
+
req.timer.start('compose:overlay');
|
|
138
|
+
const overlays = (await Promise.all(
|
|
139
|
+
compositions.map((comp) => TilerSharp.composeTileTiff(comp, DefaultResizeKernel)),
|
|
140
|
+
).then((items) => items.filter((f) => f != null))) as SharpOverlay[];
|
|
141
|
+
req.timer.end('compose:overlay');
|
|
142
|
+
|
|
143
|
+
// Create the output image and render all the individual pieces into them
|
|
144
|
+
const img = getBaseImage(ctx.tileSet.background);
|
|
145
|
+
img.composite(overlays);
|
|
146
|
+
|
|
147
|
+
req.timer.start('compose:compress');
|
|
148
|
+
const buf = await TilerSharp.toImage(ctx.outputFormat, img);
|
|
149
|
+
req.timer.end('compose:compress');
|
|
150
|
+
|
|
151
|
+
req.set('layersUsed', overlays.length);
|
|
152
|
+
req.set('bytes', buf.byteLength);
|
|
153
|
+
const response = new LambdaHttpResponse(200, 'ok');
|
|
154
|
+
response.header(HttpHeader.ETag, cacheKey);
|
|
155
|
+
response.header(HttpHeader.CacheControl, 'public, max-age=604800, stale-while-revalidate=86400');
|
|
156
|
+
response.buffer(buf, 'image/' + ctx.outputFormat);
|
|
157
|
+
|
|
158
|
+
const shortLocation = [ctx.location.lon.toFixed(7), ctx.location.lat.toFixed(7)].join('_');
|
|
159
|
+
const suggestedFileName = `preview_${ctx.tileSet.name}_z${ctx.z}_${shortLocation}.${ctx.outputFormat}`;
|
|
160
|
+
response.header('Content-Disposition', `inline; filename=\"${suggestedFileName}\"`);
|
|
161
|
+
|
|
162
|
+
return response;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function getBaseImage(bg?: { r: number; g: number; b: number; alpha: number }): sharp.Sharp {
|
|
166
|
+
if (bg == null || bg.alpha === 0) {
|
|
167
|
+
const buf = createCheckerBoard({
|
|
168
|
+
width: PreviewSize.width,
|
|
169
|
+
height: PreviewSize.height,
|
|
170
|
+
colors: { fill: PreviewBackgroundFillColor, background: 0xff },
|
|
171
|
+
size: PreviewBackgroundSizePx,
|
|
172
|
+
});
|
|
173
|
+
return sharp(buf.buffer, { raw: buf.raw });
|
|
174
|
+
}
|
|
175
|
+
return TilerSharp.createImage(bg);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export interface CheckerBoard {
|
|
179
|
+
/** Output image width in pixels */
|
|
180
|
+
width: number;
|
|
181
|
+
/** Output image height in pixels */
|
|
182
|
+
height: number;
|
|
183
|
+
colors: {
|
|
184
|
+
/** Color of the checker board eg 0xef */
|
|
185
|
+
fill: number;
|
|
186
|
+
/** Color of the background eg 0xff */
|
|
187
|
+
background: number;
|
|
188
|
+
};
|
|
189
|
+
/** Size of the checkers */
|
|
190
|
+
size: number;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Create a chess/checkerboard background alternating between two colors */
|
|
194
|
+
function createCheckerBoard(ctx: CheckerBoard): {
|
|
195
|
+
buffer: Buffer;
|
|
196
|
+
raw: { width: number; height: number; channels: 1 };
|
|
197
|
+
} {
|
|
198
|
+
const { width, height, size } = ctx;
|
|
199
|
+
const fillColor = ctx.colors.fill;
|
|
200
|
+
// Create a one band image, which starts off as full white
|
|
201
|
+
const buf = Buffer.alloc(height * width, ctx.colors.background); // 1 band grey buffer;
|
|
202
|
+
|
|
203
|
+
// Number of squares to make in x/y directions
|
|
204
|
+
const tileY = height / size;
|
|
205
|
+
const tileX = width / size;
|
|
206
|
+
|
|
207
|
+
// Fill in a square at the x/y pixel offsets
|
|
208
|
+
function fillSquare(xOffset: number, yOffset: number): void {
|
|
209
|
+
for (let y = 0; y < size; y++) {
|
|
210
|
+
const yPx = (yOffset + y) * width;
|
|
211
|
+
for (let x = 0; x < size; x++) {
|
|
212
|
+
const px = yPx + xOffset + x;
|
|
213
|
+
// Actually set the color
|
|
214
|
+
buf[px] = fillColor;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
for (let tX = 0; tX < tileX; tX++) {
|
|
219
|
+
for (let tY = 0; tY < tileY; tY++) {
|
|
220
|
+
const yOffset = tY * size;
|
|
221
|
+
const y2 = tY % 2;
|
|
222
|
+
|
|
223
|
+
// Draw every second tile alternating on rows
|
|
224
|
+
const x2 = tX % 2;
|
|
225
|
+
if (x2 === 0 && y2 === 1) continue;
|
|
226
|
+
if (x2 === 1 && y2 === 0) continue;
|
|
227
|
+
|
|
228
|
+
const xOffset = tX * size;
|
|
229
|
+
fillSquare(xOffset, yOffset);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return { buffer: buf, raw: { width, height, channels: 1 } };
|
|
234
|
+
}
|
|
@@ -24,46 +24,62 @@ export function getTiffName(name: string): string {
|
|
|
24
24
|
|
|
25
25
|
export type CloudArchive = CogTiff | Cotar;
|
|
26
26
|
|
|
27
|
+
/** Check to see if a cloud archive is a Tiff or a Cotar */
|
|
28
|
+
export function isArchiveTiff(x: CloudArchive): x is CogTiff {
|
|
29
|
+
if (x instanceof CogTiff) return true;
|
|
30
|
+
if (x.source.uri.endsWith('.tiff')) return true;
|
|
31
|
+
if (x.source.uri.endsWith('.tif')) return true;
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
27
35
|
export const TileComposer = new TileMakerSharp(256);
|
|
28
36
|
|
|
29
|
-
const DefaultResizeKernel = { in: 'lanczos3', out: 'lanczos3' } as const;
|
|
30
|
-
const DefaultBackground = { r: 0, g: 0, b: 0, alpha: 0 };
|
|
37
|
+
export const DefaultResizeKernel = { in: 'lanczos3', out: 'lanczos3' } as const;
|
|
38
|
+
export const DefaultBackground = { r: 0, g: 0, b: 0, alpha: 0 };
|
|
31
39
|
|
|
32
40
|
export const TileXyzRaster = {
|
|
33
|
-
async
|
|
41
|
+
async getAssetsForBounds(
|
|
42
|
+
req: LambdaHttpRequest,
|
|
43
|
+
tileSet: ConfigTileSetRaster,
|
|
44
|
+
tileMatrix: TileMatrixSet,
|
|
45
|
+
bounds: Bounds,
|
|
46
|
+
zoom: number,
|
|
47
|
+
ignoreOverview = false,
|
|
48
|
+
): Promise<string[]> {
|
|
34
49
|
const config = await ConfigLoader.load(req);
|
|
35
|
-
const imagery = await getAllImagery(config, tileSet.layers, [
|
|
50
|
+
const imagery = await getAllImagery(config, tileSet.layers, [tileMatrix.projection]);
|
|
36
51
|
const filteredLayers = filterLayers(req, tileSet.layers);
|
|
37
52
|
|
|
38
53
|
const output: string[] = [];
|
|
39
|
-
const tileBounds = xyz.tileMatrix.tileToSourceBounds(xyz.tile);
|
|
40
54
|
|
|
41
55
|
// All zoom level config is stored as Google zoom levels
|
|
42
|
-
const filterZoom = TileMatrixSet.convertZoomLevel(
|
|
56
|
+
const filterZoom = TileMatrixSet.convertZoomLevel(zoom, tileMatrix, TileMatrixSets.get(Epsg.Google));
|
|
43
57
|
for (const layer of filteredLayers) {
|
|
44
58
|
if (layer.maxZoom != null && filterZoom > layer.maxZoom) continue;
|
|
45
59
|
if (layer.minZoom != null && filterZoom < layer.minZoom) continue;
|
|
46
60
|
|
|
47
|
-
const imgId = layer[
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
61
|
+
const imgId = layer[tileMatrix.projection.code];
|
|
62
|
+
// Imagery does not exist for this projection
|
|
63
|
+
if (imgId == null) continue;
|
|
52
64
|
|
|
53
65
|
const img = imagery.get(imgId);
|
|
54
66
|
if (img == null) {
|
|
55
|
-
req.log.warn(
|
|
56
|
-
{ layer: layer.name, projection: xyz.tileMatrix.projection.code, imgId },
|
|
57
|
-
'Failed to lookup imagery',
|
|
58
|
-
);
|
|
67
|
+
req.log.warn({ layer: layer.name, projection: tileMatrix.projection.code, imgId }, 'Failed to lookup imagery');
|
|
59
68
|
continue;
|
|
60
69
|
}
|
|
61
|
-
if (!
|
|
70
|
+
if (!bounds.intersects(Bounds.fromJson(img.bounds))) continue;
|
|
62
71
|
|
|
63
72
|
for (const c of img.files) {
|
|
64
|
-
if (!
|
|
65
|
-
|
|
66
|
-
|
|
73
|
+
if (!bounds.intersects(Bounds.fromJson(c))) continue;
|
|
74
|
+
|
|
75
|
+
// If there are overviews and they exist for this zoom range and we are not ignoring them
|
|
76
|
+
// lets use the overviews instead!
|
|
77
|
+
if (
|
|
78
|
+
img.overviews &&
|
|
79
|
+
img.overviews.maxZoom >= filterZoom &&
|
|
80
|
+
img.overviews.minZoom <= filterZoom &&
|
|
81
|
+
ignoreOverview !== true
|
|
82
|
+
) {
|
|
67
83
|
output.push(fsa.join(img.uri, img.overviews.path));
|
|
68
84
|
break;
|
|
69
85
|
}
|
|
@@ -75,15 +91,9 @@ export const TileXyzRaster = {
|
|
|
75
91
|
return output;
|
|
76
92
|
},
|
|
77
93
|
|
|
78
|
-
async
|
|
79
|
-
if (xyz.tileType === VectorFormat.MapboxVectorTiles) return NotFound();
|
|
80
|
-
|
|
81
|
-
const assetPaths = await this.getAssetsForTile(req, tileSet, xyz);
|
|
82
|
-
const cacheKey = Etag.key(assetPaths);
|
|
83
|
-
if (Etag.isNotModified(req, cacheKey)) return NotModified();
|
|
84
|
-
|
|
94
|
+
async loadAssets(req: LambdaHttpRequest, assets: string[]): Promise<CloudArchive[]> {
|
|
85
95
|
const toLoad: Promise<CloudArchive | null>[] = [];
|
|
86
|
-
for (const assetPath of
|
|
96
|
+
for (const assetPath of assets) {
|
|
87
97
|
toLoad.push(
|
|
88
98
|
LoadingQueue((): Promise<CloudArchive | null> => {
|
|
89
99
|
if (assetPath.endsWith('.tar.co')) {
|
|
@@ -100,7 +110,22 @@ export const TileXyzRaster = {
|
|
|
100
110
|
);
|
|
101
111
|
}
|
|
102
112
|
|
|
103
|
-
|
|
113
|
+
return (await Promise.all(toLoad)).filter((f) => f != null) as CloudArchive[];
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
async getAssetsForTile(req: LambdaHttpRequest, tileSet: ConfigTileSetRaster, xyz: TileXyz): Promise<string[]> {
|
|
117
|
+
const tileBounds = xyz.tileMatrix.tileToSourceBounds(xyz.tile);
|
|
118
|
+
return TileXyzRaster.getAssetsForBounds(req, tileSet, xyz.tileMatrix, tileBounds, xyz.tile.z);
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
async tile(req: LambdaHttpRequest, tileSet: ConfigTileSetRaster, xyz: TileXyz): Promise<LambdaHttpResponse> {
|
|
122
|
+
if (xyz.tileType === VectorFormat.MapboxVectorTiles) return NotFound();
|
|
123
|
+
|
|
124
|
+
const assetPaths = await this.getAssetsForTile(req, tileSet, xyz);
|
|
125
|
+
const cacheKey = Etag.key(assetPaths);
|
|
126
|
+
if (Etag.isNotModified(req, cacheKey)) return NotModified();
|
|
127
|
+
|
|
128
|
+
const assets = await TileXyzRaster.loadAssets(req, assetPaths);
|
|
104
129
|
|
|
105
130
|
const tiler = new Tiler(xyz.tileMatrix);
|
|
106
131
|
const layers = await tiler.tile(assets, xyz.tile.x, xyz.tile.y, xyz.tile.z);
|
package/src/util/validate.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ImageFormat, Projection, TileMatrixSet, TileMatrixSets, VectorFormat } from '@basemaps/geo';
|
|
1
|
+
import { ImageFormat, LatLon, Projection, TileMatrixSet, TileMatrixSets, VectorFormat } from '@basemaps/geo';
|
|
2
2
|
import { Const, isValidApiKey, truncateApiKey } from '@basemaps/shared';
|
|
3
3
|
import { getImageFormat } from '@basemaps/tiler';
|
|
4
4
|
import { LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
|
|
@@ -55,6 +55,15 @@ export const Validate = {
|
|
|
55
55
|
if (tileType === VectorFormat.MapboxVectorTiles) return VectorFormat.MapboxVectorTiles;
|
|
56
56
|
return null;
|
|
57
57
|
},
|
|
58
|
+
|
|
59
|
+
/** Validate that a lat and lon are between -90/90 and -180/180 */
|
|
60
|
+
getLocation(lonIn: string, latIn: string): LatLon | null {
|
|
61
|
+
const lat = parseFloat(latIn);
|
|
62
|
+
const lon = parseFloat(lonIn);
|
|
63
|
+
if (isNaN(lon) || lon < -180 || lon > 180) return null;
|
|
64
|
+
if (isNaN(lat) || lat < -90 || lat > 90) return null;
|
|
65
|
+
return { lon, lat };
|
|
66
|
+
},
|
|
58
67
|
/**
|
|
59
68
|
* Validate that the tile request is somewhat valid
|
|
60
69
|
* - Valid projection
|