diff --git a/packages/geo/src/lib/layer/layer-list/layer-list-binding.directive.ts b/packages/geo/src/lib/layer/layer-list/layer-list-binding.directive.ts index ef63dc7398..365bfb1d72 100644 --- a/packages/geo/src/lib/layer/layer-list/layer-list-binding.directive.ts +++ b/packages/geo/src/lib/layer/layer-list/layer-list-binding.directive.ts @@ -1,18 +1,21 @@ import { Directive, Self, OnInit, OnDestroy, AfterViewInit, Optional } from '@angular/core'; -import { Subscription } from 'rxjs'; +import { Subscription, combineLatest } from 'rxjs'; import { RouteService } from '@igo2/core'; import { MapService } from '../../map/shared/map.service'; import { LayerListComponent } from './layer-list.component'; import { LayerListService } from './layer-list.service'; import { Layer } from '../shared/layers/layer'; +import { map, debounceTime } from 'rxjs/operators'; @Directive({ selector: '[igoLayerListBinding]' }) export class LayerListBindingDirective implements OnInit, AfterViewInit, OnDestroy { private component: LayerListComponent; - private layers$$: Subscription; + private layersOrResolutionChange$$: Subscription; + layersVisibility$$: Subscription; + layersRange$$: Subscription; constructor( @Self() component: LayerListComponent, @@ -26,20 +29,46 @@ export class LayerListBindingDirective implements OnInit, AfterViewInit, OnDestr ngOnInit() { // Override input layers this.component.layers = []; - - this.layers$$ = this.mapService - .getMap() - .layers$.subscribe((layers: Layer[]) => { - this.component.layers = layers.filter((layer: Layer) => { - return layer.showInLayerList === true; - }); + this.layersOrResolutionChange$$ = combineLatest([ + this.mapService.getMap().layers$, + this.mapService.getMap().viewController.resolution$] + ).pipe( + debounceTime(10) + ).subscribe((bunch: [Layer[], number]) => { + const shownLayers = bunch[0].filter((layer: Layer) => { + return layer.showInLayerList === true; }); + this.component.layers = shownLayers; + this.setLayersVisibilityRangeStatus(shownLayers, this.component.excludeBaseLayers); + }); } ngAfterViewInit(): void { this.initRoutes(); } + private setLayersVisibilityRangeStatus(layers: Layer[], excludeBaseLayers: boolean) { + if (this.layersVisibility$$ !== undefined) { + this.layersVisibility$$.unsubscribe(); + this.layersVisibility$$ = undefined; + } + if (this.layersRange$$ !== undefined) { + this.layersRange$$.unsubscribe(); + this.layersRange$$ = undefined; + } + this.layersVisibility$$ = combineLatest(layers + .filter(layer => layer.baseLayer !== excludeBaseLayers ) + .map((layer: Layer) => layer.visible$)) + .pipe(map((visibles: boolean[]) => visibles.every(Boolean))) + .subscribe((allLayersAreVisible: boolean) => + this.component.layersAreAllVisible = allLayersAreVisible); + + this.layersRange$$ = combineLatest(layers.map((layer: Layer) => layer.isInResolutionsRange$)) + .pipe(map((inrange: boolean[]) => inrange.every(Boolean))) + .subscribe((layersAreAllInRange: boolean) => + this.component.layersAreAllInRange = layersAreAllInRange); + } + private initRoutes() { if ( this.route && @@ -61,13 +90,13 @@ export class LayerListBindingDirective implements OnInit, AfterViewInit, OnDestr } if (onlyVisibleFromUrl && !this.layerListService.onlyVisibleInitialized && - this.component.hasLayerNotVisible) { + !this.component.layersAreAllVisible) { this.layerListService.onlyVisible = onlyVisibleFromUrl === '1' ? true : false; this.layerListService.onlyVisibleInitialized = true; } if (onlyInRangeFromUrl && !this.layerListService.onlyInRangeInitialized && - this.component.hasLayerOutOfRange) { + !this.component.layersAreAllInRange) { this.layerListService.onlyInRange = onlyInRangeFromUrl === '1' ? true : false; this.layerListService.onlyInRangeInitialized = true; } @@ -76,7 +105,15 @@ export class LayerListBindingDirective implements OnInit, AfterViewInit, OnDestr } ngOnDestroy() { - this.layers$$.unsubscribe(); + this.layersOrResolutionChange$$.unsubscribe(); + if (this.layersVisibility$$ !== undefined) { + this.layersVisibility$$.unsubscribe(); + this.layersVisibility$$ = undefined; + } + if (this.layersRange$$ !== undefined) { + this.layersRange$$.unsubscribe(); + this.layersRange$$ = undefined; + } } } 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 df5f465fd5..3c370b7d43 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 @@ -35,28 +35,32 @@ (click)="toggleSort(false)"> - - - - + + + + + - - + matTooltipShowDelay="500"> + + + + 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 de919cd9ec..636ace3ae9 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 @@ -33,8 +33,6 @@ import { changeDetection: ChangeDetectionStrategy.OnPush }) export class LayerListComponent implements OnInit, OnDestroy { - hasLayerNotVisible = false; - hasLayerOutOfRange = false; orderable = true; thresholdToFilterAndSort = 5; @@ -48,9 +46,13 @@ export class LayerListComponent implements OnInit, OnDestroy { @ContentChild('igoLayerItemToolbar') templateLayerToolbar: TemplateRef; + @Input() layersAreAllVisible: boolean = true; + + @Input() layersAreAllInRange: boolean = true; + @Input() set layers(value: Layer[]) { - this.setLayers(value); + this._layers = value; this.next(); } get layers(): Layer[] { @@ -329,7 +331,7 @@ export class LayerListComponent implements OnInit, OnDestroy { if ( this.layerFilterAndSortOptions.onlyVisible && !this.onlyVisibleInitialized && - this.hasLayerNotVisible + !this.layersAreAllVisible ) { this.onlyVisible = this.layerFilterAndSortOptions.onlyVisible; this.onlyVisibleInitialized = true; @@ -337,27 +339,10 @@ export class LayerListComponent implements OnInit, OnDestroy { if ( this.layerFilterAndSortOptions.onlyInRange && !this.onlyInRangeInitialized && - this.hasLayerOutOfRange + !this.layersAreAllInRange ) { this.onlyInRange = this.layerFilterAndSortOptions.onlyInRange; this.onlyInRangeInitialized = true; } } - - private setLayers(layers: Layer[]) { - this._layers = layers; - - if (this.excludeBaseLayers) { - this.hasLayerNotVisible = - layers.find(l => l.visible === false && !l.baseLayer) !== undefined; - this.hasLayerOutOfRange = - layers.find(l => l.isInResolutionsRange === false && !l.baseLayer) !== - undefined; - } else { - this.hasLayerNotVisible = - layers.find(l => l.visible === false) !== undefined; - this.hasLayerOutOfRange = - layers.find(l => l.isInResolutionsRange === false) !== undefined; - } - } } diff --git a/packages/geo/src/lib/layer/shared/layers/layer.ts b/packages/geo/src/lib/layer/shared/layers/layer.ts index dc59cc1607..f25463f246 100644 --- a/packages/geo/src/lib/layer/shared/layers/layer.ts +++ b/packages/geo/src/lib/layer/shared/layers/layer.ts @@ -1,4 +1,11 @@ -import { Subject } from 'rxjs'; +import { + BehaviorSubject, + Observable, + Subject, + Subscription, + combineLatest +} from 'rxjs'; +import { debounceTime, map } from 'rxjs/operators'; import olLayer from 'ol/layer/Layer'; @@ -16,9 +23,10 @@ export abstract class Layer { public firstLoadComponent: boolean = true; public map: IgoMap; public ol: olLayer; - public options: LayerOptions; public status$: Subject; + private resolution$$: Subscription; + get id(): string { return this.options.id || this.dataSource.id; } @@ -51,14 +59,6 @@ export abstract class Layer { this.options.baseLayer = baseLayer; } - get visible(): boolean { - return this.ol.get('visible'); - } - - set visible(visibility: boolean) { - this.ol.setVisible(visibility); - } - get opacity(): number { return this.ol.get('opacity'); } @@ -67,49 +67,75 @@ export abstract class Layer { this.ol.setOpacity(opacity); } - get isInResolutionsRange(): boolean { - if (!this.map) { - return false; - } + set isInResolutionsRange(value: boolean) { this.isInResolutionsRange$.next(value); } + get isInResolutionsRange(): boolean { return this.isInResolutionsRange$.value; } + readonly isInResolutionsRange$: BehaviorSubject = new BehaviorSubject(false); - const resolution = this.map.viewController.getResolution(); - const minResolution = this.ol.getMinResolution(); - const maxResolution = this.ol.getMaxResolution(); + set maxResolution(value: number) { + this.ol.setMaxResolution(value); + this.updateInResolutionsRange(); + } + get maxResolution(): number { return this.ol.getMaxResolution(); } + + set minResolution(value: number) { + this.ol.setMinResolution(value); + this.updateInResolutionsRange(); + } + get minResolution(): number { return this.ol.getMinResolution(); } - return resolution >= minResolution && resolution <= maxResolution; + set visible(value: boolean) { + this.ol.setVisible(value); + this.visible$.next(value); } + get visible(): boolean { return this.visible$.value; } + readonly visible$: BehaviorSubject = new BehaviorSubject(undefined); + + get displayed(): boolean { return this.visible && this.isInResolutionsRange; } + readonly displayed$: Observable = combineLatest([ + this.isInResolutionsRange$, + this.visible$ + ]).pipe( + map((bunch: [boolean, boolean]) => bunch[0] && bunch[1]) + ); get showInLayerList(): boolean { return this.options.showInLayerList !== false; } - constructor(options: LayerOptions) { - this.options = options; - this.dataSource = this.options.source; + constructor(public options: LayerOptions) { + this.dataSource = options.source; this.ol = this.createOlLayer(); - if (this.options.zIndex !== undefined) { - this.zIndex = this.options.zIndex; + if (options.zIndex !== undefined) { + this.zIndex = options.zIndex; + } + + if (options.baseLayer && options.visible === undefined) { + options.visible = false; } - if (this.options.baseLayer && this.options.visible === undefined) { - this.options.visible = false; + if (options.maxResolution !== undefined) { + this.maxResolution = options.maxResolution; } + if (options.minResolution !== undefined) { + this.minResolution = options.minResolution; + } + this.visible = - this.options.visible === undefined ? true : this.options.visible; + options.visible === undefined ? true : options.visible; this.opacity = - this.options.opacity === undefined ? 1 : this.options.opacity; + options.opacity === undefined ? 1 : options.opacity; if ( - this.options.legendOptions && - (this.options.legendOptions.url || this.options.legendOptions.html) + options.legendOptions && + (options.legendOptions.url || options.legendOptions.html) ) { - this.legend = this.dataSource.setLegend(this.options.legendOptions); + this.legend = this.dataSource.setLegend(options.legendOptions); } - this.legendCollapsed = this.options.legendOptions - ? this.options.legendOptions.collapsed - ? this.options.legendOptions.collapsed + this.legendCollapsed = options.legendOptions + ? options.legendOptions.collapsed + ? options.legendOptions.collapsed : true : true; @@ -118,7 +144,35 @@ export abstract class Layer { protected abstract createOlLayer(): olLayer; - setMap(map: IgoMap | undefined) { - this.map = map; + setMap(igoMap: IgoMap | undefined) { + this.map = igoMap; + + this.unobserveResolution(); + if (igoMap !== undefined) { + this.observeResolution(); + } + } + + private observeResolution() { + this.resolution$$ = this.map.viewController.resolution$ + .subscribe(() => this.updateInResolutionsRange()); + } + + private unobserveResolution() { + if (this.resolution$$ !== undefined) { + this.resolution$$.unsubscribe(); + this.resolution$$ = undefined; + } + } + + private updateInResolutionsRange() { + if (this.map !== undefined) { + const resolution = this.map.viewController.getResolution(); + const minResolution = this.minResolution || 0; + const maxResolution = this.maxResolution || Infinity; + this.isInResolutionsRange = resolution >= minResolution && resolution <= maxResolution; + } else { + this.isInResolutionsRange = false; + } } }