From 51233521a0dc1e004e676d33db0a40b1b9567343 Mon Sep 17 00:00:00 2001 From: Greg Date: Wed, 15 Jan 2025 14:37:21 +0100 Subject: [PATCH 1/4] save --- packages/base/src/mainview/mainView.tsx | 143 ++++++++++++++++++++++-- 1 file changed, 132 insertions(+), 11 deletions(-) diff --git a/packages/base/src/mainview/mainView.tsx b/packages/base/src/mainview/mainView.tsx index 134cd3f37..f3a0fef84 100644 --- a/packages/base/src/mainview/mainView.tsx +++ b/packages/base/src/mainview/mainView.tsx @@ -40,13 +40,19 @@ import { VectorTile as VectorTileLayer, WebGLTile as WebGlTileLayer } from 'ol/layer'; -import BaseLayer from 'ol/layer/Base'; +// import BaseLayer from 'ol/layer/Base'; import TileLayer from 'ol/layer/Tile'; -import { fromLonLat, toLonLat, transformExtent } from 'ol/proj'; +import { + fromLonLat, + get as olGetProj, + toLonLat, + transformExtent +} from 'ol/proj'; import Feature from 'ol/render/Feature'; import { GeoTIFF as GeoTIFFSource, ImageTile as ImageTileSource, + Source, Vector as VectorSource, VectorTile as VectorTileSource, XYZ as XYZSource @@ -96,8 +102,35 @@ export class MainView extends React.Component { constructor(props: IProps) { super(props); - proj4.defs(Array.from(proj4list)); - register(proj4); + // console.log('proj4list["EPSG:22300"]', proj4list['EPSG:22300']); + // const proj4Values: [string, string][] = Object.values(proj4list); + + // const proj4Filtered = proj4Values.filter(([_, second]) => second !== ''); + + // const excludedCodes = [ + // 'EPSG:4326', + // 'EPSG:4269', + // 'EPSG:32601', + // 'EPSG:32701', + // 'EPSG:32602', + // 'EPSG:32702', + // 'EPSG:32603', + // 'EPSG:32703', + // 'EPSG:32604', + // 'EPSG:32704', + // 'EPSG:32605', + // 'EPSG:32705', + // 'EPSG:32606', + // 'EPSG:32706', + // 'EPSG:32607', + // 'EPSG:3785', + // 'EPSG:5514', + // 'EPSG:27700', + // 'EPSG:28992' + // ]; + // proj4.defs(proj4Filtered); + + // register(proj4); this._mainViewModel = this.props.viewModel; this._mainViewModel.viewSettingChanged.connect(this._onViewChanged, this); @@ -435,7 +468,8 @@ export class MainView extends React.Component { source: IJGISSource, layerId?: string ): Promise { - let newSource; + let newSource: Source; + this._ready = false; switch (source.type) { case 'RasterSource': { @@ -620,9 +654,54 @@ export class MainView extends React.Component { } } - newSource.set('id', id); - // _sources is a list of OpenLayers sources - this._sources[id] = newSource; + //@ts-expect-error isidsd + this.waitForSourceReady(newSource) + .then(() => { + console.log('The source is ready!'); + newSource.set('id', id); + // const sp = newSource.getProjection(); + // const c = sp?.getCode(); + // console.log('source = id', id); + // console.log('source = sp', sp); + // _sources is a list of OpenLayers sources + this.addProj(newSource); + this._sources[id] = newSource; + + // const l1 = olGetProj(c); + // console.log('l1', l1, c); + // if (!l1) { + // proj4.defs([proj4list[c]]); + // register(proj4); + // } + // const l2 = olGetProj(c); + // console.log('l2', l2); + this._ready = true; + }) + .catch(error => { + console.error('An error occurred:', error.message); + }); + } + + waitForSourceReady(source: Source) { + return new Promise((resolve, reject) => { + const checkState = () => { + const state = source.getState(); + console.log(`Source state: ${state}`); + if (state === 'ready') { + source.un('change', checkState); // Stop listening once it's ready + resolve(); // Resolve the promise + } else if (state === 'error') { + source.un('change', checkState); // Stop listening on error + reject(new Error('Source failed to load.')); + } + }; + + // Listen for state changes + source.on('change', checkState); + + // Check the state immediately in case it's already 'ready' + checkState(); + }); } private computeSourceUrl(source: IJGISSource): string { @@ -749,7 +828,7 @@ export class MainView extends React.Component { private async _buildMapLayer( id: string, layer: IJGISLayer - ): Promise { + ): Promise { const sourceId = layer.parameters?.source; const source = this._model.sharedModel.getLayerSource(sourceId); if (!source) { @@ -866,10 +945,52 @@ export class MainView extends React.Component { const newMapLayer = await this._buildMapLayer(id, layer); if (newMapLayer !== undefined) { + await this.waitForReady(); + + console.log('calling insert'); this._Map.getLayers().insertAt(index, newMapLayer); } } + addProj(newMapLayer: Source) { + // const s = newMapLayer.getSource(); + // console.log('josn shit', JSON.parse(JSON.stringify(s))); + const spp = newMapLayer.getProjection(); + const c = spp?.getCode(); + console.log('addProj projection', spp); + const test = olGetProj('EPSG:3857'); + console.log('test projection', test); + const l1 = olGetProj(c); + console.log('added projection pre', l1); + if (!l1) { + proj4.defs([proj4list[c]]); + register(proj4); + } + const l2 = olGetProj(c); + console.log('added projection post', l2); + } + + /** + * Waits until the `ready` boolean in the parent class is `true`. + */ + private waitForReady(): Promise { + return new Promise(resolve => { + const checkReady = () => { + if (this._ready) { + // If ready, resolve the promise + console.log('ready in waitForReady'); + resolve(); + } else { + // If not ready, check again after a short delay + console.log('setting timeout in waitForReady'); + setTimeout(checkReady, 50); // Poll every 50ms + } + }; + + checkReady(); + }); + } + vectorLayerStyleRuleBuilder = (layer: IJGISLayer) => { const layerParams = layer.parameters; if (!layerParams) { @@ -996,7 +1117,7 @@ export class MainView extends React.Component { async updateLayer( id: string, layer: IJGISLayer, - mapLayer: BaseLayer + mapLayer: Layer ): Promise { const sourceId = layer.parameters?.source; const source = this._model.sharedModel.getLayerSource(sourceId); @@ -1226,7 +1347,7 @@ export class MainView extends React.Component { return this._Map .getLayers() .getArray() - .find(layer => layer.get('id') === id); + .find(layer => layer.get('id') === id) as Layer; } /** From 98cca2cf6bd52c24609673c6d5c8c0259b755632 Mon Sep 17 00:00:00 2001 From: Greg Date: Thu, 16 Jan 2025 10:01:33 +0100 Subject: [PATCH 2/4] Wait for layers to be ready and add projections as needed --- packages/base/src/mainview/mainView.tsx | 191 +++++++++--------------- 1 file changed, 67 insertions(+), 124 deletions(-) diff --git a/packages/base/src/mainview/mainView.tsx b/packages/base/src/mainview/mainView.tsx index f3a0fef84..44c56d74f 100644 --- a/packages/base/src/mainview/mainView.tsx +++ b/packages/base/src/mainview/mainView.tsx @@ -40,11 +40,10 @@ import { VectorTile as VectorTileLayer, WebGLTile as WebGlTileLayer } from 'ol/layer'; -// import BaseLayer from 'ol/layer/Base'; import TileLayer from 'ol/layer/Tile'; import { fromLonLat, - get as olGetProj, + get as getRegisteredProjection, toLonLat, transformExtent } from 'ol/proj'; @@ -52,7 +51,6 @@ import Feature from 'ol/render/Feature'; import { GeoTIFF as GeoTIFFSource, ImageTile as ImageTileSource, - Source, Vector as VectorSource, VectorTile as VectorTileSource, XYZ as XYZSource @@ -102,36 +100,6 @@ export class MainView extends React.Component { constructor(props: IProps) { super(props); - // console.log('proj4list["EPSG:22300"]', proj4list['EPSG:22300']); - // const proj4Values: [string, string][] = Object.values(proj4list); - - // const proj4Filtered = proj4Values.filter(([_, second]) => second !== ''); - - // const excludedCodes = [ - // 'EPSG:4326', - // 'EPSG:4269', - // 'EPSG:32601', - // 'EPSG:32701', - // 'EPSG:32602', - // 'EPSG:32702', - // 'EPSG:32603', - // 'EPSG:32703', - // 'EPSG:32604', - // 'EPSG:32704', - // 'EPSG:32605', - // 'EPSG:32705', - // 'EPSG:32606', - // 'EPSG:32706', - // 'EPSG:32607', - // 'EPSG:3785', - // 'EPSG:5514', - // 'EPSG:27700', - // 'EPSG:28992' - // ]; - // proj4.defs(proj4Filtered); - - // register(proj4); - this._mainViewModel = this.props.viewModel; this._mainViewModel.viewSettingChanged.connect(this._onViewChanged, this); @@ -468,8 +436,7 @@ export class MainView extends React.Component { source: IJGISSource, layerId?: string ): Promise { - let newSource: Source; - this._ready = false; + let newSource; switch (source.type) { case 'RasterSource': { @@ -654,54 +621,9 @@ export class MainView extends React.Component { } } - //@ts-expect-error isidsd - this.waitForSourceReady(newSource) - .then(() => { - console.log('The source is ready!'); - newSource.set('id', id); - // const sp = newSource.getProjection(); - // const c = sp?.getCode(); - // console.log('source = id', id); - // console.log('source = sp', sp); - // _sources is a list of OpenLayers sources - this.addProj(newSource); - this._sources[id] = newSource; - - // const l1 = olGetProj(c); - // console.log('l1', l1, c); - // if (!l1) { - // proj4.defs([proj4list[c]]); - // register(proj4); - // } - // const l2 = olGetProj(c); - // console.log('l2', l2); - this._ready = true; - }) - .catch(error => { - console.error('An error occurred:', error.message); - }); - } - - waitForSourceReady(source: Source) { - return new Promise((resolve, reject) => { - const checkState = () => { - const state = source.getState(); - console.log(`Source state: ${state}`); - if (state === 'ready') { - source.un('change', checkState); // Stop listening once it's ready - resolve(); // Resolve the promise - } else if (state === 'error') { - source.un('change', checkState); // Stop listening on error - reject(new Error('Source failed to load.')); - } - }; - - // Listen for state changes - source.on('change', checkState); - - // Check the state immediately in case it's already 'ready' - checkState(); - }); + newSource.set('id', id); + // _sources is a list of OpenLayers sources + this._sources[id] = newSource; } private computeSourceUrl(source: IJGISSource): string { @@ -839,6 +761,11 @@ export class MainView extends React.Component { await this.addSource(sourceId, source, id); } + if (this._layerCounter === -1) { + this._layerCounter = 0; + } + this._layerCounter++; + let newMapLayer; let layerParameters; @@ -921,12 +848,25 @@ export class MainView extends React.Component { } } + await this._waitForSourceReady(newMapLayer); + // OpenLayers doesn't have name/id field so add it newMapLayer.set('id', id); // we need to keep track of which source has which layers this._sourceToLayerMap.set(layerParameters.source, id); + const sourceProjection = newMapLayer.getSource().getProjection(); + const projectionCode = sourceProjection?.getCode(); + const isProjectionRegistered = getRegisteredProjection(projectionCode); + + if (!isProjectionRegistered) { + proj4.defs([proj4list[projectionCode]]); + register(proj4); + } + + this._layerCounter--; + return newMapLayer; } @@ -945,52 +885,12 @@ export class MainView extends React.Component { const newMapLayer = await this._buildMapLayer(id, layer); if (newMapLayer !== undefined) { - await this.waitForReady(); + await this._waitForReady(); - console.log('calling insert'); this._Map.getLayers().insertAt(index, newMapLayer); } } - addProj(newMapLayer: Source) { - // const s = newMapLayer.getSource(); - // console.log('josn shit', JSON.parse(JSON.stringify(s))); - const spp = newMapLayer.getProjection(); - const c = spp?.getCode(); - console.log('addProj projection', spp); - const test = olGetProj('EPSG:3857'); - console.log('test projection', test); - const l1 = olGetProj(c); - console.log('added projection pre', l1); - if (!l1) { - proj4.defs([proj4list[c]]); - register(proj4); - } - const l2 = olGetProj(c); - console.log('added projection post', l2); - } - - /** - * Waits until the `ready` boolean in the parent class is `true`. - */ - private waitForReady(): Promise { - return new Promise(resolve => { - const checkReady = () => { - if (this._ready) { - // If ready, resolve the promise - console.log('ready in waitForReady'); - resolve(); - } else { - // If not ready, check again after a short delay - console.log('setting timeout in waitForReady'); - setTimeout(checkReady, 50); // Poll every 50ms - } - }; - - checkReady(); - }); - } - vectorLayerStyleRuleBuilder = (layer: IJGISLayer) => { const layerParams = layer.parameters; if (!layerParams) { @@ -1178,6 +1078,48 @@ export class MainView extends React.Component { } } + /** + * Wait for all layers to be loaded. + */ + private _waitForReady(): Promise { + return new Promise(resolve => { + const checkReady = () => { + if (this._layerCounter === 0) { + resolve(); + } else { + setTimeout(checkReady, 50); + } + }; + + checkReady(); + }); + } + + /** + * Wait for a layers source state to be 'ready' + * @param layer The Layer to check + */ + private _waitForSourceReady(layer: Layer) { + return new Promise((resolve, reject) => { + const checkState = () => { + const state = layer.getSourceState(); + if (state === 'ready') { + layer.un('change', checkState); + resolve(); + } else if (state === 'error') { + layer.un('change', checkState); + reject(new Error('Source failed to load.')); + } + }; + + // Listen for state changes + layer.on('change', checkState); + + // Check the state immediately in case it's already 'ready' + checkState(); + }); + } + /** * Remove a layer from the map. * @@ -1721,4 +1663,5 @@ export class MainView extends React.Component { private _sourceToLayerMap = new Map(); private _documentPath?: string; private _contextMenu: ContextMenu; + private _layerCounter = -1; } From 27689cd0c9ad05632d9aeba3df156ab63a449464 Mon Sep 17 00:00:00 2001 From: Greg Date: Thu, 16 Jan 2025 10:48:18 +0100 Subject: [PATCH 3/4] Use set to track loading layers --- packages/base/src/mainview/mainView.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/base/src/mainview/mainView.tsx b/packages/base/src/mainview/mainView.tsx index 44c56d74f..e4c5a7048 100644 --- a/packages/base/src/mainview/mainView.tsx +++ b/packages/base/src/mainview/mainView.tsx @@ -134,6 +134,7 @@ export class MainView extends React.Component { }; this._sources = []; + this._loadingLayers = new Set(); this._commands = new CommandRegistry(); this._contextMenu = new ContextMenu({ commands: this._commands }); } @@ -761,10 +762,7 @@ export class MainView extends React.Component { await this.addSource(sourceId, source, id); } - if (this._layerCounter === -1) { - this._layerCounter = 0; - } - this._layerCounter++; + this._loadingLayers.add(id); let newMapLayer; let layerParameters; @@ -865,7 +863,7 @@ export class MainView extends React.Component { register(proj4); } - this._layerCounter--; + this._loadingLayers.delete(id); return newMapLayer; } @@ -1084,7 +1082,7 @@ export class MainView extends React.Component { private _waitForReady(): Promise { return new Promise(resolve => { const checkReady = () => { - if (this._layerCounter === 0) { + if (this._loadingLayers.size === 0) { resolve(); } else { setTimeout(checkReady, 50); @@ -1663,5 +1661,5 @@ export class MainView extends React.Component { private _sourceToLayerMap = new Map(); private _documentPath?: string; private _contextMenu: ContextMenu; - private _layerCounter = -1; + private _loadingLayers: Set; } From 5f73bad72927e1b6e2f1ec47d87c5fbbcba183ec Mon Sep 17 00:00:00 2001 From: Greg Date: Thu, 16 Jan 2025 11:13:01 +0100 Subject: [PATCH 4/4] Better error handling --- packages/base/src/mainview/mainView.tsx | 40 ++++++++++++++++++++----- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/packages/base/src/mainview/mainView.tsx b/packages/base/src/mainview/mainView.tsx index e4c5a7048..2bd4449b8 100644 --- a/packages/base/src/mainview/mainView.tsx +++ b/packages/base/src/mainview/mainView.tsx @@ -854,20 +854,44 @@ export class MainView extends React.Component { // we need to keep track of which source has which layers this._sourceToLayerMap.set(layerParameters.source, id); - const sourceProjection = newMapLayer.getSource().getProjection(); - const projectionCode = sourceProjection?.getCode(); - const isProjectionRegistered = getRegisteredProjection(projectionCode); - - if (!isProjectionRegistered) { - proj4.defs([proj4list[projectionCode]]); - register(proj4); - } + this.addProjection(newMapLayer); this._loadingLayers.delete(id); return newMapLayer; } + addProjection(newMapLayer: Layer) { + const sourceProjection = newMapLayer.getSource()?.getProjection(); + if (!sourceProjection) { + console.warn('Layer source projection is undefined or invalid'); + return; + } + + const projectionCode = sourceProjection.getCode(); + + const isProjectionRegistered = getRegisteredProjection(projectionCode); + if (!isProjectionRegistered) { + // Check if the projection exists in proj4list + if (!proj4list[projectionCode]) { + console.warn( + `Projection code '${projectionCode}' not found in proj4list` + ); + return; + } + + try { + proj4.defs([proj4list[projectionCode]]); + register(proj4); + } catch (error: any) { + console.warn( + `Failed to register projection '${projectionCode}'. Error: ${error.message}` + ); + return; + } + } + } + /** * Add a layer to the map. *