diff --git a/packages/lambda-tiler/src/index.ts b/packages/lambda-tiler/src/index.ts index 696946838..ce16d315b 100644 --- a/packages/lambda-tiler/src/index.ts +++ b/packages/lambda-tiler/src/index.ts @@ -4,6 +4,7 @@ import { arcgisInfoGet } from './arcgis/arcgis.info.js'; import { arcgisStyleJsonGet } from './arcgis/arcgis.style.json.js'; import { arcgisTileServerGet } from './arcgis/vector.tile.server.js'; import { tileAttributionGet } from './routes/attribution.js'; +import { configImageryGet, configTileSetGet } from './routes/config.js'; import { fontGet, fontList } from './routes/fonts.js'; import { healthGet } from './routes/health.js'; import { imageryGet } from './routes/imagery.js'; @@ -70,6 +71,10 @@ handler.router.get('/v1/version', versionGet); // Image Metadata handler.router.get('/v1/imagery/:imageryId/:fileName', imageryGet); +// Config +handler.router.get('/v1/config/:tileSet.json', configTileSetGet); +handler.router.get('/v1/config/:tileSet/:imageryId.json', configImageryGet); + // Sprites handler.router.get('/v1/sprites/:spriteName', spriteGet); diff --git a/packages/lambda-tiler/src/routes/config.ts b/packages/lambda-tiler/src/routes/config.ts new file mode 100644 index 000000000..3f64855c4 --- /dev/null +++ b/packages/lambda-tiler/src/routes/config.ts @@ -0,0 +1,83 @@ +import { standardizeLayerName } from '@basemaps/config'; +import { GoogleTms, TileMatrixSets } from '@basemaps/geo'; +import { HttpHeader, LambdaHttpRequest, LambdaHttpResponse } from '@linzjs/lambda'; +import { ConfigLoader } from '../util/config.loader.js'; +import { Etag } from '../util/etag.js'; +import { NotFound, NotModified } from '../util/response.js'; + +async function sendJson(req: LambdaHttpRequest, toSend: unknown): Promise { + const data = Buffer.from(JSON.stringify(toSend)); + + const cacheKey = Etag.key(data); + if (Etag.isNotModified(req, cacheKey)) return NotModified(); + + const response = new LambdaHttpResponse(200, 'ok'); + response.header(HttpHeader.ETag, cacheKey); + response.header(HttpHeader.CacheControl, 'no-store'); + response.buffer(data, 'application/json'); + req.set('bytes', data.byteLength); + return response; +} + +interface ConfigTileSetGet { + Params: { + tileSet: string; + }; +} + +export async function configTileSetGet(req: LambdaHttpRequest): Promise { + const config = await ConfigLoader.load(req); + + req.timer.start('tileset:load'); + const tileSet = await config.TileSet.get(config.TileSet.id(req.params.tileSet)); + req.timer.end('tileset:load'); + if (tileSet == null) return NotFound(); + + return sendJson(req, tileSet); +} + +interface ConfigImageryGet { + Params: { + tileSet: string; + imageryId: string; + }; +} + +/** + * Load the imagery configuration by either name or id + * + * @param req + * @returns + */ +export async function configImageryGet(req: LambdaHttpRequest): Promise { + const config = await ConfigLoader.load(req); + + req.timer.start('tileset:load'); + const tileSet = await config.TileSet.get(config.TileSet.id(req.params.tileSet)); + req.timer.end('tileset:load'); + if (tileSet == null) return NotFound(); + + req.timer.start('imagery:load'); + let imagery = await config.Imagery.get(config.Imagery.id(req.params.imageryId)); + req.timer.end('imagery:load'); + + if (imagery == null) { + const imageryLayer = tileSet.layers.find( + (f) => f.name === req.params.imageryId || standardizeLayerName(f.name) === req.params.imageryId, + ); + if (imageryLayer == null) return NotFound(); + + const tileMatrix = TileMatrixSets.find(req.query.get('tileMatrix') ?? GoogleTms.identifier); + if (tileMatrix == null) return NotFound(); + + const imageryId = imageryLayer[tileMatrix.projection.code]; + if (imageryId == null) return NotFound(); + + req.timer.start('imagery:load:sub'); + imagery = await config.Imagery.get(config.Imagery.id(imageryId)); + req.timer.end('imagery:load:sub'); + } + + if (imagery == null) return NotFound(); + return sendJson(req, imagery); +} diff --git a/packages/landing/package.json b/packages/landing/package.json index 226b5a373..20be23d25 100644 --- a/packages/landing/package.json +++ b/packages/landing/package.json @@ -29,6 +29,7 @@ ], "devDependencies": { "@basemaps/attribution": "^6.32.1", + "@basemaps/config": "^6.34.0", "@basemaps/geo": "^6.32.1", "@basemaps/infra": "^6.34.0", "@basemaps/shared": "^6.34.0", diff --git a/packages/landing/src/components/debug.tsx b/packages/landing/src/components/debug.tsx index ea71d759f..9652903f6 100644 --- a/packages/landing/src/components/debug.tsx +++ b/packages/landing/src/components/debug.tsx @@ -1,12 +1,13 @@ +import { ConfigImagery } from '@basemaps/config/build/config/imagery.js'; +import { ConfigTileSetRaster } from '@basemaps/config/build/config/tile.set.js'; import { GoogleTms } from '@basemaps/geo'; -import { BBoxFeatureCollection } from '@linzjs/geojson'; -import { StyleSpecification } from 'maplibre-gl'; import { Component, ComponentChild, Fragment } from 'preact'; import { Attributions } from '../attribution.js'; import { Config } from '../config.js'; +import { ConfigData } from '../config.layer.js'; import { MapConfig } from '../config.map.js'; -import { projectGeoJson } from '../tile.matrix.js'; -import { MapOptionType, WindowUrl } from '../url.js'; +import { DebugMap } from '../debug.map.js'; +import { WindowUrl } from '../url.js'; import { onMapLoaded } from './map.js'; function debugSlider( @@ -33,8 +34,13 @@ export class Debug extends Component< featureCogName: string | undefined; featureSourceId: string | number | undefined; featureSourceName: string | undefined; + tileSet: ConfigTileSetRaster | null; + imagery: ConfigImagery | null; + config: string | null; } > { + debugMap = new DebugMap(); + componentDidMount(): void { this.waitForMap(); } @@ -75,14 +81,52 @@ export class Debug extends Component< }; updateFromConfig(): void { - this.setPurple(Config.map.debug['debug.background'] === 'magenta'); - this.adjustRaster('osm', Config.map.debug['debug.layer.osm']); - this.adjustRaster('linz-aerial', Config.map.debug['debug.layer.linz-aerial']); - this.adjustVector(Config.map.debug['debug.layer.linz-topographic']); + if (this.state.tileSet?.id !== Config.map.layerId || this.state.config !== Config.map.config) { + this._loadingConfig = this._loadingConfig.then(() => this.loadConfig()); + } + + this.debugMap.setPurple(Config.map.debug['debug.background'] === 'magenta'); + this.debugMap.adjustRaster(this.props.map, 'osm', Config.map.debug['debug.layer.osm']); + this.debugMap.adjustRaster(this.props.map, 'linz-aerial', Config.map.debug['debug.layer.linz-aerial']); + this.debugMap.adjustVector(this.props.map, Config.map.debug['debug.layer.linz-topographic']); this.setVectorShown(Config.map.debug['debug.source'], 'source'); this.setVectorShown(Config.map.debug['debug.cog'], 'cog'); } + /** Show the source bounding box ont he map */ + toggleCogs = (e: Event): void => { + const target = e.target as HTMLInputElement; + Config.map.setDebug('debug.cog', target.checked); + this.setVectorShown(target.checked, 'cog'); + }; + + /** Show the source bounding box ont he map */ + toggleSource = (e: Event): void => { + const target = e.target as HTMLInputElement; + Config.map.setDebug('debug.source', target.checked); + this.setVectorShown(target.checked, 'source'); + }; + + _loadingConfig: Promise = Promise.resolve(); + async loadConfig(): Promise { + const tileSetId = Config.map.layerId; + if (this.state.tileSet?.id === tileSetId) return; + return ConfigData.getTileSet(tileSetId).then((tileSet) => { + this.setState({ ...this.state, tileSet, config: Config.map.config }); + + if (tileSet == null) return; + if (tileSet.layers.length !== 1) return; + + const projectionCode = Config.map.tileMatrix.projection.code; + const imageryId = tileSet.layers[0][projectionCode]; + if (imageryId == null) return; + + return ConfigData.getImagery(tileSetId, imageryId).then((imagery) => { + this.setState({ ...this.state, imagery, config: Config.map.config }); + }); + }); + } + render(): ComponentChild { if (Config.map.debug['debug.screenshot']) return null; return ( @@ -114,7 +158,7 @@ export class Debug extends Component< @@ -122,9 +166,8 @@ export class Debug extends Component< } renderCogToggle(): ComponentChild { - // TODO this is a nasty hack to detect if a direct imageryId is being viewed - if (!Config.map.layerId.startsWith('01')) return null; - const cogLocation = WindowUrl.toImageryUrl(`im_${Config.map.layerId}`, 'covering.geojson'); + if (this.state.imagery == null) return null; + const cogLocation = WindowUrl.toImageryUrl(this.state.imagery.id, 'covering.geojson'); return ( @@ -147,9 +190,8 @@ export class Debug extends Component< } renderSourceToggle(): ComponentChild { - // TODO this is a nasty hack to detect if a direct imageryId is being viewed - if (!Config.map.layerId.startsWith('01')) return null; - const sourceLocation = WindowUrl.toImageryUrl(`im_${Config.map.layerId}`, 'source.geojson'); + if (this.state.imagery == null) return null; + const sourceLocation = WindowUrl.toImageryUrl(this.state.imagery.id, 'source.geojson'); return (
@@ -170,19 +212,36 @@ export class Debug extends Component< ); } - /** Show the source bounding box ont he map */ - toggleCogs = (e: Event): void => { - const target = e.target as HTMLInputElement; - Config.map.setDebug('debug.cog', target.checked); - this.setVectorShown(target.checked, 'cog'); - }; + renderSliders(): ComponentChild | null { + // Disable the sliders for screenshots + if (Config.map.debug['debug.screenshot']) return; + // Only 3857 currently works with OSM/Topographic map + if (Config.map.tileMatrix.identifier !== GoogleTms.identifier) { + return ( +
+ + {debugSlider('linz-aerial', this.debugMap.adjustLinzAerial)} +
+ ); + } - /** Show the source bounding box ont he map */ - toggleSource = (e: Event): void => { - const target = e.target as HTMLInputElement; - Config.map.setDebug('debug.source', target.checked); - this.setVectorShown(target.checked, 'source'); - }; + return ( + +
+ + {debugSlider('osm', this.debugMap.adjustOsm)} +
+
+ + {debugSlider('linz-topographic', this.debugMap.adjustTopographic)} +
+
+ + {debugSlider('linz-aerial', this.debugMap.adjustLinzAerial)} +
+
+ ); + } trackMouseMove(layerId: string, type: 'source' | 'cog'): void { const sourceId = `${layerId}_${type}`; @@ -233,9 +292,9 @@ export class Debug extends Component< const color = type === 'source' ? '#ff00ff' : '#ff0000'; - this.loadSourceLayer(layerId, type).then(() => { + if (this.state.imagery == null) return; + this.debugMap.loadSourceLayer(this.props.map, layerId, this.state.imagery, type).then(() => { if (map.getLayer(layerLineId) != null) return; - // Fill is needed to make the mouse move work even though it has opacity 0 map.addLayer({ id: layerFillId, @@ -260,205 +319,4 @@ export class Debug extends Component< }); }); } - - _layerLoading: Map> = new Map(); - loadSourceLayer(layerId: string, type: 'source' | 'cog'): Promise { - const layerKey = `${layerId}-${type}`; - let existing = this._layerLoading.get(layerKey); - if (existing == null) { - existing = this._loadSourceLayer(layerId, type); - this._layerLoading.set(layerKey, existing); - } - return existing; - } - - async _loadSourceLayer(layerId: string, type: 'source' | 'cog'): Promise { - const map = this.props.map; - - const sourceId = `${layerId}_${type}`; - const layerFillId = `${sourceId}_fill`; - if (map.getLayer(layerFillId) != null) return; - - const sourceUri = WindowUrl.toImageryUrl( - `im_${layerId}`, - type === 'source' ? 'source.geojson' : 'covering.geojson', - ); - - const res = await fetch(sourceUri); - if (!res.ok) return; - - const data: BBoxFeatureCollection = await res.json(); - if (Config.map.tileMatrix.projection !== GoogleTms.projection) projectGeoJson(data, Config.map.tileMatrix); - - let id = 0; - // Ensure there is a id on each feature - for (const f of data.features) f.id = id++; - - map.addSource(sourceId, { type: 'geojson', data }); - } - - renderSliders(): ComponentChild | null { - // Disable the sliders for screenshots - if (Config.map.debug['debug.screenshot']) return; - // Only 3857 currently works with OSM/Topographic map - if (Config.map.tileMatrix.identifier !== GoogleTms.identifier) { - return ( -
- - {debugSlider('linz-aerial', this.adjustLinzAerial)} -
- ); - } - - return ( - -
- - {debugSlider('osm', this.adjustOsm)} -
-
- - {debugSlider('linz-topographic', this.adjustTopographic)} -
-
- - {debugSlider('linz-aerial', this.adjustLinzAerial)} -
-
- ); - } - - _styleJson: Promise | null = null; - get styleJson(): Promise { - if (this._styleJson == null) { - this._styleJson = fetch( - WindowUrl.toTileUrl(MapOptionType.Style, Config.map.tileMatrix, 'topographic', 'topographic', null), - ).then((f) => f.json()); - } - return this._styleJson; - } - - adjustTopographic = (e: Event): void => { - const slider = e.target as HTMLInputElement; - Config.map.setDebug('debug.layer.linz-topographic', Number(slider.value)); - }; - - async adjustVector(value: number): Promise { - const styleJson = await this.styleJson; - const map = this.props.map; - - const hasTopographic = map.getSource('LINZ Basemaps'); - if (hasTopographic == null) { - if (value === 0) return; // Going to remove it anyway so just abort early - const source = styleJson.sources?.['LINZ Basemaps']; - if (source == null) return; - map.addSource('LINZ Basemaps', source); - map.setStyle({ ...map.getStyle(), glyphs: styleJson.glyphs, sprite: styleJson.sprite }); - // Setting glyphs/sprites forces a full map refresh, wait for the refresh before adjusting the style - map.once('style.load', () => this.adjustVector(value)); - return; - } - - const layers = styleJson.layers?.filter((f) => f.type !== 'background' && f.source === 'LINZ Basemaps') ?? []; - - // Do not hide topographic layers when trying to inspect the topographic layer - if (Config.map.layerId === 'topographic') return; - // Force all the layers to be invisible to start, otherwise the map will "flash" on then off - for (const layer of layers) { - const paint = (layer.paint ?? {}) as Record; - if (layer.type === 'symbol') { - paint['icon-opacity'] = 0; - paint['text-opacity'] = 0; - } else { - paint[`${layer.type}-opacity`] = 0; - } - layer.paint = paint; - } - - if (value === 0) { - for (const layer of layers) { - if (map.getLayer(layer.id) == null) continue; - map.removeLayer(layer.id); - } - return; - } - - // Ensure all the layers are loaded before styling - if (map.getLayer(layers[0].id) == null) { - if (value === 0) return; - for (const layer of layers) map.addLayer(layer); - } - - for (const layer of layers) { - if (map.getLayer(layer.id) == null) continue; - if (layer.type === 'symbol') { - map.setPaintProperty(layer.id, `icon-opacity`, value); - map.setPaintProperty(layer.id, `text-opacity`, value); - } else { - map.setPaintProperty(layer.id, `${layer.type}-opacity`, value); - } - } - } - - adjustOsm = (e: Event): void => { - Config.map.setDebug('debug.layer.osm', Number((e.target as HTMLInputElement).value)); - }; - adjustLinzAerial = (e: Event): void => { - Config.map.setDebug('debug.layer.linz-aerial', Number((e.target as HTMLInputElement).value)); - }; - - adjustRaster(rasterId: 'osm' | 'linz-aerial', range: number): void { - if (this.props.map.getSource(rasterId) == null) { - this.props.map.addSource(rasterId, { - type: 'raster', - tiles: [getTileServerUrl(rasterId)], - tileSize: 256, - }); - } - - const isLayerMissing = this.props.map.getLayer(rasterId) == null; - if (range === 0) { - if (!isLayerMissing) this.props.map.removeLayer(rasterId); - return; - } - - if (isLayerMissing) { - this.props.map.addLayer({ - id: rasterId, - type: 'raster', - source: rasterId, - minzoom: 0, - maxzoom: 24, - paint: { 'raster-opacity': 0 }, - }); - - // Ensure this raster layers are below the vector layer - const sourceLayerId = `${Config.map.layerId}_source_fill`; - const isSourceLayerEnabled = this.props.map.getLayer(sourceLayerId) != null; - if (isSourceLayerEnabled) { - this.props.map.moveLayer(rasterId, sourceLayerId); - } - } - this.props.map.setPaintProperty(rasterId, 'raster-opacity', range); - } - - togglePurple = (e: Event): void => { - const target = e.target as HTMLInputElement; - this.setPurple(target.checked); - }; - - setPurple(isPurple: boolean): void { - Config.map.setDebug('debug.background', isPurple ? 'magenta' : false); - if (isPurple) document.body.style.backgroundColor = 'magenta'; - else document.body.style.backgroundColor = ''; - } -} - -export function getTileServerUrl(tileServer: 'osm' | 'linz-aerial'): string { - if (tileServer === 'osm') return 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'; - if (tileServer === 'linz-aerial') { - return WindowUrl.toTileUrl(MapOptionType.TileRaster, Config.map.tileMatrix, 'aerial', undefined, null); - } - - throw new Error('Unknown tile server'); } diff --git a/packages/landing/src/config.layer.ts b/packages/landing/src/config.layer.ts new file mode 100644 index 000000000..100165633 --- /dev/null +++ b/packages/landing/src/config.layer.ts @@ -0,0 +1,46 @@ +import { ConfigImagery } from '@basemaps/config/build/config/imagery.js'; +import { ConfigTileSetRaster } from '@basemaps/config/build/config/tile.set.js'; +import { TileMatrixSets } from '@basemaps/geo'; +import { Projection } from '@basemaps/shared/build/proj/projection.js'; + +import { BBoxFeatureCollection } from '@linzjs/geojson'; +import { WindowUrl } from './url.js'; + +export class ConfigData { + static tileSets = new Map>(); + static imagery = new Map>(); + static config: string; + + static getTileSet(tileSetId: string): Promise { + const tileSetUrl = WindowUrl.toConfigUrl(tileSetId); + + let existing = this.tileSets.get(tileSetUrl); + if (existing) return existing; + + existing = fetch(tileSetUrl).then((res) => { + if (res.ok) return res.json(); + return null; + }); + this.tileSets.set(tileSetUrl, existing); + return existing; + } + + static getImagery(tileSetId: string, imageryId: string): Promise { + const imageryUrl = WindowUrl.toConfigImageryUrl(tileSetId, imageryId); + let existing = this.imagery.get(imageryUrl); + if (existing) return existing; + + existing = fetch(imageryUrl).then((res) => { + if (res.ok) return res.json(); + return null; + }); + this.imagery.set(imageryUrl, existing); + return existing; + } + + static getGeoJson(imagery: ConfigImagery): BBoxFeatureCollection | null { + const tileMatrix = TileMatrixSets.find(imagery.tileMatrix); + if (tileMatrix == null) return null; + return Projection.get(tileMatrix).toGeoJson(imagery.files); + } +} diff --git a/packages/landing/src/debug.map.ts b/packages/landing/src/debug.map.ts new file mode 100644 index 000000000..12399ca70 --- /dev/null +++ b/packages/landing/src/debug.map.ts @@ -0,0 +1,181 @@ +import { Config } from './config.js'; +import { MapOptionType, WindowUrl } from './url.js'; +import { StyleSpecification } from 'maplibre-gl'; +import { GoogleTms } from '@basemaps/geo'; +import { projectGeoJson } from './tile.matrix.js'; +import { ConfigImagery } from '@basemaps/config/build/config/imagery.js'; +import { ConfigData } from './config.layer.js'; + +export class DebugMap { + _layerLoading: Map> = new Map(); + loadSourceLayer(map: maplibregl.Map, layerId: string, imagery: ConfigImagery, type: 'source' | 'cog'): Promise { + const layerKey = `${layerId}-${type}`; + let existing = this._layerLoading.get(layerKey); + if (existing == null) { + existing = this._loadSourceLayer(map, layerId, imagery, type); + this._layerLoading.set(layerKey, existing); + } + return existing; + } + + async _loadSourceLayer( + map: maplibregl.Map, + layerId: string, + imagery: ConfigImagery, + type: 'source' | 'cog', + ): Promise { + const sourceId = `${layerId}_${type}`; + const layerFillId = `${sourceId}_fill`; + if (map.getLayer(layerFillId) != null) return; + + const sourceUri = WindowUrl.toImageryUrl(imagery.id, type === 'source' ? 'source.geojson' : 'covering.geojson'); + + const res = await fetch(sourceUri); + let data; + if (res.ok) { + data = await res.json(); + } else { + data = ConfigData.getGeoJson(imagery); + } + if (Config.map.tileMatrix.projection !== GoogleTms.projection) projectGeoJson(data, Config.map.tileMatrix); + + let id = 0; + // Ensure there is a id on each feature + for (const f of data.features) f.id = id++; + + map.addSource(sourceId, { type: 'geojson', data }); + } + + _styleJson: Promise | null = null; + get styleJson(): Promise { + if (this._styleJson == null) { + this._styleJson = fetch( + WindowUrl.toTileUrl(MapOptionType.Style, Config.map.tileMatrix, 'topographic', 'topographic', null), + ).then((f) => f.json()); + } + return this._styleJson; + } + + adjustTopographic = (e: Event): void => { + const slider = e.target as HTMLInputElement; + Config.map.setDebug('debug.layer.linz-topographic', Number(slider.value)); + }; + + async adjustVector(map: maplibregl.Map, value: number): Promise { + const styleJson = await this.styleJson; + + const hasTopographic = map.getSource('LINZ Basemaps'); + if (hasTopographic == null) { + if (value === 0) return; // Going to remove it anyway so just abort early + const source = styleJson.sources?.['LINZ Basemaps']; + if (source == null) return; + map.addSource('LINZ Basemaps', source); + map.setStyle({ ...map.getStyle(), glyphs: styleJson.glyphs, sprite: styleJson.sprite }); + // Setting glyphs/sprites forces a full map refresh, wait for the refresh before adjusting the style + map.once('style.load', () => this.adjustVector(map, value)); + return; + } + + const layers = styleJson.layers?.filter((f) => f.type !== 'background' && f.source === 'LINZ Basemaps') ?? []; + + // Do not hide topographic layers when trying to inspect the topographic layer + if (Config.map.layerId === 'topographic') return; + // Force all the layers to be invisible to start, otherwise the map will "flash" on then off + for (const layer of layers) { + const paint = (layer.paint ?? {}) as Record; + if (layer.type === 'symbol') { + paint['icon-opacity'] = 0; + paint['text-opacity'] = 0; + } else { + paint[`${layer.type}-opacity`] = 0; + } + layer.paint = paint; + } + + if (value === 0) { + for (const layer of layers) { + if (map.getLayer(layer.id) == null) continue; + map.removeLayer(layer.id); + } + return; + } + + // Ensure all the layers are loaded before styling + if (map.getLayer(layers[0].id) == null) { + if (value === 0) return; + for (const layer of layers) map.addLayer(layer); + } + + for (const layer of layers) { + if (map.getLayer(layer.id) == null) continue; + if (layer.type === 'symbol') { + map.setPaintProperty(layer.id, `icon-opacity`, value); + map.setPaintProperty(layer.id, `text-opacity`, value); + } else { + map.setPaintProperty(layer.id, `${layer.type}-opacity`, value); + } + } + } + + adjustOsm = (e: Event): void => { + Config.map.setDebug('debug.layer.osm', Number((e.target as HTMLInputElement).value)); + }; + adjustLinzAerial = (e: Event): void => { + Config.map.setDebug('debug.layer.linz-aerial', Number((e.target as HTMLInputElement).value)); + }; + + adjustRaster(map: maplibregl.Map, rasterId: 'osm' | 'linz-aerial', range: number): void { + if (map.getSource(rasterId) == null) { + map.addSource(rasterId, { + type: 'raster', + tiles: [this.getTileServerUrl(rasterId)], + tileSize: 256, + }); + } + + const isLayerMissing = map.getLayer(rasterId) == null; + if (range === 0) { + if (!isLayerMissing) map.removeLayer(rasterId); + return; + } + + if (isLayerMissing) { + map.addLayer({ + id: rasterId, + type: 'raster', + source: rasterId, + minzoom: 0, + maxzoom: 24, + paint: { 'raster-opacity': 0 }, + }); + + // Ensure this raster layers are below the vector layer + const sourceLayerId = `${Config.map.layerId}_source_fill`; + const isSourceLayerEnabled = map.getLayer(sourceLayerId) != null; + if (isSourceLayerEnabled) { + map.moveLayer(rasterId, sourceLayerId); + } + } + map.setPaintProperty(rasterId, 'raster-opacity', range); + } + + togglePurple = (e: Event): void => { + const target = e.target as HTMLInputElement; + this.setPurple(target.checked); + }; + + setPurple(isPurple: boolean): void { + Config.map.setDebug('debug.background', isPurple ? 'magenta' : false); + if (isPurple) document.body.style.backgroundColor = 'magenta'; + else document.body.style.backgroundColor = ''; + } + + getTileServerUrl(tileServer: 'osm' | 'linz-aerial'): string { + if (tileServer === 'osm') return 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'; + if (tileServer === 'linz-aerial') { + return WindowUrl.toTileUrl(MapOptionType.TileRaster, Config.map.tileMatrix, 'aerial', undefined, null); + } + + throw new Error('Unknown tile server'); + } +} diff --git a/packages/landing/src/url.ts b/packages/landing/src/url.ts index 183678b2e..f5c81c712 100644 --- a/packages/landing/src/url.ts +++ b/packages/landing/src/url.ts @@ -122,4 +122,14 @@ export const WindowUrl = { throw new Error('Unknown url type: ' + urlType); }, + + toConfigUrl(layerId: string, config: string | null = Config.map.config): string { + const query = toQueryString({ api: Config.ApiKey, config }); + return `${this.baseUrl()}/v1/config/${layerId}.json${query}`; + }, + + toConfigImageryUrl(layerId: string, imageryId: string, config: string | null = Config.map.config): string { + const query = toQueryString({ api: Config.ApiKey, config }); + return `${this.baseUrl()}/v1/config/${layerId}/${imageryId}.json${query}`; + }, }; diff --git a/packages/landing/tsconfig.json b/packages/landing/tsconfig.json index aadd69bc4..4d815f46b 100644 --- a/packages/landing/tsconfig.json +++ b/packages/landing/tsconfig.json @@ -8,5 +8,5 @@ "lib": ["DOM"] }, "include": ["src/**/*"], - "references": [{ "path": "../geo" }, { "path": "../shared" }] + "references": [{ "path": "../config" }, { "path": "../geo" }, { "path": "../shared" }] }