Skip to content

Commit 900a84e

Browse files
authored
feat(lambda-tiler): Provide support for Arcgis online vector map. BM-78 (#2403)
* Provide support for arcgis online vector mapl] * Fix some minor issues.
1 parent f0caee1 commit 900a84e

File tree

4 files changed

+182
-1
lines changed

4 files changed

+182
-1
lines changed

packages/config/src/config/vector.style.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,22 @@ interface SourceRaster {
1515
attribution?: string;
1616
}
1717

18+
/**
19+
* https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/
20+
*/
21+
interface Layer {
22+
id: string;
23+
type: string;
24+
filter?: unknown[];
25+
layout?: unknown;
26+
minzoom?: number;
27+
maxzoom?: number;
28+
metadata?: unknown;
29+
paint?: unknown;
30+
source?: string;
31+
'source-layer'?: string;
32+
}
33+
1834
type Source = SourceVector | SourceRaster;
1935

2036
export type Sources = Record<string, Source>;
@@ -41,7 +57,7 @@ export interface StyleJson {
4157
sources: Sources;
4258

4359
/** Layers will be drawn in the order of this array. */
44-
layers: unknown[];
60+
layers: Layer[];
4561
}
4662

4763
export interface ConfigVectorStyle extends BaseConfig {
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { Config, Sources, StyleJson, TileSetType } from '@basemaps/config';
2+
import { Env, fsa } from '@basemaps/shared';
3+
import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
4+
import { convertRelativeUrl } from '../routes/tile.style.json.js';
5+
import { Etag } from '../util/etag.js';
6+
import { NotFound, NotModified } from '../util/response.js';
7+
import { Validate } from '../util/validate.js';
8+
9+
interface StyleGet {
10+
Params: {
11+
tileSet: string;
12+
};
13+
}
14+
15+
function tileserverUrl(tileSet: string, apiKey: string): string {
16+
const host = Env.get(Env.PublicUrlBase) ?? '';
17+
const url = `/v1/arcgis/rest/services/${tileSet}/VectorTileServer`;
18+
const fullUrl = new URL(fsa.join(host, url));
19+
fullUrl.searchParams.set('api', apiKey);
20+
fullUrl.searchParams.set('f', 'json');
21+
return fullUrl.toString().replace(/%7B/g, '{').replace(/%7D/g, '}');
22+
}
23+
24+
function convertStyleJson(tileSet: string, style: StyleJson, apiKey: string): StyleJson {
25+
const sources: Sources = JSON.parse(JSON.stringify(style.sources));
26+
// Only keep the vector layer and update the source url
27+
for (const [key, value] of Object.entries(sources)) {
28+
if (value.type === 'vector') {
29+
value.url = tileserverUrl(tileSet, apiKey);
30+
sources[key] = value;
31+
} else {
32+
delete sources[key];
33+
}
34+
}
35+
36+
// Remove all the not vector layers.
37+
const layers = [];
38+
for (const layer of style.layers) {
39+
if (layer.source != null && !sources.hasOwnProperty(layer.source)) continue;
40+
layers.push(layer);
41+
}
42+
43+
return {
44+
version: 8,
45+
id: style.id,
46+
name: style.name,
47+
sources,
48+
layers,
49+
metadata: style.metadata ?? {},
50+
glyphs: convertRelativeUrl(style.glyphs),
51+
sprite: convertRelativeUrl(style.sprite),
52+
} as StyleJson;
53+
}
54+
55+
export async function arcgisStyleJsonGet(req: LambdaHttpRequest<StyleGet>): Promise<LambdaHttpResponse> {
56+
const apiKey = Validate.apiKey(req);
57+
const tileSet = await Config.TileSet.get(Config.TileSet.id(req.params.tileSet));
58+
if (tileSet?.type !== TileSetType.Vector) return NotFound();
59+
60+
const style = req.query.get('style');
61+
const styleName = style ? style : 'topographic'; // Defalut to topographic style
62+
63+
// Get style Config from db
64+
const dbId = Config.Style.id(styleName);
65+
const styleConfig = await Config.Style.get(dbId);
66+
if (styleConfig == null) return NotFound();
67+
68+
// Prepare sources and add linz source
69+
const styleJson = convertStyleJson(tileSet.name, styleConfig.style, apiKey);
70+
const data = Buffer.from(JSON.stringify(styleJson));
71+
72+
const cacheKey = Etag.key(data);
73+
if (Etag.isNotModified(req, cacheKey)) return NotModified();
74+
75+
const response = new LambdaHttpResponse(200, 'ok');
76+
response.header(HttpHeader.ETag, cacheKey);
77+
response.header(HttpHeader.CacheControl, 'no-store');
78+
response.buffer(data, 'application/json');
79+
req.set('bytes', data.byteLength);
80+
return response;
81+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { Config, TileSetType } from '@basemaps/config';
2+
import { GoogleTms } from '@basemaps/geo';
3+
import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda';
4+
import { convertRelativeUrl } from '../routes/tile.style.json.js';
5+
import { NotFound } from '../util/response.js';
6+
import { Validate } from '../util/validate.js';
7+
8+
/** Zoom level for the tilematrix which will be slice by 1 during the covertion to arcgis tileserve lods */
9+
const MaxTileMatrixZoom = 18;
10+
const MinTileMatrixZoom = 18;
11+
12+
export interface VectorTileServer {
13+
Params: {
14+
tileSet: string;
15+
};
16+
}
17+
18+
export async function arcgisTileServerGet(req: LambdaHttpRequest<VectorTileServer>): Promise<LambdaHttpResponse> {
19+
const tileSet = await Config.TileSet.get(Config.TileSet.id(req.params.tileSet));
20+
if (tileSet?.type !== TileSetType.Vector) return NotFound();
21+
const apiKey = Validate.apiKey(req);
22+
const f = req.query.get('f');
23+
if (f !== 'json') return NotFound();
24+
const extent = {
25+
xmin: GoogleTms.extent.x,
26+
ymin: GoogleTms.extent.y,
27+
xmax: GoogleTms.extent.right,
28+
ymax: GoogleTms.extent.bottom,
29+
// TODO where is wkid from
30+
spatialReference: { wkid: 102100, latestWkid: GoogleTms.projection.code },
31+
};
32+
const vectorTileServer = {
33+
currentVersion: 10.81,
34+
name: tileSet.name,
35+
capabilities: 'TilesOnly',
36+
type: 'indexedVector',
37+
defaultStyles: '',
38+
tiles: [convertRelativeUrl(`/v1/tiles/${tileSet.name}/WebMercatorQuad/{z}/{x}/{y}.pbf`, apiKey)],
39+
exportTilesAllowed: false,
40+
maxExportTilesCount: 0,
41+
initialExtent: extent,
42+
fullExtent: extent,
43+
minScale: 0.0,
44+
maxScale: 0.0,
45+
tileInfo: {
46+
rows: 512,
47+
cols: 512,
48+
dpi: 96,
49+
format: 'pbf',
50+
origin: { x: GoogleTms.extent.x, y: GoogleTms.extent.bottom },
51+
spatialReference: { wkid: 102100, latestWkid: GoogleTms.projection.code },
52+
lods: GoogleTms.zooms.slice(MinTileMatrixZoom, MaxTileMatrixZoom).map((c, i) => {
53+
return {
54+
level: i,
55+
resolution: c.scaleDenominator * 0.28e-3,
56+
scale: c.scaleDenominator,
57+
};
58+
}),
59+
},
60+
maxzoom: 22,
61+
minLOD: 0,
62+
maxLOD: 15,
63+
resourceInfo: {
64+
styleVersion: 8,
65+
tileCompression: 'gzip',
66+
cacheInfo: { storageInfo: { packetSize: 128, storageFormat: 'compactV2' } },
67+
},
68+
};
69+
70+
const json = JSON.stringify(vectorTileServer, null, 2);
71+
const data = Buffer.from(json);
72+
73+
const response = new LambdaHttpResponse(200, 'ok');
74+
response.header(HttpHeader.CacheControl, 'no-store');
75+
response.buffer(data, 'application/json');
76+
req.set('bytes', data.byteLength);
77+
return response;
78+
}

packages/lambda-tiler/src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { LogConfig } from '@basemaps/shared';
22
import { LambdaHttpResponse, lf } from '@linzjs/lambda';
3+
import { arcgisStyleJsonGet } from './arcgis/arcgis.style.json.js';
4+
import { arcgisTileServerGet } from './arcgis/vector.tile.server.js';
35
import { tileAttributionGet } from './routes/attribution.js';
46
import { fontGet, fontList } from './routes/fonts.js';
57
import { healthGet } from './routes/health.js';
@@ -94,3 +96,7 @@ handler.router.get('/v1/attribution/:tileSet/:tileMatrix/summary.json', tileAttr
9496
handler.router.get('/v1/tiles/:tileSet/:tileMatrix/WMTSCapabilities.xml', wmtsCapabilitiesGet);
9597
handler.router.get('/v1/tiles/:tileSet/WMTSCapabilities.xml', wmtsCapabilitiesGet);
9698
handler.router.get('/v1/tiles/WMTSCapabilities.xml', wmtsCapabilitiesGet);
99+
100+
// Arcgis Vector
101+
handler.router.get('/v1/arcgis/rest/services/:tileSet/VectorTileServer', arcgisTileServerGet);
102+
handler.router.get('/v1/arcgis/rest/services/:tileSet/VectorTileServer/root.json', arcgisStyleJsonGet);

0 commit comments

Comments
 (0)