From e01d79071470e283b858ad3ff6393255eaeaba34 Mon Sep 17 00:00:00 2001 From: Alexandre Caron Date: Thu, 9 Jan 2025 11:23:23 -0500 Subject: [PATCH] fix(geo): layer - adjust the cancellation of ongoing request (cherry picked from commit 0be6a16ff5b919a4cf5510e612297fb897a8d27a) --- .../shared/datasources/wfs-datasource.ts | 3 +- .../shared/datasources/wms-wfs.utils.ts | 2 +- .../lib/layer/shared/layers/vector-layer.ts | 257 ++++++++++-------- .../shared/edition-workspace.service.ts | 1 - 4 files changed, 148 insertions(+), 115 deletions(-) diff --git a/packages/geo/src/lib/datasource/shared/datasources/wfs-datasource.ts b/packages/geo/src/lib/datasource/shared/datasources/wfs-datasource.ts index 24ca493ea..2d40fbd16 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/wfs-datasource.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/wfs-datasource.ts @@ -131,10 +131,9 @@ export class WFSDataSource extends DataSource { const currentExtent = extent ? OlProj.transformExtent(extent, projection, wfsProj) : undefined; - const ogcFilters = this.ogcFilters; paramsWFS.srsName = paramsWFS.srsName || projection.getCode(); - let url = buildUrl(this.options, currentExtent, wfsProj, ogcFilters); + let url = buildUrl(this.options, currentExtent, wfsProj); // Exportation want to fetch without extent/bbox restrictions if (!extent && url.includes('bbox')) { diff --git a/packages/geo/src/lib/datasource/shared/datasources/wms-wfs.utils.ts b/packages/geo/src/lib/datasource/shared/datasources/wms-wfs.utils.ts index 776fcf01a..c6bd55ff0 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/wms-wfs.utils.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/wms-wfs.utils.ts @@ -30,9 +30,9 @@ export function buildUrl( options: WFSDataSourceOptions, extent: Extent, proj: olProjection, - ogcFilters: OgcFiltersOptions, randomParam?: boolean ): string { + const ogcFilters = options.ogcFilters; const paramsWFS = options.paramsWFS; const queryStringValues = formatWFSQueryString( options, diff --git a/packages/geo/src/lib/layer/shared/layers/vector-layer.ts b/packages/geo/src/lib/layer/shared/layers/vector-layer.ts index 1887fc525..8966d78bf 100644 --- a/packages/geo/src/lib/layer/shared/layers/vector-layer.ts +++ b/packages/geo/src/lib/layer/shared/layers/vector-layer.ts @@ -2,7 +2,6 @@ import { AuthInterceptor } from '@igo2/auth'; import { MessageService } from '@igo2/core/message'; import { ObjectUtils } from '@igo2/utils'; -import olFeature from 'ol/Feature'; import OlFeature from 'ol/Feature'; import { unByKey } from 'ol/Observable'; import { asArray as ColorAsArray } from 'ol/color'; @@ -11,6 +10,7 @@ import BaseEvent from 'ol/events/Event'; import { Extent } from 'ol/extent'; import { FeatureLoader } from 'ol/featureloader'; import * as olformat from 'ol/format'; +import { ReadOptions } from 'ol/format/Feature'; import olLayerVector from 'ol/layer/Vector'; import * as olproj from 'ol/proj'; import olProjection from 'ol/proj/Projection'; @@ -32,17 +32,15 @@ import { FeatureDataSource } from '../../../datasource/shared/datasources/featur import { FeatureDataSourceOptions } from '../../../datasource/shared/datasources/feature-datasource.interface'; import { WebSocketDataSource } from '../../../datasource/shared/datasources/websocket-datasource'; import { WFSDataSource } from '../../../datasource/shared/datasources/wfs-datasource'; -import { WFSDataSourceOptions } from '../../../datasource/shared/datasources/wfs-datasource.interface'; +import { + WFSDataSourceOptions, + WFSDataSourceOptionsParams +} from '../../../datasource/shared/datasources/wfs-datasource.interface'; import { buildUrl, defaultMaxFeatures } from '../../../datasource/shared/datasources/wms-wfs.utils'; -import { - OgcFilterableDataSourceOptions, - OgcFiltersOptions -} from '../../../filter/shared/ogc-filter.interface'; -import type { MapBase } from '../../../map/shared/map.abstract'; -import { MapExtent } from '../../../map/shared/map.interface'; +import type { MapBase, MapExtent } from '../../../map/shared'; import { getResolutionFromScale } from '../../../map/shared/map.utils'; import { InsertSourceInsertDBEnum } from '../../../offline/geoDB/geoDB.enums'; import { GeoDBService } from '../../../offline/geoDB/geoDB.service'; @@ -58,12 +56,17 @@ import { Layer } from './layer'; import { LayerType } from './layer.interface'; import type { VectorLayerOptions } from './vector-layer.interface'; +interface VectorRequest { + xhr: XMLHttpRequest | undefined; + extent: Extent; + resolution: number; +} + export class VectorLayer extends Layer { type: LayerType = 'vector'; - private previousLoadExtent: Extent; - private previousLoadResolution: number; - private previousOgcFilters: OgcFiltersOptions; - private xhrAccumulator: XMLHttpRequest[] = []; + + private lastRequest: VectorRequest; + private ongoingRequests: VectorRequest[] = []; declare public dataSource: | FeatureDataSource | WFSDataSource @@ -190,7 +193,6 @@ export class VectorLayer extends Layer { this.customWFSLoader( vectorSource, wfsOptions, - this.authInterceptor, extent, resolution, proj, @@ -199,23 +201,15 @@ export class VectorLayer extends Layer { ); }; } else { - loader = (extent, resolution, proj, success, failure) => { - this.customLoader( - vectorSource, - url, - extent, - resolution, - proj, - success, - failure - ); + loader = (extent, _resolution, proj, success, failure) => { + this.customLoader(vectorSource, url, extent, proj, success, failure); }; } if (loader) { vectorSource.setLoader(loader); } } else if (this.options.idbInfo?.storeToIdb && this.geoDBService) { - const idbLoader = (extent, resolution, proj, success, failure) => { + const idbLoader = (extent, _resolution, proj, success, failure) => { this.customIDBLoader( vectorSource, olOptions.id, @@ -300,7 +294,7 @@ export class VectorLayer extends Layer { ); } - protected flash(feature) { + protected flash(feature: OlFeature) { const start = new Date().getTime(); const listenerKey = this.ol.on('postrender', animate.bind(this)); @@ -389,7 +383,7 @@ export class VectorLayer extends Layer { if (map === undefined) { this.watcher.unsubscribe(); } else { - this.watcher.subscribe(() => void 1); + this.watcher.subscribe(() => void 0); } super.init(map); } @@ -414,12 +408,13 @@ export class VectorLayer extends Layer { ); } - public enableTrackFeature(id: string | number) { + public enableTrackFeature(id: string | number): void { this.trackFeatureListenerId = this.dataSource.ol.on( 'addfeature', this.trackFeature.bind(this, id) ); } + public centerMapOnFeature(id: string | number) { const feat = this.dataSource.ol.getFeatureById(id); if (feat) { @@ -429,7 +424,7 @@ export class VectorLayer extends Layer { } } - public trackFeature(id, feat) { + public trackFeature(id: string | number, feat: { feature: OlFeature }) { if (feat.feature.getId() === id && this.visible) { this.centerMapOnFeature(id); } @@ -444,7 +439,6 @@ export class VectorLayer extends Layer { * @internal * @param vectorSource the vector source to be created * @param options olOptions from source - * @param interceptor the interceptor of the data * @param extent the extent of the requested data * @param resolution the current resolution * @param proj the projection to retrieve the data @@ -455,84 +449,113 @@ export class VectorLayer extends Layer { public customWFSLoader( vectorSource: olSourceVector, options: WFSDataSourceOptions, - interceptor: AuthInterceptor, extent: Extent, resolution: number, proj: olProjection, - success: (features: olFeature[]) => void, + success: (features: OlFeature[]) => void, failure: () => void, randomParam?: boolean ) { - { - const paramsWFS = options.paramsWFS; - const wfsProj = paramsWFS.srsName - ? new olProjection({ code: paramsWFS.srsName }) - : proj; - const currentExtent = olproj.transformExtent(extent, proj, wfsProj); - const ogcFilters = (options as OgcFilterableDataSourceOptions).ogcFilters; + const paramsWFS = options.paramsWFS; + const wfsProj = this.getProjection(proj, paramsWFS); + const currentExtent = olproj.transformExtent(extent, proj, wfsProj); - if ( - (this.previousLoadExtent && - this.previousLoadExtent !== currentExtent) || - (this.previousLoadResolution && - this.previousLoadResolution !== resolution) || - (this.previousOgcFilters && this.previousOgcFilters !== ogcFilters) - ) { - vectorSource.removeLoadedExtent(this.previousLoadExtent); - for (const xhr of this.xhrAccumulator) { - xhr.abort(); - } - } + if ( + this.lastRequest && + (this.lastRequest.extent !== currentExtent || + this.lastRequest.resolution !== resolution) + ) { + this.abortRequests(vectorSource); + } - this.previousLoadExtent = currentExtent; - this.previousLoadResolution = resolution; - this.previousOgcFilters = ogcFilters; + const url = buildUrl(options, currentExtent, wfsProj, randomParam); - paramsWFS.srsName = paramsWFS.srsName || proj.getCode(); + const request: VectorRequest = { + xhr: undefined, + extent, + resolution + }; + + const readOptions: ReadOptions = { + dataProjection: wfsProj, + featureProjection: proj + }; - const url = buildUrl( - options, - currentExtent, - wfsProj, - ogcFilters, - randomParam + if ( + paramsWFS.version === '2.0.0' && + paramsWFS.maxFeatures > defaultMaxFeatures + ) { + this.batchGetFeatures( + url, + request, + vectorSource, + paramsWFS, + readOptions, + success, + failure ); - let startIndex = 0; - if ( - paramsWFS.version === '2.0.0' && - paramsWFS.maxFeatures > defaultMaxFeatures - ) { - const nbOfFeature = 1000; - while (startIndex < paramsWFS.maxFeatures) { - let alteredUrl = url.replace( - 'count=' + paramsWFS.maxFeatures, - 'count=' + nbOfFeature - ); - alteredUrl = alteredUrl.replace('startIndex=0', '0'); - alteredUrl += '&startIndex=' + startIndex; - alteredUrl.replace(/&&/g, '&'); - this.getFeatures( - vectorSource, - currentExtent, - wfsProj, - proj, - alteredUrl, - success, - failure - ); - startIndex += nbOfFeature; - } - } else { - this.getFeatures( - vectorSource, - currentExtent, - wfsProj, - proj, - url, - success, - failure - ); - } + } else { + this.getFeatures( + url, + request, + vectorSource, + readOptions, + success, + failure + ); + } + } + + private abortRequests(vectorSource: olSourceVector): void { + vectorSource.removeLoadedExtent(this.lastRequest.extent); + for (const request of this.ongoingRequests) { + request.xhr?.abort(); + this.removeRequest(request); + } + } + + private getProjection( + proj: olProjection, + params: WFSDataSourceOptionsParams + ) { + const newProj = params.srsName + ? new olProjection({ code: params.srsName }) + : proj; + + params.srsName = newProj.getCode(); + + return newProj; + } + + private batchGetFeatures( + url: string, + request: VectorRequest, + vectorSource: olSourceVector, + paramsWFS: WFSDataSourceOptionsParams, + readOptions: ReadOptions, + success: (features: OlFeature[]) => void, + failure: () => void + ) { + const nbOfFeature = 1000; + let startIndex = 0; + while (startIndex < paramsWFS.maxFeatures) { + let alteredUrl = url.replace( + 'count=' + paramsWFS.maxFeatures, + 'count=' + nbOfFeature + ); + alteredUrl = alteredUrl.replace('startIndex=0', '0'); + alteredUrl += '&startIndex=' + startIndex; + alteredUrl.replace(/&&/g, '&'); + + this.getFeatures( + alteredUrl, + request, + vectorSource, + readOptions, + success, + failure + ); + startIndex += nbOfFeature; } } @@ -548,14 +571,13 @@ export class VectorLayer extends Layer { * @param failure failure callback */ private getFeatures( - vectorSource: olSourceVector, - extent: Extent, - dataProjection: olProjection, - featureProjection: olProjection, url: string, - success: (features: olFeature[]) => void, + request: VectorRequest, + vectorSource: olSourceVector, + readOptions: ReadOptions, + success: (features: OlFeature[]) => void, failure: () => void - ) { + ): void { const xhr = new XMLHttpRequest(); const alteredUrlWithKeyAuth = this.authInterceptor.alterUrlWithKeyAuth(url); let modifiedUrl = url; @@ -567,8 +589,10 @@ export class VectorLayer extends Layer { if (this.authInterceptor) { this.authInterceptor.interceptXhr(xhr, modifiedUrl); } + const onError = () => { - vectorSource.removeLoadedExtent(extent); + vectorSource.removeLoadedExtent(request.extent); + this.removeRequest(request); failure(); }; xhr.onerror = onError; @@ -577,40 +601,51 @@ export class VectorLayer extends Layer { if (xhr.status === 200 && xhr.responseText.length > 0) { const features = vectorSource .getFormat() - .readFeatures(xhr.responseText, { - dataProjection, - featureProjection - }) as olFeature[]; + .readFeatures(xhr.responseText, readOptions) as OlFeature[]; + if (features) { vectorSource.addFeatures(features); success(features); } else { success([]); } + this.removeRequest(request); } else { onError(); } }; - this.xhrAccumulator.push(xhr); + + request.xhr = xhr; + this.lastRequest = request; + this.ongoingRequests.push(request); + xhr.send(); } + private removeRequest(request: VectorRequest): void { + if (request === this.lastRequest) { + this.lastRequest = undefined; + } + const index = this.ongoingRequests.indexOf(request); + if (index > -1) { + this.ongoingRequests.splice(index, 1); + } + } + /** * Custom loader for vector layer. * @internal * @param vectorSource the vector source to be created * @param url the url string or function to retrieve the data * @param extent the extent of the requested data - * @param resolution the current resolution * @param projection the projection to retrieve the data */ private customLoader( vectorSource: olSourceVector, url: string, extent: Extent, - resolution: number, projection: olProjection, - success: (features: olFeature[]) => void, + success: (features: OlFeature[]) => void, failure: () => void ) { const xhr = new XMLHttpRequest(); diff --git a/packages/geo/src/lib/workspace/shared/edition-workspace.service.ts b/packages/geo/src/lib/workspace/shared/edition-workspace.service.ts index 108e7173c..2a3be280a 100644 --- a/packages/geo/src/lib/workspace/shared/edition-workspace.service.ts +++ b/packages/geo/src/lib/workspace/shared/edition-workspace.service.ts @@ -761,7 +761,6 @@ export class EditionWorkspaceService { layer.customWFSLoader( layer.ol.getSource(), layer.options.sourceOptions as WFSDataSourceOptions, - this.authInterceptor, extent, resolution, proj,