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