Skip to content

Commit

Permalink
feat(geo): save search results in layer (#1179)
Browse files Browse the repository at this point in the history
* feat(geo): save search results in layer

* create layer dialog

* clean code

* code cleaning back with collapsible component with first state

* add feature ti layer btn and dialog

* start create layer and add feature

* edit layer

* create layer and add new feature

* save feature in layer

* add translation and solve lint

* solve lintt

* solve comments

* correct igo-search-layer

* solve typo

* add searchLayerStores to save new the storest and delete zIndex property from drowinglayer to solve zindex issue

* solve lint error

* typo

* refactor(search-bar): allow queryable / switch to Feature

* refactor(search-results-add-button): renamed const

* fix/feat(search-results): fix translation/add feature properties

---------

Co-authored-by: Philippe Lafreniere <[email protected]>
Co-authored-by: Pierre-Étienne Lord <[email protected]>
  • Loading branch information
3 people authored and cbourget committed Mar 21, 2023
1 parent 7a81754 commit 9d38893
Show file tree
Hide file tree
Showing 12 changed files with 385 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<h1 mat-dialog-title class="mat-typography">{{'igo.geo.layer.saveFeatureInLayer' | translate}}</h1>

<div mat-dialog-content class="mat-typography">
<form class="igo-form" [formGroup]="form">
<igo-list [navigation]="false" [selection]="false">
<igo-search-results-item
igoListItem
color="accent"
[result]="feature"
showIcons="true">
</igo-search-results-item>
</igo-list>

<div class="igo-input-container">
<mat-form-field>
<input type="text"
placeholder="{{'igo.geo.layer.chooseOrSet' | translate}}"
matInput
[matAutocomplete]="auto"
formControlName="layerName">
<mat-autocomplete [displayWith]="displayFn" #auto="matAutocomplete">
<mat-option *ngFor="let layer of filteredLayers | async" [value]="layer">
<p mat-line>{{layer.title}}</p>
</mat-option>
</mat-autocomplete>
</mat-form-field>
</div>
</form>
</div>

<div mat-dialog-actions style="justify-content: center">
<div class="igo-form-button-group create-layer-button-top-padding">
<button
mat-raised-button
type="button"
(click)="cancel()">
{{'igo.geo.layer.cancelBtn' | translate}}
</button>
<button
id="createLayerBtnDialog"
mat-raised-button
type="button"
color="primary"
(click)="save()">
{{'igo.geo.layer.saveBtn' | translate}}
</button>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
mat-form-field {
width: 100%;
}

.create-layer-button-top-padding {
padding-top: 25px;
}

.igo-form {
padding: 10px 5px 5px;
}

.igo-form-button-group {
text-align: center;
}

button {
cursor: pointer;
}

button#createLayerBtnDialog[disabled=true] {
cursor: default;
background-color: rgba(0,0,0,.12);
color: rgba(0,0,0,.26);
}

