diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e6f579b..829b4afe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Change Log +## [v26.1.0](https://github.com/gisaia/ARLAS-WUI/tree/v26.1.0) (2024-12-02) + +[Full Changelog](https://github.com/gisaia/ARLAS-WUI/compare/v26.1.0-rc.3...v26.1.0) + +## [v26.1.0-rc.3](https://github.com/gisaia/ARLAS-WUI/tree/v26.1.0-rc.3) (2024-11-27) + +[Full Changelog](https://github.com/gisaia/ARLAS-WUI/compare/v26.1.0-rc.2...v26.1.0-rc.3) + +## [v26.1.0-rc.2](https://github.com/gisaia/ARLAS-WUI/tree/v26.1.0-rc.2) (2024-11-26) + +[Full Changelog](https://github.com/gisaia/ARLAS-WUI/compare/v26.1.0-rc.1...v26.1.0-rc.2) + ## [v26.1.0-rc.1](https://github.com/gisaia/ARLAS-WUI/tree/v26.1.0-rc.1) (2024-11-26) [Full Changelog](https://github.com/gisaia/ARLAS-WUI/compare/v26.0.8...v26.1.0-rc.1) @@ -949,19 +961,19 @@ ## [v15.0.0](https://github.com/gisaia/ARLAS-WUI/tree/v15.0.0) (2021-02-18) -[Full Changelog](https://github.com/gisaia/ARLAS-WUI/compare/v15.0.0-beta.1...v15.0.0) +[Full Changelog](https://github.com/gisaia/ARLAS-WUI/compare/v15.0.0-beta.0...v15.0.0) **Fixed bugs:** - Wrong css applied in details section in resultlist [\#268](https://github.com/gisaia/ARLAS-wui/issues/268) -## [v15.0.0-beta.1](https://github.com/gisaia/ARLAS-WUI/tree/v15.0.0-beta.1) (2021-02-04) +## [v15.0.0-beta.0](https://github.com/gisaia/ARLAS-WUI/tree/v15.0.0-beta.0) (2021-02-04) -[Full Changelog](https://github.com/gisaia/ARLAS-WUI/compare/v15.0.0-beta.0...v15.0.0-beta.1) +[Full Changelog](https://github.com/gisaia/ARLAS-WUI/compare/v15.0.0-beta.1...v15.0.0-beta.0) -## [v15.0.0-beta.0](https://github.com/gisaia/ARLAS-WUI/tree/v15.0.0-beta.0) (2021-02-04) +## [v15.0.0-beta.1](https://github.com/gisaia/ARLAS-WUI/tree/v15.0.0-beta.1) (2021-02-04) -[Full Changelog](https://github.com/gisaia/ARLAS-WUI/compare/v14.3.1...v15.0.0-beta.0) +[Full Changelog](https://github.com/gisaia/ARLAS-WUI/compare/v14.3.1...v15.0.0-beta.1) ## [v14.3.1](https://github.com/gisaia/ARLAS-WUI/tree/v14.3.1) (2021-02-03) diff --git a/src/app/components/arlas-list/arlas-list.component.html b/src/app/components/arlas-list/arlas-list.component.html index 04084bd6..b2b70091 100644 --- a/src/app/components/arlas-list/arlas-list.component.html +++ b/src/app/components/arlas-list/arlas-list.component.html @@ -45,6 +45,7 @@ (geoSortEvent)="geoSort(list, $event)" (geoAutoSortEvent)="geoAutoSort(list, $event)" (consultedItemEvent)="consultItem(list, $event)" + (selectedItemsEvent)="selectItems(list, $event)" (actionOnItemEvent)="applyActionOnItem(list, $event)" (globalActionEvent)="applyGlobalAction(list, $event)" (changeResultMode)="changeListResultMode($event, list.identifier)" diff --git a/src/app/components/arlas-map/arlas-map.component.html b/src/app/components/arlas-map/arlas-map.component.html index 6ae2360c..092307de 100644 --- a/src/app/components/arlas-map/arlas-map.component.html +++ b/src/app/components/arlas-map/arlas-map.component.html @@ -57,13 +57,14 @@ [margePanForLoad]="mapComponentConfig?.margePanForLoad" [margePanForTest]="mapComponentConfig?.margePanForTest" [initCenter]="mapComponentConfig?.initCenter" [drawData]="geojsondraw" + [transformRequest]="transformMapRequest" [initZoom]="mapComponentConfig?.initZoom" [minZoom]="mapComponentConfig?.minZoom" [maxZoom]="mapComponentConfig?.maxZoom" [displayScale]="mapComponentConfig?.displayScale" [displayCurrentCoordinates]="mapComponentConfig?.displayCurrentCoordinates" [maxWidthScale]="mapComponentConfig?.maxWidthScale" [unitScale]="mapComponentConfig?.unitScale" [idFeatureField]="mapComponentConfig?.idFieldName" [mapLayers]="mapComponentConfig?.mapLayers" [mapSources]="mapComponentConfig?.mapSources" - [featureToHightLight]="mapService.featureToHightLight" [featuresToSelect]="featuresToSelect" + [featureToHightLight]="mapService.featureToHightLight" [featuresToSelect]="mapService.featuresToSelect" [boundsToFit]="visualizeService.fitbounds" [drawPolygonVerticesLimit]="nbVerticesLimit" [drawButtonEnabled]="false" [mapAttributionPosition]="mapAttributionPosition" (onAoiChanged)="onChangeAoi($event)" diff --git a/src/app/components/arlas-map/arlas-map.component.ts b/src/app/components/arlas-map/arlas-map.component.ts index cfdb67b5..45865ba4 100644 --- a/src/app/components/arlas-map/arlas-map.component.ts +++ b/src/app/components/arlas-map/arlas-map.component.ts @@ -35,8 +35,8 @@ import { import { ElementIdentifier, MapContributor } from 'arlas-web-contributors'; import { LegendData } from 'arlas-web-contributors/contributors/MapContributor'; import { - ArlasCollaborativesearchService, ArlasCollectionService, ArlasConfigService, ArlasMapService, - ArlasMapSettings, ArlasSettingsService, ArlasStartupService, getParamValue + ArlasCollaborativesearchService, ArlasCollectionService, ArlasConfigService, ArlasIamService, ArlasMapService, + ArlasMapSettings, ArlasSettingsService, ArlasStartupService, AuthentificationService, getParamValue } from 'arlas-wui-toolkit'; import * as mapboxgl from 'mapbox-gl'; import { BehaviorSubject, debounceTime, fromEvent, merge, mergeMap, Observable, of, Subject, takeUntil } from 'rxjs'; @@ -61,9 +61,6 @@ export class ArlasMapComponent implements OnInit { public mapAttributionPosition: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' = 'top-right'; public mapLoaded = false; - /** Map interactions */ - public featuresToSelect: Array = []; - /** Map move */ public fitbounds: Array> = []; public recalculateExtent = true; @@ -85,6 +82,9 @@ export class ArlasMapComponent implements OnInit { public mapRedrawSources; public mapLegendUpdater = new Subject>>(); public mapVisibilityUpdater; + + /** Map Url enricher */ + public transformMapRequest; /** Visibility status of layers on the map */ public layersVisibilityStatus: Map = new Map(); @@ -131,7 +131,9 @@ export class ArlasMapComponent implements OnInit { private snackbar: MatSnackBar, private iconRegistry: MatIconRegistry, private domSanitizer: DomSanitizer, - private collectionService: ArlasCollectionService + private collectionService: ArlasCollectionService, + private authentService: AuthentificationService, + private arlasIamService: ArlasIamService ) { if (this.arlasStartupService.shouldRunApp && !this.arlasStartupService.emptyMode) { /** resize the map */ @@ -222,6 +224,9 @@ export class ArlasMapComponent implements OnInit { // eslint-disable-next-line max-len this.iconRegistry.addSvgIconLiteral('import_polygon', this.domSanitizer.bypassSecurityTrustHtml('')); } + + this.updateMapTransformRequest(); + } public ngAfterViewInit() { @@ -243,6 +248,71 @@ export class ArlasMapComponent implements OnInit { } } + /** Updates the map url headers at each refresh.*/ + public updateMapTransformRequest() { + const authentMode = !!this.settingsService.getAuthentSettings() ? this.settingsService.getAuthentSettings().auth_mode : undefined; + const isAuthentActivated = !!this.settingsService.getAuthentSettings() && !!this.settingsService.getAuthentSettings().use_authent; + if (isAuthentActivated) { + if (authentMode === 'iam') { + this.arlasIamService.tokenRefreshed$.pipe(takeUntil(this._onDestroy$)).subscribe({ + next: (loginData) => { + if (!!loginData) { + const org = this.arlasIamService.getOrganisation(); + const iamHeader = { + Authorization: 'Bearer ' + loginData.access_token, + }; + // Set the org filter only if the organisation is defined + if (!!org) { + iamHeader['arlas-org-filter'] = org; + } + this.setMapTransformRequest(iamHeader); + } else { + this.setMapTransformRequest(); + } + } + }); + } else { + this.authentService.canActivateProtectedRoutes.pipe(takeUntil(this._onDestroy$)).subscribe(isConnected => { + if (isConnected) { + const headers = { + Authorization: 'Bearer ' + this.authentService.accessToken + }; + this.setMapTransformRequest(headers); + } else { + this.setMapTransformRequest(); + } + }); + } + } else { + this.setMapTransformRequest(); + } + } + + /** Enriches map url by an ARLAS header only if the map url is provided by ARLAS. */ + public setMapTransformRequest(headers?: any) { + this.transformMapRequest = (url, resourceType) => { + /** Wrapping with a try block because the URL() mdn docs says : 'Throws if the passed arguments don't define a valid URL.' */ + try { + const mapServiceUrl = new URL(url); + const appUrl = new URL(window.location.href); + const mapServiceOrigin = mapServiceUrl.origin; + const appOrigin = appUrl.origin; + /** We enrich map url by an ARLAS header only if the map url is provided by ARLAS. */ + if (appOrigin === mapServiceOrigin && !!headers) { + return ({ + url, + headers, + }); + } else { + return ({ url }); + } + } catch { + return ({ url }); + } + }; + } + + /** * Wait until the map component is loaded before fetching the data * @param isLoaded Whether the map has loaded @@ -250,6 +320,7 @@ export class ArlasMapComponent implements OnInit { public onMapLoaded(isLoaded: boolean): void { if (isLoaded && !this.arlasStartupService.emptyMode) { this.mapLoaded = true; + this.mapService.setMapComponent(this.mapglComponent); this.toolkitMapService.setMap(this.mapglComponent.map); this.visualizeService.setMap(this.mapglComponent.map); @@ -277,7 +348,7 @@ export class ArlasMapComponent implements OnInit { }); if (!!this.resultlistService.previewListContrib && this.resultlistService.previewListContrib.data.length > 0 && - this.mapComponentConfig.mapLayers.events.onHover.filter(l => this.mapglComponent.map.getLayer(l)).length > 0) { + this.mapComponentConfig.mapLayers.events.onHover.filter(l => this.mapglComponent.map.getLayer(l)).length > 0) { this.resultlistService.updateVisibleItems(); } } diff --git a/src/app/services/map.service.ts b/src/app/services/map.service.ts index 32d6b88b..6b69867e 100644 --- a/src/app/services/map.service.ts +++ b/src/app/services/map.service.ts @@ -34,7 +34,10 @@ export class MapService { private mapComponentConfig: any; public mapContributors: Array = new Array(); public centerLatLng: { lat: number; lng: number; } = { lat: 0, lng: 0 }; + public featureToHightLight: FeatureHover; + public featuresToSelect: Array = []; + public coordinatesHaveSpace: boolean; public constructor() { } @@ -46,7 +49,7 @@ export class MapService { public selectFeatures(idPath: string, ids: string[] | number[], mapContributor: MapContributor) { if (!!this.mapComponent && !!mapContributor) { if (ids.length > 0) { - const featuresToSelect = ids.map(id => { + this.featuresToSelect = ids.map(id => { let idFieldName = idPath; if (mapContributor.isFlat) { idFieldName = idFieldName.replace(/\./g, '_'); @@ -56,7 +59,7 @@ export class MapService { idValue: id }; }); - this.mapComponent.selectFeaturesByCollection(featuresToSelect, mapContributor.collection); + this.mapComponent.selectFeaturesByCollection(this.featuresToSelect, mapContributor.collection); } else { this.mapComponent.selectFeaturesByCollection([], mapContributor.collection); } diff --git a/src/app/services/resultlist.service.ts b/src/app/services/resultlist.service.ts index bc5a85fe..d3fcd191 100644 --- a/src/app/services/resultlist.service.ts +++ b/src/app/services/resultlist.service.ts @@ -39,7 +39,7 @@ import { DOWNLOAD_PROCESS_NAME, ENRICH_PROCESS_NAME, getParamValue, ProcessService } from 'arlas-wui-toolkit'; -import { BehaviorSubject, finalize, Subject } from 'rxjs'; +import { BehaviorSubject, finalize, Subject, take } from 'rxjs'; @Injectable({ @@ -66,6 +66,7 @@ export class ResultlistService { private currentClickedFeatureId: string = undefined; public resultlistIsExporting = false; public activeActionsPerContId = new Map>>(); + public selectedItems = new Array(); /** Resullist component */ private listComponent: ResultListComponent; @@ -127,6 +128,7 @@ export class ResultlistService { this.addActions(); this.declareResultlistExportCsv(); + this.declareGlobalRasterVisualisation(); } } @@ -380,10 +382,11 @@ export class ResultlistService { } break; case 'selectedItemsEvent': - const ids = event.data; + const ids: Array = event.data; const idPath = this.collectionToDescription.get(currentCollection)?.id_path; if (!!idPath) { this.mapService.selectFeatures(idPath, ids, mapContributor); + this.selectedItems = ids.map(id => ({idFieldName: idPath, idValue: id})); } break; case 'actionOnItemEvent': @@ -391,11 +394,9 @@ export class ResultlistService { break; case 'globalActionEvent': if (event.data.id === 'production') { - const idsItemSelected: ElementIdentifier[] = this.mapService.mapComponent.featuresToSelect; - this.aiasDownload(idsItemSelected.map(i => i.idValue), currentCollection); + this.aiasDownload(this.selectedItems.map(i => i.idValue), currentCollection); } else if (event.data.id === 'enrich') { - const idsItemSelected: ElementIdentifier[] = this.mapService.mapComponent.featuresToSelect; - this.aiasEnrich(idsItemSelected.map(i => i.idValue), currentCollection); + this.aiasEnrich(this.selectedItems.map(i => i.idValue), currentCollection); } else if (event.data.id === 'export_csv') { this.resultlistIsExporting = true; this.exportService.fetchResultlistData$(resultListContributor, undefined) @@ -404,6 +405,19 @@ export class ResultlistService { next: (h) => this.exportService.exportResultlist(resultListContributor, h), error: (e) => this.snackbar.open(marker('An error occured exporting the list')) }); + } else if (event.data.id === 'visualize') { + this.selectedItems.forEach(e => { + // For each element, check if the necessary fields for the visualisation are present + this.listComponent.detailedDataRetriever.getValues(e.idValue, event.data.fields).pipe(take(1)).subscribe({ + next: (values: string[]) => { + // If no field is missing, visualize the raster + if (values.filter(v => !v).length === 0) { + this.visualizeRaster({action: event.data, elementidentifier: e}, resultListContributor, currentCollection, false); + this.addAction(event.origin, e.idValue, event.data.action); + } + } + }); + }); } break; case 'geoSortEvent': @@ -427,7 +441,9 @@ export class ResultlistService { .forEach(c => c.getPage(eventPaginate.reference, sort, eventPaginate.whichPage, contributor.maxPages)); } - public actionOnItemEvent(data, mapContributor: MapContributor, listContributor: ResultListContributor, collection: string) { + public actionOnItemEvent(data: {action: Action; elementidentifier: ElementIdentifier;}, + mapContributor: MapContributor, listContributor: ResultListContributor, collection: string) { + switch (data.action.id) { case 'zoomToFeature': if (!!mapContributor) { @@ -436,20 +452,7 @@ export class ResultlistService { } break; case 'visualize': - if (!!this.resultlistConfigPerContId.get(listContributor.identifier)) { - const urlVisualisationTemplate = this.resultlistConfigPerContId.get(listContributor.identifier).visualisationLink; - if (!data.action.activated) { - this.visualizeService.getVisuInfo(data.elementidentifier, collection, urlVisualisationTemplate).subscribe(url => { - this.visualizeService.displayDataOnMap(url, - data.elementidentifier, this.collectionToDescription.get(collection).geometry_path, - this.collectionToDescription.get(collection).centroid_path, collection); - }); - this.addAction(listContributor.identifier, data.elementidentifier.idValue, data.action); - } else { - this.visualizeService.removeRasters(data.elementidentifier.idValue); - this.removeAction(listContributor.identifier, data.elementidentifier.idValue, data.action.id); - } - } + this.visualizeRaster(data, listContributor, collection); break; case 'download': if (!!this.resultlistConfigPerContId.get(listContributor.identifier)) { @@ -488,6 +491,25 @@ export class ResultlistService { }, 100); } + private visualizeRaster(data: {action: Action; elementidentifier: ElementIdentifier;}, + listContributor: ResultListContributor, collection: string, fitBounds = true) { + + if (!!this.resultlistConfigPerContId.get(listContributor.identifier)) { + const urlVisualisationTemplate = this.resultlistConfigPerContId.get(listContributor.identifier).visualisationLink; + if (!data.action.activated) { + this.visualizeService.getVisuInfo(data.elementidentifier, collection, urlVisualisationTemplate).subscribe(url => { + this.visualizeService.displayDataOnMap(url, + data.elementidentifier, this.collectionToDescription.get(collection).geometry_path, + this.collectionToDescription.get(collection).centroid_path, collection, fitBounds); + }); + this.addAction(listContributor.identifier, data.elementidentifier.idValue, data.action); + } else { + this.visualizeService.removeRasters(data.elementidentifier.idValue); + this.removeAction(listContributor.identifier, data.elementidentifier.idValue, data.action.id); + } + } + } + private process(processName: string, ids: string[], collection: string, component: ComponentType, additionalData?: any) { const maxItems = this.settingsService.getProcessSettings(processName).max_items; if (ids.length <= maxItems) { @@ -606,7 +628,7 @@ export class ResultlistService { if (!resultConfig.globalActionsList) { resultConfig.globalActionsList = []; } - resultConfig.globalActionsList.push({ 'id': id, 'label': label }); + (resultConfig.globalActionsList as Array).push({ 'id': id, 'label': label }); }; } }); @@ -623,9 +645,23 @@ export class ResultlistService { if (!resultConfig.globalActionsList) { resultConfig.globalActionsList = []; } - resultConfig.globalActionsList.push({ 'id': 'export_csv', 'label': marker('Export csv'), 'alwaysEnabled': true }); + (resultConfig.globalActionsList as Array).push({ 'id': 'export_csv', 'label': marker('Export csv'), 'alwaysEnabled': true }); } }); } } + + private declareGlobalRasterVisualisation() { + this.resultlistContributors.forEach(c => { + const resultConfig = this.resultlistConfigPerContId.get(c.identifier); + if (!!resultConfig && !!resultConfig.visualisationLink) { + if (!resultConfig.globalActionsList) { + resultConfig.globalActionsList = []; + } + const reverseAction = c.actionToTriggerOnClick.find(a => a.id === 'visualize').reverseAction; + (resultConfig.globalActionsList as Array).push({id: 'visualize', label: marker('Visualize products'), + fields: this.visualizeService.getVisuFields(resultConfig.visualisationLink), reverseAction}); + } + }); + } } diff --git a/src/app/services/visualize.service.ts b/src/app/services/visualize.service.ts index 83489075..3e89439d 100644 --- a/src/app/services/visualize.service.ts +++ b/src/app/services/visualize.service.ts @@ -117,7 +117,7 @@ export class VisualizeService { return urlTemplate; } else { this.getVisuFields(urlTemplate).forEach(field => { - if (data.hits[0].data[field] === undefined) { + if (!data.hits || data.hits[0].data[field] === undefined) { return undefined; } else { urlTemplate = urlTemplate.replace('{' + field + '}', data.hits[0].data[field]); @@ -209,7 +209,7 @@ export class VisualizeService { } public displayDataOnMap(url: string, elementidentifier: ElementIdentifier, - geometryPath: string, centroidPath: string, collection) { + geometryPath: string, centroidPath: string, collection: string, fitBounds = true) { if (url !== undefined && url.indexOf('{bbox-epsg-3857}{:bbox3857:}') >= 0) { url = url.replace('{:bbox3857:}', ''); } @@ -221,7 +221,9 @@ export class VisualizeService { geometryPath, centroidPath, collection) .subscribe(d => { this.addRaster(url, 25, d.box, elementidentifier.idValue); - this.fitbounds = d.bounds; + if (fitBounds) { + this.fitbounds = d.bounds; + } }); } else { const snackMsg = this.translateService.instant('The visualisation is not available for this product.'); diff --git a/src/assets/processes/download.json b/src/assets/processes/download.json index 2fee6c77..1997b5e8 100644 --- a/src/assets/processes/download.json +++ b/src/assets/processes/download.json @@ -24,7 +24,23 @@ "enum": [ { "label": "WGS 84", - "value": "epsg:4326" + "value": "epsg:4326", + "bbox": [ + -180.0, + -90.0, + 180.0, + 90.0 + ] + }, + { + "label": "EPSG:3857", + "value": "epsg:3857", + "bbox": [ + -180.0, + -90.0, + 180.0, + 90.0 + ] } ] } diff --git a/src/settings.yaml b/src/settings.yaml index 1f27716e..77e9dddc 100644 --- a/src/settings.yaml +++ b/src/settings.yaml @@ -29,7 +29,7 @@ authentication: tab_name: "ARLAS Wui" persistence: - url: https://localhost/persist + url: https://cloud.arlas.io/arlas/persistence use_local_config: false histogram: max_buckets: 200