Skip to content

Commit

Permalink
feat(feature): Make features clickable (#149)
Browse files Browse the repository at this point in the history
* Ajustements into the lib to fit with the igo-api structure.

* Make features clickable

* Remove console.log(...)

* Local for clicked features.

* use arrow notation
  • Loading branch information
pelord authored and mbarbeau committed May 15, 2018
1 parent 9cdd8e4 commit f668d78
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 66 deletions.
9 changes: 5 additions & 4 deletions src/lib/datasource/shared/datasources/wfs-datasource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { HttpClient } from '@angular/common/http';
import * as ol from 'openlayers';

import { uuid } from '../../../utils';
import {
import {
IgoOgcFilterObject, OgcFilter, WFSWriteGetFeatureOptions,
AnyBaseOgcFilterOptions, OgcFilterWriter
AnyBaseOgcFilterOptions, OgcFilterWriter
} from '../../../filter/shared';

import { DataSource } from './datasource';
Expand Down Expand Up @@ -32,12 +32,13 @@ export class WFSDataSource extends DataSource implements OgcFilterableDataSource
this.ogcFilterWriter = new OgcFilterWriter;

this.dataSourceService.checkWfsOptions(options);
if (options['sourceFields'] === undefined) {
if (options['sourceFields'] === undefined ||
Object.keys(options['sourceFields']).length === 0) {
options['sourceFields'] = []
this.dataSourceService.wfsGetCapabilities(options)
.map(wfsCapabilities => options['wfsCapabilities'] = {
'xml': wfsCapabilities.body,
'GetPropertyValue': /GetPropertyValue/gi.test(wfsCapabilities.body) ? true : false
'GetPropertyValue': /GetPropertyValue/gi.test(wfsCapabilities.body) ? true : false
})
.subscribe(val => options['sourceFields'] =
this.dataSourceService.defineFieldAndValuefromWFS(options));
Expand Down
13 changes: 7 additions & 6 deletions src/lib/datasource/shared/datasources/wms-datasource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { Md5 } from 'ts-md5/dist/md5';
import { QueryFormat, QueryOptions } from '../../../query';

import { DataSource } from './datasource';
import {
import {
DataSourceLegendOptions, TimeFilterableDataSource,
QueryableDataSource, OgcFilterableDataSource
QueryableDataSource, OgcFilterableDataSource
} from './datasource.interface';
import { WMSDataSourceOptions } from './wms-datasource.interface';
import { OgcFilterWriter, IgoOgcFilterObject, OgcFiltersOptions } from '../../../filter/shared';
Expand Down Expand Up @@ -55,11 +55,12 @@ export class WMSDataSource extends DataSource implements
sourceParams.VERSION = sourceParams.version;
}

if (options['sourceFields'] === undefined) {
if (options['sourceFields'] === undefined ||
Object.keys(options['sourceFields']).length === 0) {
options['sourceFields'] = [{ 'name': '', 'alias': '' }]
}
// WMS With linked wfs
if (options.wfsSource) {
if (options.wfsSource && Object.keys(options.wfsSource).length > 0) {
options.wfsSource = this.dataSourceService.checkWfsOptions(options.wfsSource);
delete options.wfsSource.ogcFilters;
options['fieldNameGeometry'] = options.wfsSource['fieldNameGeometry'];
Expand All @@ -68,7 +69,7 @@ export class WMSDataSource extends DataSource implements
this.dataSourceService.wfsGetCapabilities(options)
.map(wfsCapabilities => options.wfsSource['wfsCapabilities'] = {
'xml': wfsCapabilities.body,
'GetPropertyValue': /GetPropertyValue/gi.test(wfsCapabilities.body) ? true : false
'GetPropertyValue': /GetPropertyValue/gi.test(wfsCapabilities.body) ? true : false
})
.subscribe(val => options['sourceFields'] =
this.dataSourceService.defineFieldAndValuefromWFS(options.wfsSource));
Expand All @@ -94,7 +95,7 @@ export class WMSDataSource extends DataSource implements
'srsname=' + options.wfsSource.srsname : 'srsname=EPSG:3857';
const baseWfsQuery = this.dataSourceService.buildBaseWfsUrl(options.wfsSource, 'GetFeature');
this.options.download = Object.assign({}, this.options.download, {
'dynamicUrl': `${baseWfsQuery}&${outputFormat}&${srsname}&${maxFeatures}`
'dynamicUrl': `${baseWfsQuery}&${outputFormat}&${srsname}&${maxFeatures}`
});
}

Expand Down
59 changes: 30 additions & 29 deletions src/lib/download/shared/download.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,35 +20,36 @@ export class DownloadService {
const translate = this.languageService.translate;
const title = translate.instant('igo.download.title');
this.messageService.success(translate.instant('igo.download.start'), title);

if (
layer.dataSource.options.download['dynamicUrl'] &&
layer.dataSource.options.download.url === undefined) {
let wfsOptions;
if (layer.dataSource.options['wfsSource']) {
wfsOptions = layer.dataSource.options['wfsSource'];
} else {
wfsOptions = layer.dataSource.options;
}

const outputFormatDownload =
wfsOptions['outputFormatDownload'] === undefined ?
'outputformat=' + wfsOptions['outputFormat'] :
'outputformat=' + wfsOptions['outputFormatDownload'];

const baseurl = layer.dataSource.options.download['dynamicUrl']
.replace(/&?outputformat=[^&]*/gi, '')
.replace(/&?filter=[^&]*/gi, '')
.replace(/&?bbox=[^&]*/gi, '');

const rebuildFilter = this.ogcFilterWriter.buildFilter(
layer.dataSource.options['ogcFilters']['filters'],
layer.map.getExtent(),
new ol.proj.Projection({ code: layer.map.projection }),
wfsOptions['fieldNameGeometry']);
window.open(`${baseurl}&${rebuildFilter}&${outputFormatDownload}`, '_blank');
} else if (layer.dataSource.options.download) {
window.open(layer.dataSource.options.download.url, '_blank');
if (Object.keys(layer.dataSource.options.download).length > 0 ) {
if (
layer.dataSource.options.download['dynamicUrl'] &&
layer.dataSource.options.download.url === undefined) {
let wfsOptions;
if (Object.keys(layer.dataSource.options['wfsSource']).length > 0) {
wfsOptions = layer.dataSource.options['wfsSource'];
} else {
wfsOptions = layer.dataSource.options;
}

const outputFormatDownload =
wfsOptions['outputFormatDownload'] === undefined ?
'outputformat=' + wfsOptions['outputFormat'] :
'outputformat=' + wfsOptions['outputFormatDownload'];

const baseurl = layer.dataSource.options.download['dynamicUrl']
.replace(/&?outputformat=[^&]*/gi, '')
.replace(/&?filter=[^&]*/gi, '')
.replace(/&?bbox=[^&]*/gi, '');

const rebuildFilter = this.ogcFilterWriter.buildFilter(
layer.dataSource.options['ogcFilters']['filters'],
layer.map.getExtent(),
new ol.proj.Projection({ code: layer.map.projection }),
wfsOptions['fieldNameGeometry']);
window.open(`${baseurl}&${rebuildFilter}&${outputFormatDownload}`, '_blank');
} else if (layer.dataSource.options.download) {
window.open(layer.dataSource.options.download.url, '_blank');
}
}
}
}
4 changes: 3 additions & 1 deletion src/lib/feature/shared/feature.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ export class FeatureService {
featuresAreTheSame(feature1, feature2) {
if (feature1 === undefined || feature2 === undefined) { return false; }

return feature1.id === feature2.id && feature1.source === feature2.source;
return feature1.id === feature2.id
&& feature1.source === feature2.source
&& feature1.properties === feature2.properties;
}

private sortFeatures(feature1, feature2) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@
</div>

<div class="igo-col igo-col-90 igo-col-100-m" *ngIf="(currentFilter.operator === 'Intersects' || currentFilter.operator === 'Contains' || currentFilter.operator === 'Within')">
<button mat-button [disabled]="!currentFilter.active" *ngIf="currentFilter['wkt_geometry'] && currentFilter.igoSpatialSelector === 'fixedExtent'"
<button mat-button [disabled]="!currentFilter.active" *ngIf="currentFilter.igoSpatialSelector === 'fixedExtent'"
matSuffix mat-icon-button aria-label="Clear" (click)="changeGeometry(currentFilter,value)" tooltip-position="below" matTooltipShowDelay="500"
[matTooltip]="'igo.spatialSelector.btnSetExtent' | translate">
<mat-icon>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ export class OgcFilterableItemComponent implements OnInit, OnDestroy {
{
'propertyName': firstFieldName,
'operator': 'PropertyIsEqualTo',
'active': status
'active': status,
'igoSpatialSelector': 'fixedExtent'
}, fieldNameGeometry, lastLevel, this.defaultLogicalParent));
this.datasource.options.ogcFilters.interfaceOgcFilters = arr;
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/layer/layer-item/layer-item.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ <h4 matLine [matTooltip]="layer.title +' ('+ id +') '" matTooltipShowDelay="500"
</button>

<button
*ngIf="layer.dataSource.options.download "
*ngIf="layer.dataSource.options.download && (layer.dataSource.options.download.dynamicUrl || layer.dataSource.options.download.url) "
mat-icon-button
tooltip-position="below"
matTooltipShowDelay="500"
Expand Down
113 changes: 90 additions & 23 deletions src/lib/query/shared/query.directive.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
import { Directive, Self, Input, Output, EventEmitter,
OnDestroy, AfterViewInit } from '@angular/core';
import {
Directive,
Self,
Input,
Output,
EventEmitter,
OnDestroy,
AfterViewInit
} from '@angular/core';

import { Subscription } from 'rxjs/Subscription';
import { Observable } from 'rxjs/Observable';
import { forkJoin } from 'rxjs/observable/forkJoin';

import * as ol from 'openlayers';

import { LanguageService } from '../../core';
import { IgoMap } from '../../map/shared';
import { MapBrowserComponent } from '../../map/map-browser';
import { Layer } from '../../layer';
import { Feature } from '../../feature';

import { QueryService } from '../shared/query.service';


@Directive({
selector: '[igoQuery]'
})
export class QueryDirective implements AfterViewInit, OnDestroy {

private queryLayers: Layer[];
private queryLayers$$: Subscription;
private queries$$: Subscription[] = [];
Expand All @@ -27,23 +35,30 @@ export class QueryDirective implements AfterViewInit, OnDestroy {
}

@Input()
get waitForAllQueries(): boolean { return this._waitForAllQueries; }
get waitForAllQueries(): boolean {
return this._waitForAllQueries;
}
set waitForAllQueries(value: boolean) {
this._waitForAllQueries = value;
}
private _waitForAllQueries: boolean = false;

@Output() query = new EventEmitter<{
features: Feature[] | Feature[][],
event: ol.MapBrowserEvent
@Output()
query = new EventEmitter<{
features: Feature[] | Feature[][];
event: ol.MapBrowserEvent;
}>();

constructor(@Self() private component: MapBrowserComponent,
private queryService: QueryService) {}
constructor(
@Self() private component: MapBrowserComponent,
private queryService: QueryService,
private languageService: LanguageService
) {}

ngAfterViewInit() {
this.queryLayers$$ = this.component.map.layers$
.subscribe((layers: Layer[]) => this.handleLayersChange(layers));
this.queryLayers$$ = this.component.map.layers$.subscribe(
(layers: Layer[]) => this.handleLayersChange(layers)
);

this.map.ol.on('singleclick', this.handleMapClick, this);
}
Expand All @@ -68,25 +83,77 @@ export class QueryDirective implements AfterViewInit, OnDestroy {
private handleMapClick(event: ol.MapBrowserEvent) {
this.unsubscribeQueries();

const clickedFeatures: ol.Feature[] = [];
const format = new ol.format.GeoJSON();
const mapProjection = this.map.projection;
this.map.ol.forEachFeatureAtPixel(
event.pixel,
(feature: ol.Feature, layer: ol.layer.Layer) => {
if (layer.getZIndex() !== 999) {
let title;
if (layer.get('title') !== undefined) {
title = layer.get('title');
} else {
title = this.map.layers.filter(
f => f['zIndex'] === layer.getZIndex()
)[0].dataSource['options']['title'];
}
feature.set('clickedTitle', title);
clickedFeatures.push(feature);
}
}
);
const featuresGeoJSON = JSON.parse(
format.writeFeatures(clickedFeatures, {
dataProjection: 'EPSG:4326',
featureProjection: mapProjection
})
);
let i = 0;
let parsedClickedFeatures: Feature[] = [];
parsedClickedFeatures = featuresGeoJSON.features.map(f =>
Object.assign({}, f, {
source: this.languageService.translate.instant(
'igo.clickOnMap.clickedFeature'
),
id: f.properties.clickedTitle + ' ' + String(i++),
icon: 'mouse',
title: f.properties.clickedTitle
})
);
parsedClickedFeatures.forEach(element => {
delete element.properties['clickedTitle'];
});
const view = this.map.ol.getView();
const queries$ = this.queryService.query(this.queryLayers, {
coordinates: event.coordinate,
projection: this.map.projection,
resolution: view.getResolution()
});

if (this.waitForAllQueries) {
this.queries$$.push(
forkJoin(...queries$).subscribe(
(features: Feature[][]) => this.query.emit({features: features, event: event})
)
);
if (queries$.length === 0) {
this.query.emit({ features: parsedClickedFeatures, event: event });
} else {
this.queries$$ = queries$.map((query$: Observable<Feature[]>) => {
return query$.subscribe(
(features: Feature[]) => this.query.emit({features: features, event: event})
if (this.waitForAllQueries) {
this.queries$$.push(
forkJoin(...queries$).subscribe((features: Feature[][]) =>
this.query.emit({
features: features
.filter(f => f.length > 0)
.concat(parsedClickedFeatures),
event: event
})
)
);
});
} else {
this.queries$$ = queries$.map((query$: Observable<Feature[]>) => {
return query$.subscribe((features: Feature[]) =>
this.query.emit({
features: parsedClickedFeatures.concat(features),
event: event
})
);
});
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
"catalogTool": {
"baseLayers": "Base Layers"
},
"clickOnMap": {
"clickedFeature": "Clicked Feature"
},
"confirmDialog": {
"cancelBtn": "Cancel",
"confirmBtn": "Confirm",
Expand Down
3 changes: 3 additions & 0 deletions src/locale/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
"catalogTool": {
"baseLayers": "Fonds de carte"
},
"clickOnMap": {
"clickedFeature": "Entités cliquées"
},
"confirmDialog": {
"cancelBtn": "Annuler",
"confirmBtn": "Confirmer",
Expand Down

0 comments on commit f668d78

Please sign in to comment.