.error {
color: red;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { LanguageService } from '@igo2/core';
import { Component, OnInit, Optional, Inject } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Layer } from '../../layer';
import { SearchResult } from '../shared';
import { Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';

@Component({
selector: 'igo-save-feature-dialog',
templateUrl: './save-feature-dialog.component.html',
styleUrls: ['./save-feature-dialog.component.scss']
})
export class SaveFeatureDialogComponent implements OnInit {

public form: UntypedFormGroup;
feature: SearchResult;
layers: Layer[] = [];
filteredLayers: Observable<Layer[]>;

constructor(
private formBuilder: UntypedFormBuilder,
public languageService: LanguageService,
public dialogRef: MatDialogRef<SaveFeatureDialogComponent>,
@Optional()
@Inject(MAT_DIALOG_DATA)
public data: { feature: SearchResult; layers: Layer[]}
) {
this.form = this.formBuilder.group({
layerName: ['', [Validators.required]],
});
}

ngOnInit() {
this.feature = this.data.feature;
this.layers = this.data.layers;
this.filteredLayers = this.form.controls['layerName'].valueChanges.pipe(
startWith(''),
map(val => this.filter(val))
);
}

private filter(val): Layer[] {
if(typeof val !== 'string'){
return;
}
return this.layers.map(l => l).filter(layer =>
layer?.title?.toLowerCase().includes(val.toLowerCase()));
}

displayFn(layer: Layer): string {
return layer && layer.title ? layer.title: '';
}

save() {
const data: {layer: string | Layer, feature: SearchResult} = {layer: this.form.value.layerName, feature: this.feature};
this.dialogRef.close(data);
}

cancel() {
this.dialogRef.close();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,23 @@
[svgIcon]="(isPreview$ | async) ? 'plus' : added ? 'delete' : 'plus'">
</mat-icon>
</button>

<button
igoStopPropagation
*ngIf="layer.meta.dataType === 'Feature'"
mat-icon-button
tooltip-position="below"
matTooltipShowDelay="500"
[matTooltip]="(addFeatureToLayerTooltip$ | async) | translate"
(click)="addFeatureToLayer()">
<mat-icon
matBadge="icon"
igoMatBadgeIcon="eye-off"
igoMatBadgeInverseColor="true"
[matBadgeHidden]="(inRange$ | async)"
matBadgeDisabled="true"
matBadgeSize="small"
matBadgePosition="after"
svgIcon="file-plus-outline">
</mat-icon>
</button>
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,52 @@ import {
Input,
ChangeDetectionStrategy,
OnInit,
OnDestroy
OnDestroy,
} from '@angular/core';

import { SearchResult } from '../shared/search.interfaces';
import { IgoMap } from '../../map/shared/map';
import { LayerOptions } from '../../layer/shared/layers/layer.interface';
import { LayerService } from '../../layer/shared/layer.service';
import { LAYER } from '../../layer/shared/layer.enums';
import { Subscription, BehaviorSubject } from 'rxjs';
import { Subscription, BehaviorSubject, take } from 'rxjs';
import { SaveFeatureDialogComponent } from './save-feature-dialog.component';
import { MatDialog } from '@angular/material/dialog';
import { VectorLayer } from '../../layer/shared/layers/vector-layer';
import { DataSourceService, FeatureDataSource } from '../../datasource';
import {
Feature,
FeatureMotion,
FeatureStore,
FeatureStoreLoadingStrategy,
FeatureStoreSelectionStrategy,
tryAddLoadingStrategy,
tryAddSelectionStrategy,
tryBindStoreLayer
} from '../../feature';
import { EntityStore } from '@igo2/common';
import { getTooltipsOfOlGeometry } from '../../measure';
import OlOverlay from 'ol/Overlay';
import { VectorSourceEvent as OlVectorSourceEvent } from 'ol/source/Vector';
import { default as OlGeometry } from 'ol/geom/Geometry';
import { QueryableDataSourceOptions } from '../../query';
import { createOverlayDefaultStyle } from '../../overlay';


@Component({
selector: 'igo-search-add-button',
templateUrl: './search-results-add-button.component.html',
styleUrls: ['./search-results-add-button.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SearchResultAddButtonComponent implements OnInit, OnDestroy {
export class SearchResultAddButtonComponent implements OnInit, OnDestroy{
public tooltip$: BehaviorSubject<string> = new BehaviorSubject(
'igo.geo.catalog.layer.addToMap'
);

public addFeatureToLayerTooltip$: BehaviorSubject<string> = new BehaviorSubject(
'igo.geo.search.addToLayer'
);

private resolution$$: Subscription;
private layers$$: Subscription;
Expand All @@ -38,6 +67,8 @@ export class SearchResultAddButtonComponent implements OnInit, OnDestroy {

@Input() layer: SearchResult;

@Input() store: EntityStore<SearchResult>;

/**
* Whether the layer is already added to the map
*/
Expand All @@ -57,7 +88,18 @@ export class SearchResultAddButtonComponent implements OnInit, OnDestroy {
}
private _color = 'primary';

constructor(private layerService: LayerService) {}
@Input() stores: FeatureStore<Feature>[] = [];

get allLayers() {
return this.map.layers.filter((layer) =>
layer.id.includes('igo-search-layer')
);
}

constructor(
private layerService: LayerService,
private dialog: MatDialog,
private dataSourceService: DataSourceService) {}

/**
* @internal
Expand Down Expand Up @@ -230,4 +272,138 @@ export class SearchResultAddButtonComponent implements OnInit, OnDestroy {
: 'igo.geo.catalog.layer.addToMapOutRange';
}
}

addFeatureToLayer() {
if (this.layer.meta.dataType !== 'Feature') {
return;
}

const selectedFeature = this.layer;
const dialogRef = this.dialog.open(SaveFeatureDialogComponent, {
width: '700px',
data: {
feature: selectedFeature,
layers: this.allLayers
}
});

dialogRef.afterClosed().subscribe((data: {layer: string | any, feature: SearchResult}) => {
if (data) {
if(this.stores.length > 0) {
this.stores.map((store) => {
store.state.updateAll({selected: false});
(store?.layer).visible = false;
return store;
});
}
// check if is new layer
if (typeof data.layer === 'string') {
this.createLayer(data.layer, data.feature);
} else {
const activeStore = this.stores.find(store => store.layer.id === data.layer.id);
activeStore.layer.visible = true;
activeStore.layer.opacity = 1;
this.addFeature(data.feature, activeStore);
}
}
});
}

createLayer(layerTitle: string, selectedFeature: SearchResult) {

const activeStore: FeatureStore<Feature> = new FeatureStore<Feature>([], {
map: this.map
});

// set layer id
let layerCounterID: number = 0;
for (const layer of this.allLayers) {
let numberId = Number(layer.id.replace('igo-search-layer',''));
layerCounterID = Math.max(numberId,layerCounterID);
}

this.dataSourceService
.createAsyncDataSource({
type: 'vector',
queryable: true
} as QueryableDataSourceOptions)
.pipe(take(1))
.subscribe((dataSource: FeatureDataSource) => {
let searchLayer: VectorLayer = new VectorLayer({
isIgoInternalLayer: true,
id: 'igo-search-layer' + ++layerCounterID,
title: layerTitle,
source: dataSource,
style: createOverlayDefaultStyle({
text: '',
strokeWidth: 1,
fillColor: 'rgba(255,255,255,0.4)',
strokeColor: 'rgba(143,7,7,1)'
}),
showInLayerList: true,
exportable: true,
workspace: {
enabled: true
}
});

tryBindStoreLayer(activeStore, searchLayer);
tryAddLoadingStrategy(
activeStore,
new FeatureStoreLoadingStrategy({
motion: FeatureMotion.None
})
);

tryAddSelectionStrategy(
activeStore,
new FeatureStoreSelectionStrategy({
map: this.map,
motion: FeatureMotion.None,
many: true
})
);

activeStore.layer.visible = true;
activeStore.source.ol.on(
'removefeature',
(event: OlVectorSourceEvent<OlGeometry>) => {
const olGeometry = event.feature.getGeometry();
this.clearLabelsOfOlGeometry(olGeometry);
}
);

this.addFeature(selectedFeature, activeStore);
this.stores.push(activeStore);
});
}

addFeature(feature: SearchResult, activeStore: FeatureStore<Feature>) {
const newFeature = {
type: feature.data.type,
geometry: {
coordinates: feature.data.geometry.coordinates,
type: feature.data.geometry.type
},
projection: feature.data.projection,
properties: feature.data.properties,
meta: {
id: feature.meta.id
}
};
delete newFeature.properties.Route;
activeStore.update(newFeature);
activeStore.setLayerExtent();
activeStore.layer.ol.getSource().refresh();
}

private clearLabelsOfOlGeometry(olGeometry) {
getTooltipsOfOlGeometry(olGeometry).forEach(
(olTooltip: OlOverlay | undefined) => {
if (olTooltip && olTooltip.getMap()) {
this.map.ol.removeOverlay(olTooltip);
}
}
);
}
}
Loading

0 comments on commit 9d38893

Please sign in to comment.