diff --git a/packages/common/src/lib/action/actionbar/actionbar.component.html b/packages/common/src/lib/action/actionbar/actionbar.component.html index 43e10039f8..48c802113f 100644 --- a/packages/common/src/lib/action/actionbar/actionbar.component.html +++ b/packages/common/src/lib/action/actionbar/actionbar.component.html @@ -53,6 +53,9 @@
diff --git a/packages/geo/src/lib/layer/layer-list/layer-list.component.html b/packages/geo/src/lib/layer/layer-list/layer-list.component.html index 0326e5486c..323a4bdfce 100644 --- a/packages/geo/src/lib/layer/layer-list/layer-list.component.html +++ b/packages/geo/src/lib/layer/layer-list/layer-list.component.html @@ -77,7 +77,7 @@ matTooltipShowDelay="500" [matTooltip]="(sortAlpha || onlyVisible || keyword) ? ('igo.geo.layer.filterRaiseLayer' | translate) : ('igo.geo.layer.raiseLayer' | translate)" [disabled]="raiseDisabled" - (click)="activeLayer.map.raiseLayer(activeLayer)"> + (click)="moveActiveLayer(activeLayer,layerListDisplacement.Raise)"> @@ -89,7 +89,7 @@ matTooltipShowDelay="500" [matTooltip]="(sortAlpha || onlyVisible || keyword) ? ('igo.geo.layer.filterLowerLayer' | translate) : ('igo.geo.layer.lowerLayer' | translate)" [disabled]="lowerDisabled" - (click)="activeLayer.map.lowerLayer(activeLayer)"> + (click)="moveActiveLayer(activeLayer,layerListDisplacement.Lower)"> diff --git a/packages/geo/src/lib/layer/layer-list/layer-list.component.ts b/packages/geo/src/lib/layer/layer-list/layer-list.component.ts index fcc90faef3..07220656ec 100644 --- a/packages/geo/src/lib/layer/layer-list/layer-list.component.ts +++ b/packages/geo/src/lib/layer/layer-list/layer-list.component.ts @@ -12,8 +12,7 @@ import { import type { TemplateRef } from '@angular/core'; import { FloatLabelType } from '@angular/material/form-field'; -import { Layer } from '../shared'; -import { LayerListControlsEnum } from './layer-list.enum'; +import { LayerListControlsEnum, LayerListDisplacement } from './layer-list.enum'; import { LayerListSelectVisibleEnum } from './layer-list.enum'; import { BehaviorSubject, @@ -29,6 +28,8 @@ import { } from '../../metadata/shared/metadata.interface'; import { LayerListControlsOptions } from '../layer-list-tool/layer-list-tool.interface'; import { IgoMap } from '../../map/shared/map'; +import { Layer } from '../shared/layers/layer'; +import { LinkedProperties, LayersLink } from '../shared/layers/layer.interface'; // TODO: This class could use a clean up. Also, some methods could be moved ealsewhere @Component({ @@ -210,13 +211,17 @@ export class LayerListComponent implements OnInit, OnDestroy { } } + get layerListDisplacement(): typeof LayerListDisplacement { + return LayerListDisplacement; + } + public toggleOpacity = false; public selectAllCheck: boolean; public selectAllCheck$ = new BehaviorSubject(undefined); private selectAllCheck$$: Subscription; - constructor(private elRef: ElementRef) {} + constructor(private elRef: ElementRef) { } /** * Subscribe to the search term stream and trigger researches @@ -258,9 +263,9 @@ export class LayerListComponent implements OnInit, OnDestroy { if (this.excludeBaseLayers) { this.selectAllCheck = checks === - this.layers.filter( - (lay) => lay.baseLayer !== true && lay.showInLayerList - ).length + this.layers.filter( + (lay) => lay.baseLayer !== true && lay.showInLayerList + ).length ? true : false; } else { @@ -329,6 +334,65 @@ export class LayerListComponent implements OnInit, OnDestroy { return false; } + moveActiveLayer(activeLayer: Layer, actiontype: LayerListDisplacement) { + const layersToMove = [activeLayer]; + const sortedLayersToMove = []; + this.getLinkedLayers(activeLayer, layersToMove); + this.layers.map(layer => { + if (layersToMove.indexOf(layer) !== -1) { + sortedLayersToMove.push(layer); + } + }); + + if (actiontype === LayerListDisplacement.Raise) { + this.raiseLayers(sortedLayersToMove, false); + } else if (actiontype === LayerListDisplacement.Lower) { + this.lowerLayers(sortedLayersToMove, false); + } + } + + private getLinkedLayers(activeLayer: Layer, layersList: Layer[]) { + const linkedLayers = activeLayer.options.linkedLayers as LayersLink; + if (!linkedLayers) { + return; + } + const currentLinkedId = linkedLayers.linkId; + const currentLinks = linkedLayers.links; + const isParentLayer = currentLinks ? true : false; + + if (isParentLayer) { + // search for child layers + currentLinks.map(link => { + if (!link.properties || link.properties.indexOf(LinkedProperties.ZINDEX) === -1) { + return; + } + link.linkedIds.map(linkedId => { + const childLayer = this.layers.find(layer => layer.options.linkedLayers?.linkId === linkedId); + if (childLayer) { + if (!layersList.includes(childLayer)) { + layersList.push(childLayer); + } + } + }); + }); + } else { + // search for parent layer + this.layers.map(parentLayer => { + if (parentLayer.options.linkedLayers?.links) { + parentLayer.options.linkedLayers.links.map(l => { + if ( + l.properties?.indexOf(LinkedProperties.ZINDEX) !== -1 && + l.bidirectionnal !== false && + l.linkedIds.indexOf(currentLinkedId) !== -1) { + layersList.push(parentLayer); + this.getLinkedLayers(parentLayer, layersList); + } + }); + } + }); + } + } + /* * For selection mode disabled attribute */ @@ -376,7 +440,7 @@ export class LayerListComponent implements OnInit, OnDestroy { return true; } - raiseLayers(layers: Layer[]) { + raiseLayers(layers: Layer[], fromUi: boolean = true) { const layersToRaise = []; for (const layer of layers) { const index = this.layers.findIndex((lay) => lay.id === layer.id); @@ -385,14 +449,15 @@ export class LayerListComponent implements OnInit, OnDestroy { } } this.map.raiseLayers(layersToRaise); - setTimeout(() => { - const elements = this.computeElementRef(); - if (!this.isScrolledIntoView(elements[0], elements[1].offsetParent)) { - elements[0].scrollTop = elements[1].offsetParent.offsetTop; - } - }, 100); + if (fromUi) { + setTimeout(() => { + const elements = this.computeElementRef(); + if (!this.isScrolledIntoView(elements[0], elements[1].offsetParent)) { + elements[0].scrollTop = elements[1].offsetParent.offsetTop; + } + }, 100); + } } - /* * For selection mode disabled attribute */ @@ -442,7 +507,7 @@ export class LayerListComponent implements OnInit, OnDestroy { return true; } - lowerLayers(layers: Layer[]) { + lowerLayers(layers: Layer[], fromUi: boolean = true) { const layersToLower = []; for (const layer of layers) { const index = this.layers.findIndex((lay) => lay.id === layer.id); @@ -451,17 +516,18 @@ export class LayerListComponent implements OnInit, OnDestroy { } } this.map.lowerLayers(layersToLower); - setTimeout(() => { - const elements = this.computeElementRef('lower'); - if (!this.isScrolledIntoView(elements[0], elements[1].offsetParent)) { - elements[0].scrollTop = - elements[1].offsetParent.offsetTop + - elements[1].offsetParent.offsetHeight - - elements[0].clientHeight; - } - }, 100); + if (fromUi) { + setTimeout(() => { + const elements = this.computeElementRef('lower'); + if (!this.isScrolledIntoView(elements[0], elements[1].offsetParent)) { + elements[0].scrollTop = + elements[1].offsetParent.offsetTop + + elements[1].offsetParent.offsetHeight - + elements[0].clientHeight; + } + }, 100); + } } - private next() { this.change$.next(); } @@ -790,11 +856,11 @@ export class LayerListComponent implements OnInit, OnDestroy { const checkItem = type === 'lower' ? this.elRef.nativeElement.getElementsByClassName( - 'mat-checkbox-checked' - )[checkItems.length - 1] + 'mat-checkbox-checked' + )[checkItems.length - 1] : this.elRef.nativeElement.getElementsByClassName( - 'mat-checkbox-checked' - )[0]; + 'mat-checkbox-checked' + )[0]; const igoList = this.elRef.nativeElement.getElementsByTagName( 'igo-list' )[0]; diff --git a/packages/geo/src/lib/layer/layer-list/layer-list.enum.ts b/packages/geo/src/lib/layer/layer-list/layer-list.enum.ts index 5fe8f7dd0b..25a93a8846 100644 --- a/packages/geo/src/lib/layer/layer-list/layer-list.enum.ts +++ b/packages/geo/src/lib/layer/layer-list/layer-list.enum.ts @@ -9,3 +9,8 @@ export enum LayerListSelectVisibleEnum { MIXED = 'MIXED', NULL = 'NULL' } + +export enum LayerListDisplacement { + Raise = 'raise', + Lower = 'lower', +} diff --git a/packages/geo/src/lib/layer/shared/layers/layer.interface.ts b/packages/geo/src/lib/layer/shared/layers/layer.interface.ts index 855312744c..a8514d9bfa 100644 --- a/packages/geo/src/lib/layer/shared/layers/layer.interface.ts +++ b/packages/geo/src/lib/layer/shared/layers/layer.interface.ts @@ -20,12 +20,43 @@ export interface LayerOptions { maxScaleDenom?: number; showInLayerList?: boolean; removable?: boolean; + workspace?: GeoWorkspaceOptions; legendOptions?: LegendOptions; ol?: olLayer; tooltip?: TooltipContent; _internal?: { [key: string]: string }; active?: boolean; check?: boolean; + linkedLayers?: LayersLink; +} + +export interface GeoWorkspaceOptions { + srcId?: string; + workspaceId?: string; + minResolution?: number; + maxResolution?: number; + enabled?: boolean; +} + +export interface LayersLink { + linkId: string; + links?: LayersLinkProperties[]; +} +export interface LayersLinkProperties { + bidirectionnal?: boolean; + linkedIds: string[]; + syncedDelete: boolean; + properties: LinkedProperties[]; +} + +export enum LinkedProperties { + OPACITY = 'opacity', + VISIBLE = 'visible', + OGCFILTERS = 'ogcFilters', + MINRESOLUTION = 'minResolution', + MAXRESOLUTION = 'maxResolution', + ZINDEX = 'zIndex', + TIMEFILTER = 'timeFilter' } export interface GroupLayers { diff --git a/packages/geo/src/lib/layer/shared/layers/layer.ts b/packages/geo/src/lib/layer/shared/layers/layer.ts index 1b38145f8b..96b57f9c27 100644 --- a/packages/geo/src/lib/layer/shared/layers/layer.ts +++ b/packages/geo/src/lib/layer/shared/layers/layer.ts @@ -17,6 +17,7 @@ import { IgoMap } from '../../../map/shared/map'; import { getResolutionFromScale } from '../../../map/shared/map.utils'; import { LayerOptions } from './layer.interface'; +import { LayerSyncWatcher } from '../../utils/layerSync-watcher'; export abstract class Layer { public collapsed: boolean; @@ -117,6 +118,8 @@ export abstract class Layer { return this.options.showInLayerList !== false; } + private layerSyncWatcher: LayerSyncWatcher; + constructor( public options: LayerOptions, protected authInterceptor?: AuthInterceptor @@ -162,6 +165,10 @@ export abstract class Layer { this.unobserveResolution(); if (igoMap !== undefined) { this.observeResolution(); + this.layerSyncWatcher = new LayerSyncWatcher(this, this.map); + this.layerSyncWatcher.subscribe(() => {}); + } else { + this.layerSyncWatcher.unsubscribe(); } } diff --git a/packages/geo/src/lib/layer/utils/index.ts b/packages/geo/src/lib/layer/utils/index.ts index 23128a43cd..84c438ea9e 100644 --- a/packages/geo/src/lib/layer/utils/index.ts +++ b/packages/geo/src/lib/layer/utils/index.ts @@ -1,4 +1,5 @@ export * from './image-watcher'; +export * from './layerSync-watcher'; export * from './tile-watcher'; export * from './outputLegend'; export * from './vector-watcher'; diff --git a/packages/geo/src/lib/layer/utils/layerSync-watcher.ts b/packages/geo/src/lib/layer/utils/layerSync-watcher.ts new file mode 100644 index 0000000000..0005dbf2c4 --- /dev/null +++ b/packages/geo/src/lib/layer/utils/layerSync-watcher.ts @@ -0,0 +1,266 @@ +import { Watcher } from '@igo2/utils'; +import { Layer } from '../shared/layers/layer'; +import olLayer from 'ol/layer/Layer'; +import { DataSource } from '../../datasource/shared/datasources/datasource'; +import { IgoMap } from '../../map/shared/map'; +import { LayersLink, LinkedProperties } from '../shared/layers/layer.interface'; +import { OgcFilterableDataSource, OgcFilterableDataSourceOptions } from '../../filter/shared/ogc-filter.interface'; +import { WMSDataSource } from '../../datasource/shared/datasources/wms-datasource'; +import { OgcFilterWriter } from '../../filter/shared/ogc-filter'; +import { TimeFilterableDataSource } from '../../filter/shared/time-filter.interface'; +import { Subscription } from 'rxjs'; +import { first } from 'rxjs/operators'; + +export class LayerSyncWatcher extends Watcher { + private ogcFilters$$: Subscription; + private timeFilter$$: Subscription; + + private ol: olLayer; + private layer: Layer; + private dataSource: DataSource; + private map: IgoMap; + private ogcFilterWriter; + + constructor(layer: Layer, map: IgoMap) { + super(); + this.ol = layer.ol; + this.layer = layer; + this.dataSource = layer.options.source; + this.map = map; + this.ogcFilterWriter = new OgcFilterWriter(); + } + + protected watch() { + + this.ol.on('propertychange', evt => this.transferCommonProperties(evt)); + if ((this.dataSource as OgcFilterableDataSource).ogcFilters$) { + this.ogcFilters$$ = (this.dataSource as OgcFilterableDataSource).ogcFilters$ + .subscribe(ogcFilters => this.transferOgcFiltersProperties(ogcFilters)); + } + if ((this.dataSource as TimeFilterableDataSource).timeFilter$) { + this.timeFilter$$ = (this.dataSource as TimeFilterableDataSource).timeFilter$ + .subscribe(timeFilter => this.transferTimeFilterProperties(timeFilter)); + } + this.syncChildLayers(); + } + + protected unwatch() { + this.ol.un('propertychange', evt => this.transferCommonProperties(evt)); + if (this.ogcFilters$$) { this.ogcFilters$$.unsubscribe(); } + if (this.timeFilter$$) { this.timeFilter$$.unsubscribe(); } + } + + + private syncChildLayers() { + // Force the sync the child layers with parent on the first load. + if (!this.map) { + return; + } + this.map.status$ + .pipe(first()) + .subscribe(() => { + this.map.layers + .filter(layer => layer.options.linkedLayers?.links) + .map(layer => { + layer.options.linkedLayers.links.map(link => { + if (link.properties?.indexOf(LinkedProperties.VISIBLE) !== -1) { + layer.ol.set('visible', !(layer.visible), false); + layer.ol.set('visible', !(layer.visible), false); + layer.visible = layer.visible; + } + if (link.properties?.indexOf(LinkedProperties.OPACITY) !== -1) { + const baseOpacity = layer.ol.get('opacity'); + layer.ol.set('opacity', 0, false); + layer.ol.set('opacity', baseOpacity, false); + layer.opacity = layer.opacity; + } + if (link.properties?.indexOf(LinkedProperties.MINRESOLUTION) !== -1) { + const baseMinResolution = layer.ol.get('minResolution'); + layer.ol.set('minResolution', 0, false); + layer.ol.set('minResolution', baseMinResolution, false); + layer.minResolution = layer.minResolution; + } + if (link.properties?.indexOf(LinkedProperties.MAXRESOLUTION) !== -1) { + const baseMaxResolution = layer.ol.get('maxResolution'); + layer.ol.set('maxResolution', 0, false); + layer.ol.set('maxResolution', baseMaxResolution, false); + layer.minResolution = layer.minResolution; + } + if (link.properties?.indexOf(LinkedProperties.OGCFILTERS) !== -1) { + const ogcFilters$ = (layer.dataSource as OgcFilterableDataSource).ogcFilters$; + ogcFilters$.next(ogcFilters$.value); + } + if (link.properties?.indexOf(LinkedProperties.TIMEFILTER) !== -1) { + const timeFilter$ = (layer.dataSource as TimeFilterableDataSource).timeFilter$; + timeFilter$.next(timeFilter$.value); + } + }); + }); + }); + } + + private transferCommonProperties(layerChange) { + const key = layerChange.key; + const layerChangeProperties = layerChange.target.getProperties(); + const newValue = layerChangeProperties[key]; + + if (['visible', 'opacity', 'minResolution', 'maxResolution'].indexOf(key) === -1) { + return; + } + const linkedLayers = layerChangeProperties.linkedLayers as LayersLink; + if (!linkedLayers) { + return; + } + const currentLinkedId = linkedLayers.linkId; + const currentLinks = linkedLayers.links; + const isParentLayer = currentLinks ? true : false; + + if (isParentLayer) { + // search for child layers + const silent = true; + currentLinks.map(link => { + if (!link.properties || link.properties.indexOf(key) === -1) { + return; + } + link.linkedIds.map(linkedId => { + const layerToApply = this.map.layers.find(layer => layer.options.linkedLayers?.linkId === linkedId); + if (layerToApply) { + layerToApply.ol.set(key, newValue, silent); + if (key === 'visible') { + layerToApply.visible$.next(newValue); + } + } + }); + }); + } else { + // search for parent layer + const silent = false; + this.map.layers.map(layer => { + if (layer.options.linkedLayers?.links) { + layer.options.linkedLayers.links.map(l => { + if (l.properties && l.properties.indexOf(key) !== -1 && + l.bidirectionnal !== false && l.linkedIds.indexOf(currentLinkedId) !== -1) { + layer.ol.set(key, newValue, silent); + if (key === 'visible') { + layer.visible$.next(newValue); + } + } + }); + } + }); + } + } + + private transferOgcFiltersProperties(ogcFilters) { + const linkedLayers = this.ol.getProperties().linkedLayers as LayersLink; + if (!linkedLayers) { + return; + } + const currentLinkedId = linkedLayers.linkId; + const currentLinks = linkedLayers.links; + const isParentLayer = currentLinks ? true : false; + if (isParentLayer) { + // search for child layers + currentLinks.map(link => { + if (!link.properties || link.properties.indexOf(LinkedProperties.OGCFILTERS) === -1) { + return; + } + link.linkedIds.map(linkedId => { + const layerToApply = this.map.layers.find(layer => layer.options.linkedLayers?.linkId === linkedId); + if (layerToApply) { + const layerType = layerToApply.ol.getProperties().sourceOptions.type; + (layerToApply.dataSource as OgcFilterableDataSource).setOgcFilters(ogcFilters, false); + if (layerType === 'wfs') { + layerToApply.ol.getSource().clear(); + layerToApply.ol.getSource().refresh(); + } + if (layerType === 'wms') { + const appliedOgcFilter = this.ol.values_.sourceOptions.params.FILTER; + (layerToApply.dataSource as WMSDataSource).ol.updateParams({ FILTER: appliedOgcFilter }); + } + } + }); + }); + } else { + // search for parent layer + this.map.layers.map(layer => { + if (layer.options.linkedLayers?.links) { + layer.options.linkedLayers.links.map(l => { + if (l.properties && l.properties.indexOf(LinkedProperties.OGCFILTERS) !== -1 && + l.bidirectionnal !== false && l.linkedIds.indexOf(currentLinkedId) !== -1) { + const layerType = layer.ol.getProperties().sourceOptions.type; + if (layerType === 'wfs') { + (layer.dataSource as OgcFilterableDataSource).setOgcFilters(ogcFilters, true); + layer.ol.getSource().clear(); + layer.ol.getSource().refresh(); + } + if (layerType === 'wms') { + let appliedOgcFilter = this.ol.values_.sourceOptions.params.FILTER; + if (this.ol.getProperties().sourceOptions.type === 'wfs') { + appliedOgcFilter = this.ogcFilterWriter.handleOgcFiltersAppliedValue( + layer.dataSource.options as OgcFilterableDataSourceOptions, + (this.dataSource.options as any).fieldNameGeometry, + this.map.viewController.getExtent(), + this.map.viewController.getOlProjection() + ); + + } + (layer.dataSource as WMSDataSource).ol.updateParams({ FILTER: appliedOgcFilter }); + (layer.dataSource as OgcFilterableDataSource).setOgcFilters(ogcFilters, true); + } + } + }); + } + }); + } + } + + private transferTimeFilterProperties(timeFilter) { + const linkedLayers = this.ol.getProperties().linkedLayers as LayersLink; + if (!linkedLayers) { + return; + } + const currentLinkedId = linkedLayers.linkId; + const currentLinks = linkedLayers.links; + const isParentLayer = currentLinks ? true : false; + if (isParentLayer) { + // search for child layers + currentLinks.map(link => { + if (!link.properties || link.properties.indexOf(LinkedProperties.TIMEFILTER) === -1) { + return; + } + link.linkedIds.map(linkedId => { + const childLayer = this.map.layers.find(layer => + layer.dataSource instanceof WMSDataSource && + layer.options.linkedLayers?.linkId === linkedId); + if (childLayer) { + (childLayer.dataSource as TimeFilterableDataSource).setTimeFilter(timeFilter, false); + const appliedTimeFilter = this.ol.values_.source.getParams().TIME; + (childLayer.dataSource as WMSDataSource).ol.updateParams({ TIME: appliedTimeFilter }); + } + }); + }); + } else { + // search for parent layer + this.map.layers + .filter(layer => layer.dataSource instanceof WMSDataSource) + .map(parentLayer => { + if (parentLayer.options.linkedLayers?.links) { + parentLayer.options.linkedLayers.links.map(l => { + if (l.properties && l.properties.indexOf(LinkedProperties.TIMEFILTER) !== -1 && + l.bidirectionnal !== false && l.linkedIds.indexOf(currentLinkedId) !== -1) { + const appliedTimeFilter = this.ol.values_.source.getParams().TIME; + (parentLayer.dataSource as WMSDataSource).ol.updateParams({ TIME: appliedTimeFilter }); + (parentLayer.dataSource as TimeFilterableDataSource).setTimeFilter(timeFilter, true); + } + }); + } + }); + } + } + + + + + +} diff --git a/packages/geo/src/lib/map/baselayers-switcher/mini-basemap.component.ts b/packages/geo/src/lib/map/baselayers-switcher/mini-basemap.component.ts index bdbe5af81a..e10d9872f7 100644 --- a/packages/geo/src/lib/map/baselayers-switcher/mini-basemap.component.ts +++ b/packages/geo/src/lib/map/baselayers-switcher/mini-basemap.component.ts @@ -6,7 +6,7 @@ import { ApplicationRef } from '@angular/core'; -import { Layer } from '../../layer/shared'; +import { Layer, LayerOptions } from '../../layer/shared'; import { LayerService } from '../../layer/shared/layer.service'; import { IgoMap } from '../shared'; @@ -101,5 +101,36 @@ export class MiniBaseMapComponent implements AfterViewInit, OnDestroy { const layer = this.layerService.createLayer(options); this.basemap.addLayer(layer); + this.handleLinkedBaseLayer(layer); + } + + private handleLinkedBaseLayer(baselayer: Layer) { + const linkedLayers = baselayer.options.linkedLayers; + if (!linkedLayers) { + return; + } + const currentLinkedId = linkedLayers.linkId; + const currentLinks = linkedLayers.links; + const isParentLayer = currentLinks ? true : false; + if (isParentLayer && currentLinkedId === baselayer.options.linkedLayers.linkId) { + // search for child layers + currentLinks.map(link => { + link.linkedIds.map(linkedId => { + const layerToApply = this.map.layers.find(l => l.options.linkedLayers?.linkId === linkedId); + if (layerToApply) { + const linkedLayerOptions: any = Object.assign( + Object.create(layerToApply.options), + layerToApply.options, + { + zIndex: 9000, + visible: true, + baseLayer: false, + } as LayerOptions + ); + this.basemap.addLayer(this.layerService.createLayer(linkedLayerOptions)); + } + }); + }); + } } } diff --git a/packages/geo/src/lib/map/shared/map.ts b/packages/geo/src/lib/map/shared/map.ts index 1cac12795c..b75deae0b3 100644 --- a/packages/geo/src/lib/map/shared/map.ts +++ b/packages/geo/src/lib/map/shared/map.ts @@ -277,6 +277,14 @@ export class IgoMap { if (index >= 0) { layersToRemove.push(layer); newLayers.splice(index, 1); + this.handleLinkedLayersDeletion(layer, layersToRemove); + layersToRemove.map(linkedLayer => { + layersToRemove.push(linkedLayer); + const linkedIndex = newLayers.indexOf(linkedLayer); + if (linkedIndex >= 0) { + newLayers.splice(linkedIndex, 1); + } + }); } }); @@ -284,6 +292,49 @@ export class IgoMap { this.setLayers(newLayers); } + /** + * Build a list of linked layers to delete + * @param srcLayer Layer that has triggered the deletion + * @param layersToRemove list to append the layer to delete into + */ + handleLinkedLayersDeletion(srcLayer: Layer, layersToRemove: Layer[]) { + const linkedLayers = srcLayer.options.linkedLayers; + if (!linkedLayers) { + return; + } + const currentLinkedId = linkedLayers.linkId; + const currentLinks = linkedLayers.links; + const isParentLayer = currentLinks ? true : false; + if (isParentLayer) { + // search for child layers + currentLinks.map(link => { + if (!link.syncedDelete) { + return; + } + link.linkedIds.map(linkedId => { + const layerToApply = this.layers.find(layer => layer.options.linkedLayers?.linkId === linkedId); + if (layerToApply) { + layersToRemove.push(layerToApply); + } + }); + }); + } else { + // search for parent layer + this.layers.map(layer => { + if (layer.options.linkedLayers?.links) { + layer.options.linkedLayers.links.map(l => { + if ( + l.syncedDelete && l.bidirectionnal !== false && + l.linkedIds.indexOf(currentLinkedId) !== -1) { + layersToRemove.push(layer); + this.handleLinkedLayersDeletion(layer, layersToRemove); + } + }); + } + }); + } + } + /** * Remove all layers */ diff --git a/packages/geo/src/lib/workspace/shared/feature-workspace.service.ts b/packages/geo/src/lib/workspace/shared/feature-workspace.service.ts index 73f339fd91..c7a5958f1c 100644 --- a/packages/geo/src/lib/workspace/shared/feature-workspace.service.ts +++ b/packages/geo/src/lib/workspace/shared/feature-workspace.service.ts @@ -6,7 +6,8 @@ import { EntityStoreFilterCustomFuncStrategy, EntityRecord, EntityStoreStrategyFuncOptions, - EntityStoreFilterSelectionStrategy + EntityStoreFilterSelectionStrategy, + EntityTableColumnRenderer } from '@igo2/common'; import { @@ -19,6 +20,7 @@ import { FeatureStoreInMapResolutionStrategy } from '../../feature'; import { VectorLayer } from '../../layer'; +import { GeoWorkspaceOptions } from '../../layer/shared/layers/layer.interface'; import { IgoMap } from '../../map'; import { SourceFieldsOptionsParams, FeatureDataSource } from '../../datasource'; @@ -39,6 +41,19 @@ export class FeatureWorkspaceService { constructor(private storageService: StorageService) {} createWorkspace(layer: VectorLayer, map: IgoMap): FeatureWorkspace { + if (layer.options.workspace?.enabled === false) { + return; + } + layer.options.workspace = Object.assign({}, layer.options.workspace, {enabled: true}); + + layer.options.workspace = Object.assign({}, layer.options.workspace, + { + enabled: true, + srcId: layer.id, + workspaceId: layer.id + } as GeoWorkspaceOptions); + + const wks = new FeatureWorkspace({ id: layer.id, title: layer.title, @@ -105,7 +120,8 @@ export class FeatureWorkspaceService { .map(key => { return { name: `properties.${key}`, - title: key + title: key, + renderer: EntityTableColumnRenderer.UnsanitizedHTML }; }); workspace.meta.tableTemplate = { @@ -119,7 +135,8 @@ export class FeatureWorkspaceService { const columns = fields.map((field: SourceFieldsOptionsParams) => { return { name: `properties.${field.name}`, - title: field.alias ? field.alias : field.name + title: field.alias ? field.alias : field.name, + renderer: EntityTableColumnRenderer.UnsanitizedHTML }; }); workspace.meta.tableTemplate = { diff --git a/packages/geo/src/lib/workspace/shared/wfs-workspace.service.ts b/packages/geo/src/lib/workspace/shared/wfs-workspace.service.ts index b80632ba89..8acf83e516 100644 --- a/packages/geo/src/lib/workspace/shared/wfs-workspace.service.ts +++ b/packages/geo/src/lib/workspace/shared/wfs-workspace.service.ts @@ -6,7 +6,8 @@ import { EntityStoreFilterCustomFuncStrategy, EntityRecord, EntityStoreStrategyFuncOptions, - EntityStoreFilterSelectionStrategy + EntityStoreFilterSelectionStrategy, + EntityTableColumnRenderer } from '@igo2/common'; import { @@ -19,6 +20,7 @@ import { FeatureStoreInMapResolutionStrategy } from '../../feature'; import { VectorLayer } from '../../layer'; +import { GeoWorkspaceOptions } from '../../layer/shared/layers/layer.interface'; import { IgoMap } from '../../map'; import { SourceFieldsOptionsParams, FeatureDataSource } from '../../datasource'; @@ -35,11 +37,20 @@ export class WfsWorkspaceService { return this.storageService.get('zoomAuto') as boolean; } - constructor( - private storageService: StorageService - ) {} + constructor(private storageService: StorageService) {} + + createWorkspace(layer: VectorLayer, map: IgoMap): WfsWorkspace { + if (layer.options.workspace?.enabled === false) { + return; + } + + layer.options.workspace = Object.assign({}, layer.options.workspace, + { + enabled: true, + srcId: layer.id, + workspaceId: layer.id + } as GeoWorkspaceOptions); - createWorkspace(layer: VectorLayer, map: IgoMap): WfsWorkspace { const wks = new WfsWorkspace({ id: layer.id, title: layer.title, @@ -106,7 +117,8 @@ export class WfsWorkspaceService { .map(key => { return { name: `properties.${key}`, - title: key + title: key, + renderer: EntityTableColumnRenderer.UnsanitizedHTML }; }); workspace.meta.tableTemplate = { @@ -120,7 +132,8 @@ export class WfsWorkspaceService { const columns = fields.map((field: SourceFieldsOptionsParams) => { return { name: `properties.${field.name}`, - title: field.alias ? field.alias : field.name + title: field.alias ? field.alias : field.name, + renderer: EntityTableColumnRenderer.UnsanitizedHTML }; }); workspace.meta.tableTemplate = { diff --git a/packages/geo/src/lib/workspace/shared/wms-actions.service.ts b/packages/geo/src/lib/workspace/shared/wms-actions.service.ts deleted file mode 100644 index ae744d74b3..0000000000 --- a/packages/geo/src/lib/workspace/shared/wms-actions.service.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Inject, Injectable } from '@angular/core'; - -import { Action, Widget } from '@igo2/common'; - -import { OgcFilterWidget } from '../widgets'; -import { WmsWorkspace } from './wms-workspace'; - -@Injectable({ - providedIn: 'root' -}) -export class WmsActionsService { - - constructor( - @Inject(OgcFilterWidget) private ogcFilterWidget: Widget - ) {} - - buildActions(workspace: WmsWorkspace): Action[] { - return [ - { - id: 'ogcFilter', - icon: 'filter-list', - title: 'igo.geo.workspace.ogcFilter.title', - tooltip: 'igo.geo.workspace.ogcFilter.tooltip', - handler: (widget: Widget, ws: WmsWorkspace) => { - ws.activateWidget(widget, { - map: ws.map, - layer: ws.layer - }); - }, - args: [this.ogcFilterWidget, workspace] - } - ]; - } -} diff --git a/packages/geo/src/lib/workspace/shared/wms-workspace.service.ts b/packages/geo/src/lib/workspace/shared/wms-workspace.service.ts index 7ac14362e9..5009a73a34 100644 --- a/packages/geo/src/lib/workspace/shared/wms-workspace.service.ts +++ b/packages/geo/src/lib/workspace/shared/wms-workspace.service.ts @@ -1,32 +1,210 @@ import { Injectable } from '@angular/core'; +import { ActionStore, EntityRecord, EntityStoreFilterCustomFuncStrategy, EntityStoreFilterSelectionStrategy, EntityStoreStrategyFuncOptions, EntityTableColumnRenderer, EntityTableTemplate } from '@igo2/common'; +import { StorageScope, StorageService } from '@igo2/core'; +import { skipWhile, take } from 'rxjs/operators'; +import { SourceFieldsOptionsParams } from '../../datasource'; +import { FeatureDataSource } from '../../datasource/shared/datasources/feature-datasource'; +import { WFSDataSourceOptions } from '../../datasource/shared/datasources/wfs-datasource.interface'; +import { Feature, FeatureMotion, FeatureStore, FeatureStoreInMapExtentStrategy, FeatureStoreInMapResolutionStrategy, FeatureStoreLoadingLayerStrategy, FeatureStoreSelectionStrategy } from '../../feature'; -import { ActionStore } from '@igo2/common'; - -import { - FeatureStore, - FeatureStoreLoadingLayerStrategy, - FeatureStoreSelectionStrategy -} from '../../feature'; -import { ImageLayer } from '../../layer'; +import { OgcFilterableDataSourceOptions } from '../../filter/shared/ogc-filter.interface'; +import { ImageLayer, LayerService, LayersLinkProperties, LinkedProperties, VectorLayer } from '../../layer'; +import { GeoWorkspaceOptions } from '../../layer/shared/layers/layer.interface'; import { IgoMap } from '../../map'; - -import { WmsWorkspace } from './wms-workspace'; +import { QueryableDataSourceOptions } from '../../query/shared/query.interfaces'; +import { WfsWorkspace } from './wfs-workspace'; @Injectable({ providedIn: 'root' }) export class WmsWorkspaceService { - constructor() {} + get zoomAuto(): boolean { + return this.storageService.get('zoomAuto') as boolean; + } + + constructor(private layerService: LayerService, private storageService: StorageService) { } + + createWorkspace(layer: ImageLayer, map: IgoMap): WfsWorkspace { + if (layer.options.workspace?.enabled !== true) { + return; + } + const wmsLinkId = layer.id + '.WmsWorkspaceTableSrc'; + const wfsLinkId = layer.id + '.WfsWorkspaceTableDest'; + if (!layer.options.linkedLayers) { + layer.options.linkedLayers = { linkId: wmsLinkId, links: [] }; + } + const linkProperties = { + bidirectionnal: true, + syncedDelete: true, + linkedIds: [wfsLinkId], + properties: [ + LinkedProperties.ZINDEX, + LinkedProperties.VISIBLE] + } as LayersLinkProperties; + + if (!layer.options.workspace?.minResolution) { + linkProperties.properties.push(LinkedProperties.MINRESOLUTION); + } + let hasOgcFilters = false; + if ((layer.dataSource.options as OgcFilterableDataSourceOptions).ogcFilters?.enabled) { + linkProperties.properties.push(LinkedProperties.OGCFILTERS); + hasOgcFilters = true; + } + if (!layer.options.workspace?.maxResolution) { + linkProperties.properties.push(LinkedProperties.MAXRESOLUTION); + } + + let clonedLinks: LayersLinkProperties[] = []; + if (layer.options.linkedLayers.links) { + clonedLinks = JSON.parse(JSON.stringify(layer.options.linkedLayers.links)); + } + clonedLinks.push(linkProperties); + + layer.options.linkedLayers.linkId = layer.options.linkedLayers.linkId ? layer.options.linkedLayers.linkId : wmsLinkId, + layer.options.linkedLayers.links = clonedLinks; + interface WFSoptions extends WFSDataSourceOptions, OgcFilterableDataSourceOptions { } + let wks; + this.layerService + .createAsyncLayer({ + linkedLayers: { + linkId: wfsLinkId + }, + workspace: { + srcId: layer.id, + workspaceId: undefined, + enabled: false, + }, + showInLayerList: false, + opacity: 0, + title: layer.title, + minResolution: layer.options.workspace?.minResolution || layer.minResolution || 0, + maxResolution: layer.options.workspace?.maxResolution || layer.maxResolution || Infinity, + sourceOptions: { + download: layer.dataSource.options.download, + type: 'wfs', + url: layer.dataSource.options.url || layer.dataSource.options.url, + queryable: true, + queryTitle: (layer.dataSource.options as QueryableDataSourceOptions).queryTitle, + params: layer.dataSource.options.paramsWFS, + ogcFilters: Object.assign({}, layer.dataSource.ogcFilters$.value, {enabled: hasOgcFilters}), + sourceFields: layer.dataSource.options.sourceFields || undefined + } as WFSoptions + }) + .subscribe((workspaceLayer: VectorLayer) => { + map.addLayer(workspaceLayer); + layer.ol.setProperties({ linkedLayers: { linkId: layer.options.linkedLayers.linkId, links: clonedLinks } }, false); + workspaceLayer.dataSource.ol.refresh(); + + wks = new WfsWorkspace({ + id: layer.id, + title: layer.title, + layer: workspaceLayer, + map, + entityStore: this.createFeatureStore(workspaceLayer, map), + actionStore: new ActionStore([]), + meta: { + tableTemplate: undefined + } + }); + this.createTableTemplate(wks, workspaceLayer); + + workspaceLayer.options.workspace.workspaceId = workspaceLayer.id; + layer.options.workspace = Object.assign({}, layer.options.workspace, + { + enabled: true, + srcId: layer.id, + workspaceId: workspaceLayer.id + } as GeoWorkspaceOptions); - createWorkspace(layer: ImageLayer, map: IgoMap): WmsWorkspace { - return new WmsWorkspace({ - id: layer.id, - title: layer.title, - layer, + delete layer.dataSource.options.download; + return wks; + + }); + + return wks; + } + + private createFeatureStore(layer: VectorLayer, map: IgoMap): FeatureStore { + const store = new FeatureStore([], { map }); + store.bindLayer(layer); + + const loadingStrategy = new FeatureStoreLoadingLayerStrategy({}); + const inMapExtentStrategy = new FeatureStoreInMapExtentStrategy({}); + const inMapResolutionStrategy = new FeatureStoreInMapResolutionStrategy({}); + const selectedRecordStrategy = new EntityStoreFilterSelectionStrategy({}); + const selectionStrategy = new FeatureStoreSelectionStrategy({ + layer: new VectorLayer({ + zIndex: 300, + source: new FeatureDataSource(), + style: undefined, + showInLayerList: false, + exportable: false, + browsable: false + }), map, - actionStore: new ActionStore([]) + hitTolerance: 15, + motion: this.zoomAuto ? FeatureMotion.Default : FeatureMotion.None, + many: true, + dragBox: true }); + this.storageService.set('rowsInMapExtent', true, StorageScope.SESSION); + store.addStrategy(loadingStrategy, true); + store.addStrategy(inMapExtentStrategy, true); + store.addStrategy(inMapResolutionStrategy, true); + store.addStrategy(selectionStrategy, true); + store.addStrategy(selectedRecordStrategy, false); + store.addStrategy(this.createFilterInMapExtentOrResolutionStrategy(), true); + return store; } + private createTableTemplate(workspace: WfsWorkspace, layer: VectorLayer): EntityTableTemplate { + const fields = layer.dataSource.options.sourceFields || []; + + if (fields.length === 0) { + workspace.entityStore.entities$.pipe( + skipWhile(val => val.length === 0), + take(1) + ).subscribe(entities => { + const columnsFromFeatures = (entities[0] as Feature).ol.getKeys() + .filter( + col => !col.startsWith('_') && + col !== 'geometry' && + col !== (entities[0] as Feature).ol.getGeometryName() && + !col.match(/boundedby/gi)) + .map(key => { + return { + name: `properties.${key}`, + title: key, + renderer: EntityTableColumnRenderer.UnsanitizedHTML + }; + }); + workspace.meta.tableTemplate = { + selection: true, + sort: true, + columns: columnsFromFeatures + }; + }); + return; + } + const columns = fields.map((field: SourceFieldsOptionsParams) => { + return { + name: `properties.${field.name}`, + title: field.alias ? field.alias : field.name, + renderer: EntityTableColumnRenderer.UnsanitizedHTML + }; + }); + workspace.meta.tableTemplate = { + selection: true, + sort: true, + columns + }; + } + + private createFilterInMapExtentOrResolutionStrategy(): EntityStoreFilterCustomFuncStrategy { + const filterClauseFunc = (record: EntityRecord) => { + return record.state.inMapExtent === true && record.state.inMapResolution === true; + }; + return new EntityStoreFilterCustomFuncStrategy({filterClauseFunc} as EntityStoreStrategyFuncOptions); + } } diff --git a/packages/geo/src/lib/workspace/shared/workspace.utils.ts b/packages/geo/src/lib/workspace/shared/workspace.utils.ts index 91284adb28..8412a4f5e5 100644 --- a/packages/geo/src/lib/workspace/shared/workspace.utils.ts +++ b/packages/geo/src/lib/workspace/shared/workspace.utils.ts @@ -11,12 +11,6 @@ export function mapExtentStrategyActiveToolTip(ws: WfsWorkspace | FeatureWorkspa ); } -export function featureMotionStrategyActiveToolTip(ws: WfsWorkspace | FeatureWorkspace): Observable { - return ws.entityStore.getStrategyOfType(EntityStoreFilterCustomFuncStrategy).active$.pipe( - map((active: boolean) => active ? 'igo.geo.workspace.zoomAuto.tooltip' : 'igo.geo.workspace.zoomAuto.tooltip') - ); -} - export function noElementSelected(ws: WfsWorkspace | FeatureWorkspace): Observable { return ws.entityStore.stateView.manyBy$((record: EntityRecord) => { return record.state.selected === true; diff --git a/packages/geo/src/lib/workspace/workspace-selector/workspace-selector.directive.ts b/packages/geo/src/lib/workspace/workspace-selector/workspace-selector.directive.ts index 906ac5f927..49ab57d0c8 100644 --- a/packages/geo/src/lib/workspace/workspace-selector/workspace-selector.directive.ts +++ b/packages/geo/src/lib/workspace/workspace-selector/workspace-selector.directive.ts @@ -11,9 +11,10 @@ import { WFSDataSource, WMSDataSource, FeatureDataSource } from '../../datasourc import { OgcFilterableDataSourceOptions } from '../../filter'; import { WfsWorkspaceService } from '../shared/wfs-workspace.service'; -// import { WmsWorkspaceService } from '../shared/wms-workspace.service'; +import { WmsWorkspaceService } from '../shared/wms-workspace.service'; import { FeatureWorkspaceService } from '../shared/feature-workspace.service'; import { FeatureStoreInMapExtentStrategy } from '../../feature/shared/strategies/in-map-extent'; +import { QueryableDataSourceOptions } from '../../query/shared/query.interfaces'; @Directive({ selector: '[igoWorkspaceSelector]' @@ -32,7 +33,7 @@ export class WorkspaceSelectorDirective implements OnInit, OnDestroy { constructor( private component: WorkspaceSelectorComponent, private wfsWorkspaceService: WfsWorkspaceService, - // private wmsWorkspaceService: WmsWorkspaceService, + private wmsWorkspaceService: WmsWorkspaceService, private featureWorkspaceService: FeatureWorkspaceService ) {} @@ -86,8 +87,14 @@ export class WorkspaceSelectorDirective implements OnInit, OnDestroy { if (layer.dataSource instanceof WFSDataSource) { const wfsWks = this.wfsWorkspaceService.createWorkspace(layer as VectorLayer, this.map); return wfsWks; - /* } else if (layer.dataSource instanceof WMSDataSource) { - return this.wmsWorkspaceService.createWorkspace(layer as ImageLayer, this.map);*/ + } else if (layer.dataSource instanceof WMSDataSource) { + if (!layer.dataSource.options.paramsWFS) { return; } + const wmsWks = this.wmsWorkspaceService.createWorkspace(layer as ImageLayer, this.map); + wmsWks?.inResolutionRange$.subscribe((inResolutionRange) => { + (layer.dataSource.options as QueryableDataSourceOptions).queryable = !inResolutionRange; + (wmsWks.layer.dataSource.options as QueryableDataSourceOptions).queryable = inResolutionRange; + }); + return wmsWks; } else if (layer.dataSource instanceof FeatureDataSource && (layer as VectorLayer).exportable === true) { const featureWks = this.featureWorkspaceService.createWorkspace(layer as VectorLayer, this.map); return featureWks; @@ -107,9 +114,7 @@ export class WorkspaceSelectorDirective implements OnInit, OnDestroy { if (dataSource instanceof WMSDataSource) { const dataSourceOptions = (dataSource.options || {}) as OgcFilterableDataSourceOptions; - return ( - dataSourceOptions.ogcFilters && dataSourceOptions.ogcFilters.enabled - ); + return (dataSourceOptions.ogcFilters?.enabled || dataSource.options.paramsWFS?.featureTypes !== undefined); } return false; diff --git a/packages/geo/src/lib/workspace/workspace-updator/workspace-updator.directive.ts b/packages/geo/src/lib/workspace/workspace-updator/workspace-updator.directive.ts index 82cfc9064a..29423ab046 100644 --- a/packages/geo/src/lib/workspace/workspace-updator/workspace-updator.directive.ts +++ b/packages/geo/src/lib/workspace/workspace-updator/workspace-updator.directive.ts @@ -11,9 +11,10 @@ import { WFSDataSource, WMSDataSource, FeatureDataSource } from '../../datasourc import { OgcFilterableDataSourceOptions } from '../../filter'; import { WfsWorkspaceService } from '../shared/wfs-workspace.service'; -// import { WmsWorkspaceService } from '../shared/wms-workspace.service'; +import { WmsWorkspaceService } from '../shared/wms-workspace.service'; import { FeatureWorkspaceService } from '../shared/feature-workspace.service'; import { FeatureStoreInMapExtentStrategy } from '../../feature/shared/strategies/in-map-extent'; +import { QueryableDataSourceOptions } from '../../query/shared/query.interfaces'; @Directive({ selector: '[igoWorkspaceUpdator]' @@ -29,7 +30,7 @@ export class WorkspaceUpdatorDirective implements OnInit, OnDestroy { constructor( private wfsWorkspaceService: WfsWorkspaceService, - // private wmsWorkspaceService: WmsWorkspaceService, + private wmsWorkspaceService: WmsWorkspaceService, private featureWorkspaceService: FeatureWorkspaceService ) {} @@ -83,8 +84,14 @@ export class WorkspaceUpdatorDirective implements OnInit, OnDestroy { if (layer.dataSource instanceof WFSDataSource) { const wfsWks = this.wfsWorkspaceService.createWorkspace(layer as VectorLayer, this.map); return wfsWks; - /*} else if (layer.dataSource instanceof WMSDataSource) { - return this.wmsWorkspaceService.createWorkspace(layer as ImageLayer, this.map);*/ + } else if (layer.dataSource instanceof WMSDataSource) { + if (!layer.dataSource.options.paramsWFS) { return; } + const wmsWks = this.wmsWorkspaceService.createWorkspace(layer as ImageLayer, this.map); + wmsWks?.inResolutionRange$.subscribe((inResolutionRange) => { + (layer.dataSource.options as QueryableDataSourceOptions).queryable = !inResolutionRange; + (wmsWks.layer.dataSource.options as QueryableDataSourceOptions).queryable = inResolutionRange; + }); + return wmsWks; } else if (layer.dataSource instanceof FeatureDataSource && (layer as VectorLayer).exportable === true) { const featureWks = this.featureWorkspaceService.createWorkspace(layer as VectorLayer, this.map); return featureWks; @@ -104,9 +111,7 @@ export class WorkspaceUpdatorDirective implements OnInit, OnDestroy { if (dataSource instanceof WMSDataSource) { const dataSourceOptions = (dataSource.options || {}) as OgcFilterableDataSourceOptions; - return ( - dataSourceOptions.ogcFilters && dataSourceOptions.ogcFilters.enabled - ); + return (dataSourceOptions.ogcFilters?.enabled || dataSource.options.paramsWFS?.featureTypes !== undefined); } return false; diff --git a/packages/geo/src/locale/en.geo.json b/packages/geo/src/locale/en.geo.json index 516a414694..bfe1a83ad9 100644 --- a/packages/geo/src/locale/en.geo.json +++ b/packages/geo/src/locale/en.geo.json @@ -546,23 +546,6 @@ "modify.tooltip": "Modify" } }, - "workspace": { - "ogcFilter.close": "Close", - "ogcFilter.title": "Filters", - "ogcFilter.tooltip": "Apply filters", - "download.title": "Download", - "download.tooltip": "Download", - "inMapExtent.title": "Show records contained in map", - "inMapExtent.active.tooltip": "Show records contained in map", - "inMapExtent.inactive.tooltip": "Show all records", - "zoomAuto.title": "Zoom auto", - "zoomAuto.tooltip": "Zoom auto", - "selected.title": "Show selected records only", - "selected.tooltip": "Show selected records only", - "clearSelection.title": "Deselect all", - "clearSelection.tooltip": "Deselect all records" - - }, "network": { "online": { "message": "Online", @@ -572,6 +555,10 @@ "message": "Offline", "title": "Status:" } + }, + "workspace": { + "inMapExtent.active.tooltip": "Ne montrer que les enregistrements contenus dans la carte", + "inMapExtent.inactive.tooltip": "Montrer tous les enregistrements" } } } diff --git a/packages/geo/src/locale/fr.geo.json b/packages/geo/src/locale/fr.geo.json index 8c0b38a0f5..69831d59d8 100644 --- a/packages/geo/src/locale/fr.geo.json +++ b/packages/geo/src/locale/fr.geo.json @@ -547,22 +547,6 @@ "modify.tooltip": "Modifier" } }, - "workspace": { - "ogcFilter.close": "Fermer", - "ogcFilter.title": "Filtres", - "ogcFilter.tooltip": "Appliquer des filtres sur la couche", - "download.title": "Télécharger les données associées", - "download.tooltip": "Télécharger les données associées", - "inMapExtent.title": "Ne montrer que les enregistrements contenus dans la carte", - "inMapExtent.active.tooltip": "Ne montrer que les enregistrements contenus dans la carte", - "inMapExtent.inactive.tooltip": "Montrer tous les enregistrements", - "zoomAuto.title": "Zoom auto", - "zoomAuto.tooltip": "Zoom auto", - "selected.title": "Ne montrer que les enregistrements sélectionnés", - "selected.tooltip": "Ne montrer que les enregistrements sélectionnés", - "clearSelection.title": "Tout désélectionner", - "clearSelection.tooltip": "Désélectionner les enregistrements sélectionnés" - }, "network": { "online": { "message": "En ligne", @@ -572,6 +556,10 @@ "message": "Hors-Ligne", "title": "Statut:" } + }, + "workspace": { + "inMapExtent.active.tooltip": "Ne montrer que les enregistrements contenus dans la carte", + "inMapExtent.inactive.tooltip": "Montrer tous les enregistrements" } } } diff --git a/packages/integration/src/lib/filter/spatial-filter-tool/spatial-filter-tool.component.ts b/packages/integration/src/lib/filter/spatial-filter-tool/spatial-filter-tool.component.ts index 8550f75031..147568628b 100644 --- a/packages/integration/src/lib/filter/spatial-filter-tool/spatial-filter-tool.component.ts +++ b/packages/integration/src/lib/filter/spatial-filter-tool/spatial-filter-tool.component.ts @@ -151,7 +151,7 @@ export class SpatialFilterToolComponent implements OnDestroy { if (layerToOpenWks) { this.workspaceState.workspacePanelExpanded = true; - this.workspaceState.setActiveWorkspaceByLayerId(layerToOpenWks.id); + this.workspaceState.setActiveWorkspaceById(layerToOpenWks.id); } } }); @@ -344,6 +344,7 @@ export class SpatialFilterToolComponent implements OnDestroy { .subscribe((dataSource: DataSource) => { const olLayer = this.layerService.createLayer({ title: ('Zone ' + i) as string, + workspace: { enabled: true }, _internal: { code: this.type === SpatialFilterType.Predefined diff --git a/packages/integration/src/lib/map/map-details-tool/map-details-tool.component.html b/packages/integration/src/lib/map/map-details-tool/map-details-tool.component.html index e659376767..1e514dbe6a 100644 --- a/packages/integration/src/lib/map/map-details-tool/map-details-tool.component.html +++ b/packages/integration/src/lib/map/map-details-tool/map-details-tool.component.html @@ -12,7 +12,7 @@ - + diff --git a/packages/integration/src/lib/map/map-details-tool/map-details-tool.component.ts b/packages/integration/src/lib/map/map-details-tool/map-details-tool.component.ts index ad9a22e623..cb7271a565 100644 --- a/packages/integration/src/lib/map/map-details-tool/map-details-tool.component.ts +++ b/packages/integration/src/lib/map/map-details-tool/map-details-tool.component.ts @@ -133,7 +133,11 @@ export class MapDetailsToolComponent implements OnInit { this.toolState.toolbox.activateTool('contextManager'); } - activateExport(id: string) { + activateExport(layer: Layer) { + let id = layer.id; + if (layer.options.workspace?.workspaceId) { + id = layer.options.workspace.workspaceId !== layer.id ? layer.options.workspace.workspaceId : layer.id; + } this.importExportState.setsExportOptions({ layers: [id] } as ExportOptions); this.importExportState.setMode('export'); this.toolState.toolbox.activateTool('importExport'); diff --git a/packages/integration/src/lib/map/map-tool/map-tool.component.html b/packages/integration/src/lib/map/map-tool/map-tool.component.html index ce6b212a80..6a7719a3e8 100644 --- a/packages/integration/src/lib/map/map-tool/map-tool.component.html +++ b/packages/integration/src/lib/map/map-tool/map-tool.component.html @@ -15,7 +15,7 @@ - + diff --git a/packages/integration/src/lib/map/map-tool/map-tool.component.ts b/packages/integration/src/lib/map/map-tool/map-tool.component.ts index c3d1102586..1b8f7b0348 100644 --- a/packages/integration/src/lib/map/map-tool/map-tool.component.ts +++ b/packages/integration/src/lib/map/map-tool/map-tool.component.ts @@ -5,7 +5,8 @@ import { LayerListControlsEnum, LayerListControlsOptions, IgoMap, - ExportOptions + ExportOptions, + Layer } from '@igo2/geo'; import { MapState } from './../map.state'; import { ImportExportState } from '../../import-export/import-export.state'; @@ -75,7 +76,11 @@ export class MapToolComponent { private importExportState: ImportExportState ) {} - activateExport(id: string) { + activateExport(layer: Layer) { + let id = layer.id; + if (layer.options.workspace?.workspaceId) { + id = layer.options.workspace.workspaceId !== layer.id ? layer.options.workspace.workspaceId : layer.id; + } this.importExportState.setsExportOptions({ layers: [id] } as ExportOptions); this.importExportState.setMode('export'); this.toolState.toolbox.activateTool('importExport'); diff --git a/packages/integration/src/lib/map/map-tools/map-tools.component.html b/packages/integration/src/lib/map/map-tools/map-tools.component.html index e4899ce73a..69a7b793fb 100644 --- a/packages/integration/src/lib/map/map-tools/map-tools.component.html +++ b/packages/integration/src/lib/map/map-tools/map-tools.component.html @@ -19,7 +19,7 @@ - + { this.handleZoomAuto(workspace); @@ -88,7 +87,7 @@ export class FeatureActionsService implements OnDestroy { { id: 'filterInMapExtent', checkbox: true, - title: 'igo.geo.workspace.inMapExtent.title', + title: 'igo.integration.workspace.inMapExtent.title', tooltip: mapExtentStrategyActiveToolTip(workspace), checkCondition: this.rowsInMapExtent, handler: () => { @@ -110,8 +109,8 @@ export class FeatureActionsService implements OnDestroy { { id: 'selectedOnly', checkbox: true, - title: 'igo.geo.workspace.selected.title', - tooltip: 'igo.geo.workspace.selected.tooltip', + title: 'igo.integration.workspace.selected.title', + tooltip: 'igo.integration.workspace.selected.tooltip', checkCondition: false, handler: () => { const filterStrategy = workspace.entityStore.getStrategyOfType( @@ -127,8 +126,8 @@ export class FeatureActionsService implements OnDestroy { { id: 'clearselection', icon: 'select-off', - title: 'igo.geo.workspace.clearSelection.title', - tooltip: 'igo.geo.workspace.clearSelection.tooltip', + title: 'igo.integration.workspace.clearSelection.title', + tooltip: 'igo.integration.workspace.clearSelection.tooltip', handler: (ws: FeatureWorkspace) => { ws.entityStore.state.updateMany(ws.entityStore.view.all(), { selected: false @@ -139,9 +138,9 @@ export class FeatureActionsService implements OnDestroy { }, { id: 'featureDownload', - icon: 'download', - title: 'igo.geo.workspace.download.title', - tooltip: 'igo.geo.workspace.download.tooltip', + icon: 'file-export', + title: 'igo.integration.workspace.download.title', + tooltip: 'igo.integration.workspace.download.tooltip', handler: (ws: FeatureWorkspace) => { const filterStrategy = ws.entityStore.getStrategyOfType( EntityStoreFilterCustomFuncStrategy diff --git a/packages/integration/src/lib/workspace/shared/wfs-actions.service.ts b/packages/integration/src/lib/workspace/shared/wfs-actions.service.ts index 00c87fdaad..dfbdb902d3 100644 --- a/packages/integration/src/lib/workspace/shared/wfs-actions.service.ts +++ b/packages/integration/src/lib/workspace/shared/wfs-actions.service.ts @@ -5,12 +5,12 @@ import { BehaviorSubject, Subscription } from 'rxjs'; import { WfsWorkspace, mapExtentStrategyActiveToolTip, - featureMotionStrategyActiveToolTip, FeatureStoreSelectionStrategy, FeatureMotion, noElementSelected, ExportOptions, - OgcFilterWidget + OgcFilterWidget, + OgcFilterableDataSource } from '@igo2/geo'; import { StorageService, StorageScope, StorageServiceEvent } from '@igo2/core'; import { StorageState } from '../../storage/storage.state'; @@ -62,12 +62,12 @@ export class WfsActionsService implements OnDestroy { this.handleZoomAuto(workspace); } ); - return [ + const actions = [ { id: 'zoomAuto', checkbox: true, - title: 'igo.geo.workspace.zoomAuto.title', - tooltip: featureMotionStrategyActiveToolTip(workspace), + title: 'igo.integration.workspace.zoomAuto.title', + tooltip: 'igo.integration.workspace.zoomAuto.tooltip', checkCondition: this.zoomAuto$, handler: () => { this.handleZoomAuto(workspace); @@ -77,7 +77,7 @@ export class WfsActionsService implements OnDestroy { { id: 'filterInMapExtent', checkbox: true, - title: 'igo.geo.workspace.inMapExtent.title', + title: 'igo.integration.workspace.inMapExtent.title', tooltip: mapExtentStrategyActiveToolTip(workspace), checkCondition: this.rowsInMapExtent, handler: () => { @@ -94,8 +94,8 @@ export class WfsActionsService implements OnDestroy { { id: 'selectedOnly', checkbox: true, - title: 'igo.geo.workspace.selected.title', - tooltip: 'selectedOnly', + title: 'igo.integration.workspace.selected.title', + tooltip: 'igo.integration.workspace.selected.title', checkCondition: false, handler: () => { const filterStrategy = workspace.entityStore @@ -110,8 +110,8 @@ export class WfsActionsService implements OnDestroy { { id: 'clearselection', icon: 'select-off', - title: 'igo.geo.workspace.clearSelection.title', - tooltip: 'igo.geo.workspace.clearSelection.tooltip', + title: 'igo.integration.workspace.clearSelection.title', + tooltip: 'igo.integration.workspace.clearSelection.tooltip', handler: (ws: WfsWorkspace) => { ws.entityStore.state.updateMany(ws.entityStore.view.all(), { selected: false }); }, @@ -120,9 +120,9 @@ export class WfsActionsService implements OnDestroy { }, { id: 'wfsDownload', - icon: 'download', - title: 'igo.geo.workspace.download.title', - tooltip: 'igo.geo.workspace.download.tooltip', + icon: 'file-export', + title: 'igo.integration.workspace.download.title', + tooltip: 'igo.integration.workspace.download.tooltip', handler: (ws: WfsWorkspace) => { const filterStrategy = ws.entityStore.getStrategyOfType(EntityStoreFilterCustomFuncStrategy); const filterSelectionStrategy = ws.entityStore.getStrategyOfType(EntityStoreFilterSelectionStrategy); @@ -137,8 +137,8 @@ export class WfsActionsService implements OnDestroy { { id: 'ogcFilter', icon: 'filter', - title: 'igo.geo.workspace.ogcFilter.title', - tooltip: 'igo.geo.workspace.ogcFilter.tooltip', + title: 'igo.integration.workspace.ogcFilter.title', + tooltip: 'igo.integration.workspace.ogcFilter.tooltip', handler: (widget: Widget, ws: WfsWorkspace) => { ws.activateWidget(widget, { map: ws.map, @@ -148,6 +148,8 @@ export class WfsActionsService implements OnDestroy { args: [this.ogcFilterWidget, workspace] }, ]; + return (workspace.layer.dataSource as OgcFilterableDataSource).ogcFilters$?.value?.enabled ? + actions : actions.filter(action => action.id !== 'ogcFilter'); } private handleZoomAuto(workspace: WfsWorkspace) { diff --git a/packages/integration/src/lib/workspace/workspace-button/workspace-button.component.ts b/packages/integration/src/lib/workspace/workspace-button/workspace-button.component.ts index a5d5665a1f..47ba2d0e14 100644 --- a/packages/integration/src/lib/workspace/workspace-button/workspace-button.component.ts +++ b/packages/integration/src/lib/workspace/workspace-button/workspace-button.component.ts @@ -1,8 +1,7 @@ import { Component, Input, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core'; -import { VectorLayer } from '@igo2/geo'; import type { Layer } from '@igo2/geo'; import { WorkspaceState } from '../workspace.state'; -import { BehaviorSubject, Subscription } from 'rxjs'; +import { BehaviorSubject, combineLatest, Subscription } from 'rxjs'; @Component({ selector: 'igo-workspace-button', @@ -15,16 +14,26 @@ export class WorkspaceButtonComponent implements OnInit, OnDestroy { public hasWorkspace$: BehaviorSubject = new BehaviorSubject(false); private hasWorkspace$$: Subscription; - @Input() layer: Layer; + private _layer: Layer; + private layer$: BehaviorSubject = new BehaviorSubject(undefined); + @Input() + set layer(value: Layer) { + this._layer = value; + this.layer$.next(this._layer); + } + + get layer(): Layer { + return this._layer; + } @Input() color: string = 'primary'; - constructor(private workspaceState: WorkspaceState) {} + constructor(private workspaceState: WorkspaceState) { } ngOnInit(): void { - this.hasWorkspace$$ = this.workspaceState.workspaceEnabled$.subscribe(wksEnabled => - this.hasWorkspace$.next(wksEnabled && this.layer instanceof VectorLayer) - ); + this.hasWorkspace$$ = combineLatest([this.workspaceState.workspaceEnabled$, this.layer$]) + .subscribe(bunch => this.hasWorkspace$.next(bunch[0] && bunch[1]?.options.workspace?.enabled) + ); } ngOnDestroy(): void { @@ -36,10 +45,10 @@ export class WorkspaceButtonComponent implements OnInit, OnDestroy { this.workspaceState.workspace$.value && (this.workspaceState.workspace$.value as any).layer.id === this.layer.id && this.workspaceState.workspacePanelExpanded) { - this.workspaceState.workspacePanelExpanded = false; + this.workspaceState.workspacePanelExpanded = false; } else { this.workspaceState.workspacePanelExpanded = true; - this.workspaceState.setActiveWorkspaceByLayerId(this.layer.id); + this.workspaceState.setActiveWorkspaceById(this.layer.id); } } } diff --git a/packages/integration/src/lib/workspace/workspace.state.ts b/packages/integration/src/lib/workspace/workspace.state.ts index 7fbfd1af2b..5b9375e536 100644 --- a/packages/integration/src/lib/workspace/workspace.state.ts +++ b/packages/integration/src/lib/workspace/workspace.state.ts @@ -88,12 +88,12 @@ export class WorkspaceState implements OnDestroy { }); } - public setActiveWorkspaceByLayerId(id: string) { - const wksFromLayerId = this.store + public setActiveWorkspaceById(id: string) { + const wksFromId = this.store .all() - .find(workspace => (workspace as WfsWorkspace | FeatureWorkspace).layer.id === id); - if (wksFromLayerId) { - this.store.activateWorkspace(wksFromLayerId); + .find(workspace => workspace.id === id); + if (wksFromId) { + this.store.activateWorkspace(wksFromId); } } diff --git a/packages/integration/src/locale/en.integration.json b/packages/integration/src/locale/en.integration.json index 7c0596a652..d04391b5b0 100644 --- a/packages/integration/src/locale/en.integration.json +++ b/packages/integration/src/locale/en.integration.json @@ -40,7 +40,21 @@ "importExportContext": "Context" }, "workspace": { - "toggleWorkspace": "Open/Close the table view" + "toggleWorkspace": "Open/Close the table view", + "ogcFilter.close": "Close", + "ogcFilter.title": "Filters", + "ogcFilter.tooltip": "Apply filters", + "download.title": "Download", + "download.tooltip": "Download", + "inMapExtent.title": "Show records contained in map", + "inMapExtent.active.tooltip": "Show records contained in map", + "inMapExtent.inactive.tooltip": "Show all records", + "zoomAuto.title": "Zoom auto", + "zoomAuto.tooltip": "Zoom auto", + "selected.title": "Show selected records only", + "selected.tooltip": "Show selected records only", + "clearSelection.title": "Deselect all", + "clearSelection.tooltip": "Deselect all records" } } } diff --git a/packages/integration/src/locale/fr.integration.json b/packages/integration/src/locale/fr.integration.json index 9ce4506334..6450ac48f3 100644 --- a/packages/integration/src/locale/fr.integration.json +++ b/packages/integration/src/locale/fr.integration.json @@ -40,7 +40,21 @@ "importExportContext": "Contexte" }, "workspace": { - "toggleWorkspace": "Ouvrir/fermer la vue tabulaire" + "toggleWorkspace": "Ouvrir/fermer la vue tabulaire", + "ogcFilter.close": "Fermer", + "ogcFilter.title": "Filtres", + "ogcFilter.tooltip": "Appliquer des filtres sur la couche", + "download.title": "Télécharger les données associées", + "download.tooltip": "Télécharger les données associées", + "inMapExtent.title": "Ne montrer que les enregistrements contenus dans la carte", + "inMapExtent.active.tooltip": "Ne montrer que les enregistrements contenus dans la carte", + "inMapExtent.inactive.tooltip": "Montrer tous les enregistrements", + "zoomAuto.title": "Zoom auto", + "zoomAuto.tooltip": "Zoom auto", + "selected.title": "Ne montrer que les enregistrements sélectionnés", + "selected.tooltip": "Ne montrer que les enregistrements sélectionnés", + "clearSelection.title": "Tout désélectionner", + "clearSelection.tooltip": "Désélectionner les enregistrements sélectionnés" } } }