From 78faa14bcf05ef7854fd29a44fb8014c84bd2421 Mon Sep 17 00:00:00 2001 From: Wentao Kuang Date: Wed, 31 Aug 2022 11:32:16 +1200 Subject: [PATCH 1/7] Relocate debug map adjustables into seperate location. --- packages/landing/src/components/debug.tsx | 276 +++++----------------- packages/landing/src/debug.map.ts | 175 ++++++++++++++ 2 files changed, 228 insertions(+), 223 deletions(-) create mode 100644 packages/landing/src/debug.map.ts diff --git a/packages/landing/src/components/debug.tsx b/packages/landing/src/components/debug.tsx index ea71d759f..337105b64 100644 --- a/packages/landing/src/components/debug.tsx +++ b/packages/landing/src/components/debug.tsx @@ -1,12 +1,10 @@ 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 { 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( @@ -35,6 +33,8 @@ export class Debug extends Component< featureSourceName: string | undefined; } > { + debugMap = new DebugMap(); + componentDidMount(): void { this.waitForMap(); } @@ -75,14 +75,28 @@ 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']); + 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'); + }; + render(): ComponentChild { if (Config.map.debug['debug.screenshot']) return null; return ( @@ -114,7 +128,7 @@ export class Debug extends Component< @@ -170,19 +184,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,7 +264,7 @@ export class Debug extends Component< const color = type === 'source' ? '#ff00ff' : '#ff0000'; - this.loadSourceLayer(layerId, type).then(() => { + this.debugMap.loadSourceLayer(this.props.map, layerId, type).then(() => { if (map.getLayer(layerLineId) != null) return; // Fill is needed to make the mouse move work even though it has opacity 0 @@ -260,205 +291,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/debug.map.ts b/packages/landing/src/debug.map.ts new file mode 100644 index 000000000..ed2c7b0c3 --- /dev/null +++ b/packages/landing/src/debug.map.ts @@ -0,0 +1,175 @@ +import { Config } from './config.js'; +import { MapOptionType, WindowUrl } from './url.js'; +import { BBoxFeatureCollection } from '@linzjs/geojson'; +import { StyleSpecification } from 'maplibre-gl'; +import { GoogleTms } from '@basemaps/geo'; +import { projectGeoJson } from './tile.matrix.js'; + +export class DebugMap { + _layerLoading: Map> = new Map(); + loadSourceLayer(map: maplibregl.Map, layerId: string, type: 'source' | 'cog'): Promise { + const layerKey = `${layerId}-${type}`; + let existing = this._layerLoading.get(layerKey); + if (existing == null) { + existing = this._loadSourceLayer(map, layerId, type); + this._layerLoading.set(layerKey, existing); + } + return existing; + } + + async _loadSourceLayer(map: maplibregl.Map, layerId: string, type: 'source' | 'cog'): Promise { + 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 }); + } + + _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'); + } +} From 0570302b321009e3969a8daf566e70cc5799c3a4 Mon Sep 17 00:00:00 2001 From: Wentao Kuang Date: Sun, 4 Sep 2022 16:35:28 +1200 Subject: [PATCH 2/7] Load config into landing debug page --- packages/lambda-tiler/src/index.ts | 5 ++ packages/lambda-tiler/src/routes/config.ts | 83 ++++++++++++++++++++++ packages/landing/package.json | 1 + packages/landing/src/components/debug.tsx | 29 +++++++- packages/landing/src/config.layer.ts | 43 +++++++++++ packages/landing/src/url.ts | 10 +++ packages/landing/tsconfig.json | 2 +- 7 files changed, 169 insertions(+), 4 deletions(-) create mode 100644 packages/lambda-tiler/src/routes/config.ts create mode 100644 packages/landing/src/config.layer.ts 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 337105b64..ab18d8206 100644 --- a/packages/landing/src/components/debug.tsx +++ b/packages/landing/src/components/debug.tsx @@ -1,7 +1,9 @@ +import { ConfigImagery, ConfigTileSetRaster } from '@basemaps/config'; import { GoogleTms } from '@basemaps/geo'; 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 { DebugMap } from '../debug.map.js'; import { WindowUrl } from '../url.js'; @@ -31,6 +33,8 @@ export class Debug extends Component< featureCogName: string | undefined; featureSourceId: string | number | undefined; featureSourceName: string | undefined; + tileSet: ConfigTileSetRaster | null; + imagery: ConfigImagery | null; } > { debugMap = new DebugMap(); @@ -97,6 +101,26 @@ export class Debug extends Component< 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 }); + + 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 }, () => this.updateFromConfig()); + }); + }); + } + render(): ComponentChild { if (Config.map.debug['debug.screenshot']) return null; return ( @@ -161,9 +185,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 (
diff --git a/packages/landing/src/config.layer.ts b/packages/landing/src/config.layer.ts new file mode 100644 index 000000000..3ae47968f --- /dev/null +++ b/packages/landing/src/config.layer.ts @@ -0,0 +1,43 @@ +import { ConfigImagery, ConfigTileSetRaster } from '@basemaps/config'; +import { TileMatrixSets } from '@basemaps/geo'; +import { Projection } from '@basemaps/shared'; +import { BBoxFeatureCollection } from '@linzjs/geojson'; +import { WindowUrl } from './url.js'; + +export class ConfigData { + static tileSets = new Map>(); + static imagery = new Map>(); + + 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/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" }] } From e170430745f6580e61612feab930ce94ae72e1fd Mon Sep 17 00:00:00 2001 From: Wentao Kuang Date: Thu, 8 Sep 2022 11:49:53 +1200 Subject: [PATCH 3/7] Update debug page to load imagery by config and get cog by imagery --- packages/landing/package.json | 1 - packages/landing/src/components/debug.tsx | 19 +++++++----- packages/landing/src/config.layer.ts | 7 +++-- packages/landing/src/debug.map.ts | 36 ++++++++++++++++++----- 4 files changed, 45 insertions(+), 18 deletions(-) diff --git a/packages/landing/package.json b/packages/landing/package.json index 20be23d25..226b5a373 100644 --- a/packages/landing/package.json +++ b/packages/landing/package.json @@ -29,7 +29,6 @@ ], "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 ab18d8206..1687bb47b 100644 --- a/packages/landing/src/components/debug.tsx +++ b/packages/landing/src/components/debug.tsx @@ -1,4 +1,5 @@ -import { ConfigImagery, ConfigTileSetRaster } from '@basemaps/config'; +import { ConfigImagery } from '@basemaps/config/src/config/imagery.js'; +import { ConfigTileSetRaster } from '@basemaps/config/src/config/tile.set.js'; import { GoogleTms } from '@basemaps/geo'; import { Component, ComponentChild, Fragment } from 'preact'; import { Attributions } from '../attribution.js'; @@ -35,6 +36,7 @@ export class Debug extends Component< featureSourceName: string | undefined; tileSet: ConfigTileSetRaster | null; imagery: ConfigImagery | null; + config: string | null; } > { debugMap = new DebugMap(); @@ -79,6 +81,10 @@ export class Debug extends Component< }; updateFromConfig(): void { + 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']); @@ -116,7 +122,7 @@ export class Debug extends Component< if (imageryId == null) return; return ConfigData.getImagery(tileSetId, imageryId).then((imagery) => { - this.setState({ ...this.state, imagery }, () => this.updateFromConfig()); + this.setState({ ...this.state, imagery }); }); }); } @@ -160,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 ( @@ -287,9 +292,9 @@ export class Debug extends Component< const color = type === 'source' ? '#ff00ff' : '#ff0000'; - this.debugMap.loadSourceLayer(this.props.map, 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, diff --git a/packages/landing/src/config.layer.ts b/packages/landing/src/config.layer.ts index 3ae47968f..058a4fbff 100644 --- a/packages/landing/src/config.layer.ts +++ b/packages/landing/src/config.layer.ts @@ -1,12 +1,15 @@ -import { ConfigImagery, ConfigTileSetRaster } from '@basemaps/config'; +import { ConfigImagery } from '@basemaps/config/src/config/imagery.js'; +import { ConfigTileSetRaster } from '@basemaps/config/src/config/tile.set.js'; import { TileMatrixSets } from '@basemaps/geo'; -import { Projection } from '@basemaps/shared'; +import { Projection } from '@basemaps/shared/src/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); diff --git a/packages/landing/src/debug.map.ts b/packages/landing/src/debug.map.ts index ed2c7b0c3..ae33f6679 100644 --- a/packages/landing/src/debug.map.ts +++ b/packages/landing/src/debug.map.ts @@ -1,31 +1,36 @@ import { Config } from './config.js'; import { MapOptionType, WindowUrl } from './url.js'; -import { BBoxFeatureCollection } from '@linzjs/geojson'; import { StyleSpecification } from 'maplibre-gl'; import { GoogleTms } from '@basemaps/geo'; import { projectGeoJson } from './tile.matrix.js'; +import { ConfigImagery } from '@basemaps/config/src/config/imagery.js'; +import { ConfigData } from './config.layer.js'; +import { BBoxFeatureCollection } from '@linzjs/geojson'; export class DebugMap { _layerLoading: Map> = new Map(); - loadSourceLayer(map: maplibregl.Map, layerId: string, type: 'source' | 'cog'): Promise { + 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, type); + existing = this._loadSourceLayer(map, layerId, imagery, type); + if (existing == null && type === 'cog') existing = this._loadCogLayer(map, layerId, imagery); this._layerLoading.set(layerKey, existing); } return existing; } - async _loadSourceLayer(map: maplibregl.Map, layerId: string, type: 'source' | 'cog'): Promise { + 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( - `im_${layerId}`, - type === 'source' ? 'source.geojson' : 'covering.geojson', - ); + const sourceUri = WindowUrl.toImageryUrl(imagery.id, type === 'source' ? 'source.geojson' : 'covering.geojson'); const res = await fetch(sourceUri); if (!res.ok) return; @@ -40,6 +45,21 @@ export class DebugMap { map.addSource(sourceId, { type: 'geojson', data }); } + async _loadCogLayer(map: maplibregl.Map, layerId: string, imagery: ConfigImagery): Promise { + const sourceId = `${layerId}_cog`; + const layerFillId = `${sourceId}_fill`; + if (map.getLayer(layerFillId) != null) return; + + const data = ConfigData.getGeoJson(imagery); + if (data == null) return; + 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) { From 42a9b529f5d9ed3b6f19a2892c5101dec89c158b Mon Sep 17 00:00:00 2001 From: Wentao Kuang Date: Thu, 8 Sep 2022 12:20:01 +1200 Subject: [PATCH 4/7] Fix the missing await. --- packages/landing/package.json | 1 + packages/landing/src/debug.map.ts | 17 +++++++---------- 2 files changed, 8 insertions(+), 10 deletions(-) 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/debug.map.ts b/packages/landing/src/debug.map.ts index ae33f6679..f3bfd8bb5 100644 --- a/packages/landing/src/debug.map.ts +++ b/packages/landing/src/debug.map.ts @@ -13,24 +13,21 @@ export class DebugMap { const layerKey = `${layerId}-${type}`; let existing = this._layerLoading.get(layerKey); if (existing == null) { - existing = this._loadSourceLayer(map, layerId, imagery, type); - if (existing == null && type === 'cog') existing = this._loadCogLayer(map, layerId, imagery); + if (type === 'cog') existing = this._loadCogLayer(map, layerId, imagery); + else { + existing = this._loadSourceLayer(map, layerId, imagery); + } this._layerLoading.set(layerKey, existing); } return existing; } - async _loadSourceLayer( - map: maplibregl.Map, - layerId: string, - imagery: ConfigImagery, - type: 'source' | 'cog', - ): Promise { - const sourceId = `${layerId}_${type}`; + async _loadSourceLayer(map: maplibregl.Map, layerId: string, imagery: ConfigImagery): Promise { + const sourceId = `${layerId}_source`; const layerFillId = `${sourceId}_fill`; if (map.getLayer(layerFillId) != null) return; - const sourceUri = WindowUrl.toImageryUrl(imagery.id, type === 'source' ? 'source.geojson' : 'covering.geojson'); + const sourceUri = WindowUrl.toImageryUrl(imagery.id, 'source.geojson'); const res = await fetch(sourceUri); if (!res.ok) return; From 13cab7d4f35b005bc83efed247e6048cde97536c Mon Sep 17 00:00:00 2001 From: Wentao Kuang Date: Thu, 8 Sep 2022 15:24:28 +1200 Subject: [PATCH 5/7] Load cog and source from imagery config --- packages/landing/src/components/debug.tsx | 4 +-- packages/landing/src/debug.map.ts | 40 +++++++++-------------- 2 files changed, 17 insertions(+), 27 deletions(-) diff --git a/packages/landing/src/components/debug.tsx b/packages/landing/src/components/debug.tsx index 1687bb47b..e02641c80 100644 --- a/packages/landing/src/components/debug.tsx +++ b/packages/landing/src/components/debug.tsx @@ -112,7 +112,7 @@ export class Debug extends Component< const tileSetId = Config.map.layerId; if (this.state.tileSet?.id === tileSetId) return; return ConfigData.getTileSet(tileSetId).then((tileSet) => { - this.setState({ ...this.state, tileSet }); + this.setState({ ...this.state, tileSet, config: Config.map.config }); if (tileSet == null) return; if (tileSet.layers.length !== 1) return; @@ -122,7 +122,7 @@ export class Debug extends Component< if (imageryId == null) return; return ConfigData.getImagery(tileSetId, imageryId).then((imagery) => { - this.setState({ ...this.state, imagery }); + this.setState({ ...this.state, imagery, config: Config.map.config }); }); }); } diff --git a/packages/landing/src/debug.map.ts b/packages/landing/src/debug.map.ts index f3bfd8bb5..e62e0999f 100644 --- a/packages/landing/src/debug.map.ts +++ b/packages/landing/src/debug.map.ts @@ -13,26 +13,31 @@ export class DebugMap { const layerKey = `${layerId}-${type}`; let existing = this._layerLoading.get(layerKey); if (existing == null) { - if (type === 'cog') existing = this._loadCogLayer(map, layerId, imagery); - else { - existing = this._loadSourceLayer(map, layerId, imagery); - } + existing = this._loadSourceLayer(map, layerId, imagery, type); this._layerLoading.set(layerKey, existing); } return existing; } - async _loadSourceLayer(map: maplibregl.Map, layerId: string, imagery: ConfigImagery): Promise { - const sourceId = `${layerId}_source`; + 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, 'source.geojson'); + const sourceUri = WindowUrl.toImageryUrl(imagery.id, type === 'source' ? 'source.geojson' : 'covering.geojson'); const res = await fetch(sourceUri); - if (!res.ok) return; - - const data: BBoxFeatureCollection = await res.json(); + 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; @@ -42,21 +47,6 @@ export class DebugMap { map.addSource(sourceId, { type: 'geojson', data }); } - async _loadCogLayer(map: maplibregl.Map, layerId: string, imagery: ConfigImagery): Promise { - const sourceId = `${layerId}_cog`; - const layerFillId = `${sourceId}_fill`; - if (map.getLayer(layerFillId) != null) return; - - const data = ConfigData.getGeoJson(imagery); - if (data == null) return; - 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) { From 5fb525fb736d9ca5a94456fd58f97e9aa088ec92 Mon Sep 17 00:00:00 2001 From: Wentao Kuang Date: Thu, 8 Sep 2022 15:25:19 +1200 Subject: [PATCH 6/7] fix linting --- packages/landing/src/debug.map.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/landing/src/debug.map.ts b/packages/landing/src/debug.map.ts index e62e0999f..9d5e2e146 100644 --- a/packages/landing/src/debug.map.ts +++ b/packages/landing/src/debug.map.ts @@ -5,7 +5,6 @@ import { GoogleTms } from '@basemaps/geo'; import { projectGeoJson } from './tile.matrix.js'; import { ConfigImagery } from '@basemaps/config/src/config/imagery.js'; import { ConfigData } from './config.layer.js'; -import { BBoxFeatureCollection } from '@linzjs/geojson'; export class DebugMap { _layerLoading: Map> = new Map(); From 6eba77a4b53e4003e7dfe4b4a006bb5ac42d264e Mon Sep 17 00:00:00 2001 From: Wentao Kuang Date: Mon, 12 Sep 2022 13:15:19 +1200 Subject: [PATCH 7/7] Fix the import --- packages/landing/src/components/debug.tsx | 4 ++-- packages/landing/src/config.layer.ts | 6 +++--- packages/landing/src/debug.map.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/landing/src/components/debug.tsx b/packages/landing/src/components/debug.tsx index e02641c80..9652903f6 100644 --- a/packages/landing/src/components/debug.tsx +++ b/packages/landing/src/components/debug.tsx @@ -1,5 +1,5 @@ -import { ConfigImagery } from '@basemaps/config/src/config/imagery.js'; -import { ConfigTileSetRaster } from '@basemaps/config/src/config/tile.set.js'; +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 { Component, ComponentChild, Fragment } from 'preact'; import { Attributions } from '../attribution.js'; diff --git a/packages/landing/src/config.layer.ts b/packages/landing/src/config.layer.ts index 058a4fbff..100165633 100644 --- a/packages/landing/src/config.layer.ts +++ b/packages/landing/src/config.layer.ts @@ -1,7 +1,7 @@ -import { ConfigImagery } from '@basemaps/config/src/config/imagery.js'; -import { ConfigTileSetRaster } from '@basemaps/config/src/config/tile.set.js'; +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/src/proj/projection.js'; +import { Projection } from '@basemaps/shared/build/proj/projection.js'; import { BBoxFeatureCollection } from '@linzjs/geojson'; import { WindowUrl } from './url.js'; diff --git a/packages/landing/src/debug.map.ts b/packages/landing/src/debug.map.ts index 9d5e2e146..12399ca70 100644 --- a/packages/landing/src/debug.map.ts +++ b/packages/landing/src/debug.map.ts @@ -3,7 +3,7 @@ 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/src/config/imagery.js'; +import { ConfigImagery } from '@basemaps/config/build/config/imagery.js'; import { ConfigData } from './config.layer.js'; export class DebugMap {