From fc89dd7062495205c474e4e21bb7ca6b9c735c94 Mon Sep 17 00:00:00 2001 From: cbourget Date: Wed, 17 Jul 2019 13:52:55 -0400 Subject: [PATCH 01/38] feat(entity-selector): support multiple selections --- .../entity-selector.component.html | 3 +- .../entity-selector.component.ts | 71 +++++++++++++++---- 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/packages/common/src/lib/entity/entity-selector/entity-selector.component.html b/packages/common/src/lib/entity/entity-selector/entity-selector.component.html index 4b4cdc194a..ddd07b7596 100644 --- a/packages/common/src/lib/entity/entity-selector/entity-selector.component.html +++ b/packages/common/src/lib/entity/entity-selector/entity-selector.component.html @@ -1,10 +1,11 @@ {{emptyText}} + {{multiText$ | async}} {{titleAccessor(entity)}} diff --git a/packages/common/src/lib/entity/entity-selector/entity-selector.component.ts b/packages/common/src/lib/entity/entity-selector/entity-selector.component.ts index 5e0bdda1ec..35c3d55f96 100644 --- a/packages/common/src/lib/entity/entity-selector/entity-selector.component.ts +++ b/packages/common/src/lib/entity/entity-selector/entity-selector.component.ts @@ -30,6 +30,14 @@ export class EntitySelectorComponent implements OnInit, OnDestroy { */ selected$ = new BehaviorSubject(undefined); + /** + * The current multi select option text + * @internal + */ + multiText$ = new BehaviorSubject(undefined); + + readonly multiSelectValue = {id: 'IGO_MULTI_SELECT'}; + /** * Subscription to the selected entity */ @@ -45,11 +53,6 @@ export class EntitySelectorComponent implements OnInit, OnDestroy { */ @Input() store: EntityStore; - /** - * Wheter selecting many entities is allowed - */ - @Input() many: boolean = false; - /** * Title accessor */ @@ -60,6 +63,21 @@ export class EntitySelectorComponent implements OnInit, OnDestroy { */ @Input() emptyText: string = undefined; + /** + * Wheter selecting many entities is allowed + */ + @Input() multi: boolean = false; + + /** + * Text to display for the select all option + */ + @Input() multiAllText: string = 'All'; + + /** + * Text to display for the select none option + */ + @Input() multiNoneText: string = 'None'; + /** * Field placeholder */ @@ -81,16 +99,12 @@ export class EntitySelectorComponent implements OnInit, OnDestroy { */ ngOnInit() { this.watcher = new EntityStoreWatcher(this.store, this.cdRef); + this.selected$$ = this.store.stateView .manyBy$((record: EntityRecord) => record.state.selected === true) .subscribe((records: EntityRecord[]) => { const entities = records.map((record: EntityRecord) => record.entity); - if (this.many === true) { - this.selected$.next(entities); - } else { - const entity = entities.length > 0 ? entities[0] : undefined; - this.selected$.next(entity); - } + this.onSelectFromStore(entities); }); } @@ -108,14 +122,45 @@ export class EntitySelectorComponent implements OnInit, OnDestroy { * @internal */ onSelectionChange(event: {value: object | undefined}) { - const entities = event.value instanceof Array ? event.value : [event.value]; + const values = event.value instanceof Array ? event.value : [event.value]; + + const multiSelect = values.find((_value: object) => _value === this.multiSelectValue); + let entities = values.filter((_value: object) => _value !== this.multiSelectValue); + if (multiSelect !== undefined) { + if (entities.length === this.store.count) { + entities = []; + } else if (entities.length < this.store.count) { + entities = this.store.all(); + } + } + if (entities.length === 0) { this.store.state.updateAll({selected: false}); } else { this.store.state.updateMany(entities, {selected: true}, true); } - this.selectedChange.emit({selected: true, value: event.value}); + const value = this.multi ? entities : event.value; + this.selectedChange.emit({selected: true, value}); + } + + private onSelectFromStore(entities: object[]) { + if (this.multi === true) { + this.selected$.next(entities); + } else { + const entity = entities.length > 0 ? entities[0] : undefined; + this.selected$.next(entity); + } + + this.updateMultiToggleWithEntities(entities); + } + + private updateMultiToggleWithEntities(entities: object[]) { + if (entities.length === this.store.count && this.multiText$.value !== this.multiNoneText) { + this.multiText$.next(this.multiNoneText); + } else if (entities.length < this.store.count && this.multiText$.value !== this.multiAllText) { + this.multiText$.next(this.multiAllText); + } } } From 4a0444c2ed3a76bac2b07e9f8a1b028dabffeb5a Mon Sep 17 00:00:00 2001 From: cbourget Date: Wed, 17 Jul 2019 13:53:31 -0400 Subject: [PATCH 02/38] feat(view): add a count and empty observables to entity views --- packages/common/src/lib/entity/shared/view.ts | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/common/src/lib/entity/shared/view.ts b/packages/common/src/lib/entity/shared/view.ts index e88868ef60..938043c02c 100644 --- a/packages/common/src/lib/entity/shared/view.ts +++ b/packages/common/src/lib/entity/shared/view.ts @@ -48,12 +48,14 @@ export class EntityView { /** * Number of entities */ - get count(): number { return this.values$.value.length; } + readonly count$ = new BehaviorSubject(0); + get count(): number { return this.count$.value; } /** - * Whether there are entities in the view + * Whether the store is empty */ - get empty(): boolean { return this.count === 0; } + readonly empty$ = new BehaviorSubject(true); + get empty(): boolean { return this.empty$.value; } constructor(private source$: BehaviorSubject) {} @@ -161,10 +163,11 @@ export class EntityView { this.lifted = true; const source$ = this.joins.length > 0 ? this.liftJoinedSource() : this.liftSource(); this.values$$ = combineLatest(source$, this.filter$, this.sort$) - .pipe(skip(1), debounceTime(50)) + .pipe(skip(1), debounceTime(25)) .subscribe((bunch: [V[], EntityFilterClause, EntitySortClause]) => { - const [values, filter, sort] = bunch; - this.values$.next(this.processValues(values, filter, sort)); + const [_values, filter, sort] = bunch; + const values = this.processValues(_values, filter, sort); + this.setValues(values); }); } @@ -255,4 +258,12 @@ export class EntityView { ); }); } + + private setValues(values: V[]) { + this.values$.next(values); + const count = values.length; + const empty = count === 0; + this.count$.next(count); + this.empty$.next(empty); + } } From 43d88a26918a1e16ff66ccf069c7d49e504b8ae5 Mon Sep 17 00:00:00 2001 From: cbourget Date: Wed, 17 Jul 2019 13:54:22 -0400 Subject: [PATCH 03/38] fix(form): fix disabled form fields --- .../lib/form/form-field/form-field-select.component.ts | 5 +++-- .../lib/form/form-field/form-field-text.component.ts | 5 +++-- .../form/form-field/form-field-textarea.component.ts | 5 +++-- .../src/lib/form/form-field/form-field.component.ts | 10 +++++++--- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/packages/common/src/lib/form/form-field/form-field-select.component.ts b/packages/common/src/lib/form/form-field/form-field-select.component.ts index c3c735231d..54a8744b87 100644 --- a/packages/common/src/lib/form/form-field/form-field-select.component.ts +++ b/packages/common/src/lib/form/form-field/form-field-select.component.ts @@ -2,6 +2,7 @@ import { Input, Component, ChangeDetectionStrategy, + OnInit } from '@angular/core'; import { FormControl } from '@angular/forms'; @@ -20,7 +21,7 @@ import { FormFieldComponent } from '../shared/form-field-component'; templateUrl: './form-field-select.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) -export class FormFieldSelectComponent { +export class FormFieldSelectComponent implements OnInit { choices$: Observable; @@ -87,7 +88,7 @@ export class FormFieldSelectComponent { } else { this.formControl.enable(); } - this.disabled$.next(disabled); + this.disabled$.next(disabled); } } diff --git a/packages/common/src/lib/form/form-field/form-field-text.component.ts b/packages/common/src/lib/form/form-field/form-field-text.component.ts index e885b57fa4..3dbb7517ca 100644 --- a/packages/common/src/lib/form/form-field/form-field-text.component.ts +++ b/packages/common/src/lib/form/form-field/form-field-text.component.ts @@ -2,6 +2,7 @@ import { Input, Component, ChangeDetectionStrategy, + OnInit } from '@angular/core'; import { FormControl } from '@angular/forms'; @@ -22,7 +23,7 @@ import { FormFieldComponent } from '../shared/form-field-component'; templateUrl: './form-field-text.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) -export class FormFieldTextComponent { +export class FormFieldTextComponent implements OnInit { disabled$: BehaviorSubject = new BehaviorSubject(false); @@ -75,7 +76,7 @@ export class FormFieldTextComponent { } else { this.formControl.enable(); } - this.disabled$.next(disabled); + this.disabled$.next(disabled); } } diff --git a/packages/common/src/lib/form/form-field/form-field-textarea.component.ts b/packages/common/src/lib/form/form-field/form-field-textarea.component.ts index 81e0c446c6..76586fe27a 100644 --- a/packages/common/src/lib/form/form-field/form-field-textarea.component.ts +++ b/packages/common/src/lib/form/form-field/form-field-textarea.component.ts @@ -2,6 +2,7 @@ import { Input, Component, ChangeDetectionStrategy, + OnInit } from '@angular/core'; import { FormControl } from '@angular/forms'; @@ -19,7 +20,7 @@ import { FormFieldComponent } from '../shared/form-field-component'; templateUrl: './form-field-textarea.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) -export class FormFieldTextareaComponent { +export class FormFieldTextareaComponent implements OnInit { disabled$: BehaviorSubject = new BehaviorSubject(false); @@ -72,7 +73,7 @@ export class FormFieldTextareaComponent { } else { this.formControl.enable(); } - this.disabled$.next(disabled); + this.disabled$.next(disabled); } } diff --git a/packages/common/src/lib/form/form-field/form-field.component.ts b/packages/common/src/lib/form/form-field/form-field.component.ts index 36ed27a851..3db6ae6650 100644 --- a/packages/common/src/lib/form/form-field/form-field.component.ts +++ b/packages/common/src/lib/form/form-field/form-field.component.ts @@ -4,7 +4,7 @@ import { ChangeDetectionStrategy } from '@angular/core'; -import { FormField, FormFieldInputs } from '../shared/form.interfaces'; +import { FormField, FormFieldInputs, FormFieldOptions } from '../shared/form.interfaces'; import { FormFieldService } from '../shared/form-field.service'; import { getDefaultErrorMessages } from '../shared'; @@ -25,6 +25,10 @@ export class FormFieldComponent { */ @Input() field: FormField; + get fieldOptions(): FormFieldOptions { + return this.field.options || {}; + } + constructor(private formFieldService: FormFieldService) {} getFieldComponent(): any { @@ -32,11 +36,11 @@ export class FormFieldComponent { } getFieldInputs(): FormFieldInputs { - const errors = this.field.options.errors || {}; + const errors = this.fieldOptions.errors || {}; return Object.assign( { placeholder: this.field.title, - disableSwitch: this.field.options.disableSwitch || false + disableSwitch: this.fieldOptions.disableSwitch || false }, Object.assign({}, this.field.inputs || {}), { From 4564e91a14ef2ac6d50110861492fc7e6ab07a36 Mon Sep 17 00:00:00 2001 From: cbourget Date: Wed, 17 Jul 2019 13:55:59 -0400 Subject: [PATCH 04/38] fix(workspace-selector): change many attr to multi --- .../workspace-selector/workspace-selector.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/common/src/lib/workspace/workspace-selector/workspace-selector.component.html b/packages/common/src/lib/workspace/workspace-selector/workspace-selector.component.html index 9d0bf7d728..813bc74ed7 100644 --- a/packages/common/src/lib/workspace/workspace-selector/workspace-selector.component.html +++ b/packages/common/src/lib/workspace/workspace-selector/workspace-selector.component.html @@ -1,6 +1,6 @@ From 80d34dfa0649e132868cc7feeac89bfb51886709 Mon Sep 17 00:00:00 2001 From: cbourget Date: Wed, 17 Jul 2019 13:56:22 -0400 Subject: [PATCH 05/38] style(workspace): clean workspace class --- .../src/lib/workspace/shared/workspace.ts | 54 ++++++------------- 1 file changed, 17 insertions(+), 37 deletions(-) diff --git a/packages/common/src/lib/workspace/shared/workspace.ts b/packages/common/src/lib/workspace/shared/workspace.ts index 6f9daf06bb..3db3a8b4aa 100644 --- a/packages/common/src/lib/workspace/shared/workspace.ts +++ b/packages/common/src/lib/workspace/shared/workspace.ts @@ -14,25 +14,20 @@ import { WorkspaceOptions } from './workspace.interfaces'; */ export class Workspace { - /** - * Observable of the selected entity - */ - public entity$ = new BehaviorSubject(undefined); - /** * Observable of the selected widget */ - public widget$ = new BehaviorSubject(undefined); + readonly widget$ = new BehaviorSubject(undefined); /** * Observable of the selected widget's inputs */ - public widgetInputs$ = new BehaviorSubject<{[key: string]: any}>({}); + readonly widgetInputs$ = new BehaviorSubject<{[key: string]: any}>({}); /** * Observable of the selected widget's subscribers */ - public widgetSubscribers$ = new BehaviorSubject<{[key: string]: (event: any) => void}>({}); + readonly widgetSubscribers$ = new BehaviorSubject<{[key: string]: (event: any) => void}>({}); /** * Subscription to the selected entity @@ -47,12 +42,12 @@ export class Workspace { /** * State change that trigger an update of the actions availability */ - private changes$: Subject = new Subject(); + private change: Subject = new Subject(); /** * Subscription to state changes */ - private changes$$: Subscription; + private change$: Subscription; /** * Workspace id @@ -79,11 +74,6 @@ export class Workspace { */ get actionStore(): ActionStore { return this.options.actionStore; } - /** - * Selected entity - */ - get entity(): E { return this.entity$.value; } - /** * Selected widget */ @@ -113,22 +103,17 @@ export class Workspace { this.active = true; if (this.entityStore !== undefined) { - this.entities$$ = this.entityStore.stateView - .manyBy$((record: EntityRecord) => record.state.selected === true) - .subscribe((records: EntityRecord[]) => { - // If more than one entity is selected, consider that no entity at all is selected. - const entity = (records.length === 0 || records.length > 1) ? undefined : records[0].entity; - this.onSelectEntity(entity); - }); + this.entities$$ = this.entityStore.stateView.all$() + .subscribe(() => this.onStateChange()); } if (this.actionStore !== undefined) { - this.changes$$ = this.changes$ - .pipe(debounceTime(50)) + this.change$ = this.change + .pipe(debounceTime(35)) .subscribe(() => this.actionStore.updateActionsAvailability()); } - this.changes$.next(); + this.change.next(); } /** @@ -141,8 +126,8 @@ export class Workspace { if (this.entities$$ !== undefined) { this.entities$$.unsubscribe(); } - if (this.changes$$ !== undefined) { - this.changes$$.unsubscribe(); + if (this.change$ !== undefined) { + this.change$.unsubscribe(); } } @@ -161,6 +146,7 @@ export class Workspace { this.widget$.next(widget); this.widgetInputs$.next(inputs); this.widgetSubscribers$.next(subscribers); + this.change.next(); } /** @@ -168,20 +154,14 @@ export class Workspace { */ deactivateWidget() { this.widget$.next(undefined); - this.changes$.next(); + this.change.next(); } /** - * When an entity is selected, keep a reference to that - * entity and update the actions availability. - * @param entity Entity + * When the state changes, update the actions availability. */ - private onSelectEntity(entity: E) { - if (entity === this.entity$.value) { - return; - } - this.entity$.next(entity); - this.changes$.next(); + private onStateChange() { + this.change.next(); } } From 83d61ce1464b417e9988409d9706c1cdf7a54a77 Mon Sep 17 00:00:00 2001 From: cbourget Date: Wed, 17 Jul 2019 13:58:21 -0400 Subject: [PATCH 06/38] feat(catalog): allow ctalogs to define query params and source options --- .../lib/catalog/shared/catalog.interface.ts | 2 + .../src/lib/catalog/shared/catalog.service.ts | 39 +++++++++++++------ 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/packages/geo/src/lib/catalog/shared/catalog.interface.ts b/packages/geo/src/lib/catalog/shared/catalog.interface.ts index a986fd5fcd..e697b6b58f 100644 --- a/packages/geo/src/lib/catalog/shared/catalog.interface.ts +++ b/packages/geo/src/lib/catalog/shared/catalog.interface.ts @@ -19,6 +19,8 @@ export interface Catalog { timeFilter?: TimeFilterOptions; queryFormat?: QueryFormat; queryHtmlTarget?: QueryHtmlTarget; + queryParams?: { [key: string]: string }; + sourceOptions?: { [key: string]: any }; count?: number; tooltipType?: TooltipType.ABSTRACT | TooltipType.TITLE; sortDirection?: 'asc' | 'desc'; diff --git a/packages/geo/src/lib/catalog/shared/catalog.service.ts b/packages/geo/src/lib/catalog/shared/catalog.service.ts index c9764f6b94..c97ceee1cc 100644 --- a/packages/geo/src/lib/catalog/shared/catalog.service.ts +++ b/packages/geo/src/lib/catalog/shared/catalog.service.ts @@ -140,6 +140,8 @@ export class CatalogService { private includeRecursiveItems(catalog: Catalog, layerList: any, items: CatalogItem[]) { // Dig all levels until last level (layer object are not defined on last level) const regexes = (catalog.regFilters || []).map((pattern: string) => new RegExp(pattern)); + const catalogQueryParams = catalog.queryParams || {}; + const catalogSourceOptions = catalog.sourceOptions || {}; for (const group of layerList.Layer) { if (group.Layer !== undefined) { @@ -168,19 +170,25 @@ export class CatalogService { const timeFilter = this.capabilitiesService.getTimeFilter(layer); const timeFilterable = timeFilter && Object.keys(timeFilter).length > 0 ? true : false; - const sourceOptions = { + const params = Object.assign({}, catalogQueryParams, { + layers: layer.Name, + feature_count: catalog.count + }); + const baseSourceOptions = { type: 'wms', url: catalog.url, - params: { - layers: layer.Name, - feature_count: catalog.count - }, timeFilter: { ...timeFilter, ...catalog.timeFilter }, timeFilterable: timeFilterable ? true : false, queryable: layer.queryable, queryFormat: configuredQueryFormat, queryHtmlTarget: catalog.queryHtmlTarget || QueryHtmlTarget.IFRAME - } as WMSDataSourceOptions; + }; + const sourceOptions = Object.assign( + {}, + baseSourceOptions, + catalogSourceOptions, + {params} + ) as WMSDataSourceOptions; layers.push({ id: generateIdFromSourceOptions(sourceOptions), @@ -220,24 +228,31 @@ export class CatalogService { private getWMTSItems(catalog: Catalog, capabilities: {[key: string]: any}): CatalogItemLayer[] { const layers = capabilities.Contents.Layer; const regexes = (catalog.regFilters || []).map((pattern: string) => new RegExp(pattern)); + const catalogQueryParams = catalog.queryParams || {}; + const catalogSourceOptions = catalog.sourceOptions || {}; return layers.map((layer: any) => { if (this.testLayerRegexes(layer.Identifier, regexes) === false) { return undefined; } - - const sourceOptions = { + const params = Object.assign({}, catalogQueryParams, { + version: '1.0.0' + }); + const baseSourceOptions = { type: 'wmts', url: catalog.url, layer: layer.Identifier, matrixSet: catalog.matrixSet, optionsFromCapabilities: true, requestEncoding: catalog.requestEncoding || 'KVP', - style: 'default', - params: { - version: '1.0.0' - } + style: 'default' } as WMTSDataSourceOptions; + const sourceOptions = Object.assign( + {}, + baseSourceOptions, + catalogSourceOptions, + {params} + ) as WMTSDataSourceOptions; return { id: generateIdFromSourceOptions(sourceOptions), From e502a52fc39e86a70f2b4c96687c4b24145f030d Mon Sep 17 00:00:00 2001 From: cbourget Date: Wed, 17 Jul 2019 13:59:32 -0400 Subject: [PATCH 07/38] feat(catalog): optionnally force a user to expand a group of layers before adding it to the map --- .../catalog-browser-group.component.html | 7 ++-- .../catalog-browser-group.component.ts | 34 +++++++++++++++++++ .../catalog-browser.component.html | 1 + .../catalog-browser.component.ts | 5 +++ 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/packages/geo/src/lib/catalog/catalog-browser/catalog-browser-group.component.html b/packages/geo/src/lib/catalog/catalog-browser/catalog-browser-group.component.html index 5e32477cb0..16c3876af0 100644 --- a/packages/geo/src/lib/catalog/catalog-browser/catalog-browser-group.component.html +++ b/packages/geo/src/lib/catalog/catalog-browser/catalog-browser-group.component.html @@ -1,11 +1,12 @@ + [collapsed]="collapsed" + (toggle)="onToggleCollapsed($event)">

{{title}}

@@ -17,6 +18,7 @@

{{title}}

matTooltipShowDelay="500" [matTooltip]="'igo.geo.catalog.group.removeFromMap' | translate" color="warn" + [disabled]="disabled$ | async" (click)="onToggleClick()"> @@ -28,6 +30,7 @@

{{title}}

tooltip-position="below" matTooltipShowDelay="500" [matTooltip]="'igo.geo.catalog.group.addToMap' | translate" + [disabled]="disabled$ | async" (click)="onToggleClick()"> diff --git a/packages/geo/src/lib/catalog/catalog-browser/catalog-browser-group.component.ts b/packages/geo/src/lib/catalog/catalog-browser/catalog-browser-group.component.ts index 8e61b494a9..c0ba9304e5 100644 --- a/packages/geo/src/lib/catalog/catalog-browser/catalog-browser-group.component.ts +++ b/packages/geo/src/lib/catalog/catalog-browser/catalog-browser-group.component.ts @@ -30,6 +30,7 @@ import { changeDetection: ChangeDetectionStrategy.OnPush }) export class CatalogBrowserGroupComponent implements OnInit, OnDestroy { + /** * Group's items store * @internal @@ -42,6 +43,12 @@ export class CatalogBrowserGroupComponent implements OnInit, OnDestroy { */ added$: BehaviorSubject = new BehaviorSubject(false); + /** + * Whether the toggle button is disabled + * @internal + */ + disabled$: BehaviorSubject = new BehaviorSubject(false); + /** * Catalog */ @@ -52,6 +59,16 @@ export class CatalogBrowserGroupComponent implements OnInit, OnDestroy { */ @Input() group: CatalogItemGroup; + /** + * Whether the group is collapsed + */ + @Input() collapsed: boolean = true; + + /** + * Whether the group can be toggled when it's collapsed + */ + @Input() toggleCollapsed: boolean = true; + /** * Parent catalog's items store state. Groups share a unique * EntityState that tracks the group and it's layers state (whether they are added or not). @@ -90,6 +107,7 @@ export class CatalogBrowserGroupComponent implements OnInit, OnDestroy { ngOnInit() { this.store.load(this.group.items); this.evaluateAdded(); + this.evaluateDisabled(this.collapsed); if (this.catalog && this.catalog.sortDirection !== undefined) { this.store.view.sort({ direction: this.catalog.sortDirection, @@ -124,6 +142,14 @@ export class CatalogBrowserGroupComponent implements OnInit, OnDestroy { this.added$.value ? this.remove() : this.add(); } + /** + * On toggle button click, emit the added change event + * @internal + */ + onToggleCollapsed(collapsed: boolean) { + this.evaluateDisabled(collapsed); + } + /** * When a layer is added or removed, evaluate if all the layers of the group * are now added or removed. If so, consider that the group itself is added @@ -184,4 +210,12 @@ export class CatalogBrowserGroupComponent implements OnInit, OnDestroy { }); this.added$.next(added); } + + private evaluateDisabled(collapsed: boolean) { + let disabled = false; + if (this.toggleCollapsed === false) { + disabled = collapsed; + } + this.disabled$.next(disabled); + } } diff --git a/packages/geo/src/lib/catalog/catalog-browser/catalog-browser.component.html b/packages/geo/src/lib/catalog/catalog-browser/catalog-browser.component.html index 0cdc51dd12..bd1243d19c 100644 --- a/packages/geo/src/lib/catalog/catalog-browser/catalog-browser.component.html +++ b/packages/geo/src/lib/catalog/catalog-browser/catalog-browser.component.html @@ -5,6 +5,7 @@ [catalog]="catalog" [group]="item" [state]="store.state" + [toggleCollapsed]="toggleCollapsedGroup" (addedChange)="onGroupAddedChange($event)" (layerAddedChange)="onLayerAddedChange($event)"> diff --git a/packages/geo/src/lib/catalog/catalog-browser/catalog-browser.component.ts b/packages/geo/src/lib/catalog/catalog-browser/catalog-browser.component.ts index f89998cc39..b5e12f4f1c 100644 --- a/packages/geo/src/lib/catalog/catalog-browser/catalog-browser.component.ts +++ b/packages/geo/src/lib/catalog/catalog-browser/catalog-browser.component.ts @@ -52,6 +52,11 @@ export class CatalogBrowserComponent implements OnInit, OnDestroy { */ @Input() map: IgoMap; + /** + * Whether a group can be toggled when it's collapsed + */ + @Input() toggleCollapsedGroup: boolean = true; + constructor( private layerService: LayerService, private cdRef: ChangeDetectorRef From f537a950dd947c1c41bd90318234ef9c781610f2 Mon Sep 17 00:00:00 2001 From: cbourget Date: Wed, 17 Jul 2019 14:00:21 -0400 Subject: [PATCH 08/38] fix(print): fix print undefined comment --- packages/geo/src/lib/print/shared/print.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/geo/src/lib/print/shared/print.service.ts b/packages/geo/src/lib/print/shared/print.service.ts index 420da034a8..539b2f6d86 100644 --- a/packages/geo/src/lib/print/shared/print.service.ts +++ b/packages/geo/src/lib/print/shared/print.service.ts @@ -223,10 +223,10 @@ export class PrintService { const marginBottom = 15; const heightPixels = doc.internal.pageSize.height - marginBottom; - let textProjScale: string; + let textProjScale: string = ''; if (projection === true) { const projText = translate.instant('igo.geo.printForm.projection'); - textProjScale = projText + ': ' + map.projection; + textProjScale += projText + ': ' + map.projection; } if (scale === true) { if (projection === true) { From b5bc01a46145e188d349b4d8b3038f24bd7dd39c Mon Sep 17 00:00:00 2001 From: cbourget Date: Wed, 17 Jul 2019 14:01:14 -0400 Subject: [PATCH 09/38] feat(catalog tool): allow catalog tool to define the toggle group input --- .../catalog-browser-tool.component.html | 3 ++- .../catalog-browser-tool/catalog-browser-tool.component.ts | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/integration/src/lib/catalog/catalog-browser-tool/catalog-browser-tool.component.html b/packages/integration/src/lib/catalog/catalog-browser-tool/catalog-browser-tool.component.html index c744a78a83..01bdcfbfa6 100644 --- a/packages/integration/src/lib/catalog/catalog-browser-tool/catalog-browser-tool.component.html +++ b/packages/integration/src/lib/catalog/catalog-browser-tool/catalog-browser-tool.component.html @@ -2,5 +2,6 @@ *ngIf="store$ | async as store" [catalog]="catalog" [store]="store" - [map]="map"> + [map]="map" + [toggleCollapsedGroup]="toggleCollapsedGroup"> diff --git a/packages/integration/src/lib/catalog/catalog-browser-tool/catalog-browser-tool.component.ts b/packages/integration/src/lib/catalog/catalog-browser-tool/catalog-browser-tool.component.ts index 48b25db182..fea03db7a8 100644 --- a/packages/integration/src/lib/catalog/catalog-browser-tool/catalog-browser-tool.component.ts +++ b/packages/integration/src/lib/catalog/catalog-browser-tool/catalog-browser-tool.component.ts @@ -1,5 +1,6 @@ import { Component, + Input, OnInit, OnDestroy, ChangeDetectionStrategy @@ -48,6 +49,11 @@ export class CatalogBrowserToolComponent implements OnInit, OnDestroy { */ private catalog$$: Subscription; + /** + * Whether a group can be toggled when it's collapsed + */ + @Input() toggleCollapsedGroup: boolean = true; + /** * Map to add layers to * @internal From bcd2a1162ccd2bde3876a05539405143b2243a18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Barbeau?= Date: Tue, 23 Jul 2019 13:32:20 -0400 Subject: [PATCH 10/38] fix(demo): fix action demo --- demo/src/app/common/action/action.component.ts | 17 +++++++++++++---- demo/src/locale/en.json | 8 +++++++- demo/src/locale/fr.json | 8 +++++++- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/demo/src/app/common/action/action.component.ts b/demo/src/app/common/action/action.component.ts index e383d6b922..2c078d3883 100644 --- a/demo/src/app/common/action/action.component.ts +++ b/demo/src/app/common/action/action.component.ts @@ -1,6 +1,11 @@ import { Component, OnInit, OnDestroy } from '@angular/core'; -import { Media, MediaOrientation, MediaService } from '@igo2/core'; +import { + Media, + MediaOrientation, + MediaService, + LanguageService +} from '@igo2/core'; import { ActionStore, ActionbarMode } from '@igo2/common'; import { Overlay } from '@angular/cdk/overlay'; @@ -23,7 +28,11 @@ export class AppActionComponent implements OnInit, OnDestroy { return ActionbarMode.Overlay; } - constructor(private mediaService: MediaService, public overlay: Overlay) {} + constructor( + public overlay: Overlay, + private mediaService: MediaService, + private languageService: LanguageService + ) {} ngOnInit() { const added = () => this.added === true; @@ -32,7 +41,7 @@ export class AppActionComponent implements OnInit, OnDestroy { { id: 'add', title: 'Add', - icon: 'add', + icon: 'plus', tooltip: 'Add Tooltip', handler: () => { alert('Add!'); @@ -43,7 +52,7 @@ export class AppActionComponent implements OnInit, OnDestroy { { id: 'edit', title: 'Edit', - icon: 'edit', + icon: 'pencil', tooltip: 'Edit Tooltip', handler: (item: string) => { alert(`Edit item ${item}!`); diff --git a/demo/src/locale/en.json b/demo/src/locale/en.json index c7f8a0ff3d..7924656796 100644 --- a/demo/src/locale/en.json +++ b/demo/src/locale/en.json @@ -4,5 +4,11 @@ "Salutation": "Salutation", "coordinates": "Show coordinates,", "googleMap": "Show in Google maps", - "googleStreetView": "Show in Google Street view" + "googleStreetView": "Show in Google Street view", + "Add Tooltip": "Add Tooltip", + "Edit Tooltip": "Edit Tooltip", + "Delete Tooltip": "Delete Tooltip", + "Add": "Add", + "Edit": "Edit", + "Delete": "Delete" } diff --git a/demo/src/locale/fr.json b/demo/src/locale/fr.json index 087dc8bb4f..4c2a6a4b5d 100644 --- a/demo/src/locale/fr.json +++ b/demo/src/locale/fr.json @@ -4,5 +4,11 @@ "Salutation": "Salutation", "coordinates": "Afficher les coordonnées", "googleMap": "Afficher sur Google maps", - "googleStreetView": "Afficher sur Google Street view" + "googleStreetView": "Afficher sur Google Street view", + "Add Tooltip": "Ajouter Tooltip", + "Edit Tooltip": "Éditer Tooltip", + "Delete Tooltip": "Supprimer Tooltip", + "Add": "Ajouter", + "Edit": "Éditer", + "Delete": "Supprimer" } From 58dd071e8a7ee34f0ba4641380c00feeaa233f2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-=C3=89tienne=20Lord?= <7397743+pelord@users.noreply.github.com> Date: Wed, 24 Jul 2019 09:34:00 -0400 Subject: [PATCH 11/38] feat(rotation-button): Set option to always show even if no rotation (#312) * feat(rotation-btn)Shown if disabled(except mobile) * feat(view) animate view rotation on reset * i18n(rotation) typo * feat(rotation-button)Set option to show/hide --- .../context/context/context.component.html | 2 +- .../geo/directions/directions.component.html | 2 +- demo/src/app/geo/print/print.component.html | 2 +- .../geo/simple-map/simple-map.component.html | 2 +- .../rotation-button.component.html | 21 ++++++++++++------- .../rotation-button.component.scss | 6 ++++++ .../rotation-button.component.ts | 13 ++++++++++++ .../src/lib/map/shared/controllers/view.ts | 2 +- packages/geo/src/locale/en.geo.json | 3 ++- packages/geo/src/locale/fr.geo.json | 3 ++- 10 files changed, 42 insertions(+), 14 deletions(-) diff --git a/demo/src/app/context/context/context.component.html b/demo/src/app/context/context/context.component.html index da98a5331f..0a7b6724f9 100644 --- a/demo/src/app/context/context/context.component.html +++ b/demo/src/app/context/context/context.component.html @@ -16,7 +16,7 @@ [map]="map"> - + diff --git a/demo/src/app/geo/directions/directions.component.html b/demo/src/app/geo/directions/directions.component.html index 06b50a544b..2503a807c7 100644 --- a/demo/src/app/geo/directions/directions.component.html +++ b/demo/src/app/geo/directions/directions.component.html @@ -12,7 +12,7 @@ - + diff --git a/demo/src/app/geo/print/print.component.html b/demo/src/app/geo/print/print.component.html index d24a411dd5..c7865faf9d 100644 --- a/demo/src/app/geo/print/print.component.html +++ b/demo/src/app/geo/print/print.component.html @@ -18,7 +18,7 @@ - + diff --git a/demo/src/app/geo/simple-map/simple-map.component.html b/demo/src/app/geo/simple-map/simple-map.component.html index 716527e633..a865626c32 100644 --- a/demo/src/app/geo/simple-map/simple-map.component.html +++ b/demo/src/app/geo/simple-map/simple-map.component.html @@ -12,7 +12,7 @@ - + diff --git a/packages/geo/src/lib/map/rotation-button/rotation-button.component.html b/packages/geo/src/lib/map/rotation-button/rotation-button.component.html index b4a8475d4b..1ab5abf982 100644 --- a/packages/geo/src/lib/map/rotation-button/rotation-button.component.html +++ b/packages/geo/src/lib/map/rotation-button/rotation-button.component.html @@ -1,10 +1,17 @@ -
- +
+ +
+ + + + + +
- - + diff --git a/packages/geo/src/lib/search/search-bar/search-bar.component.scss b/packages/geo/src/lib/search/search-bar/search-bar.component.scss index 614ec5d153..653f6fb120 100644 --- a/packages/geo/src/lib/search/search-bar/search-bar.component.scss +++ b/packages/geo/src/lib/search/search-bar/search-bar.component.scss @@ -15,7 +15,7 @@ :host ::ng-deep div.mat-form-field-infix { left: $igo-padding; right: $igo-padding; - padding: 0 0 12px 0 !important; + padding: 0 0 12px 0 !important; } :host ::ng-deep div.mat-form-field-underline { @@ -25,6 +25,8 @@ .igo-search-bar-container { position: relative; width: 100%; + display: inline-flex; + overflow: hidden; } .igo-search-bar-container > mat-form-field { @@ -36,12 +38,12 @@ } .search-bar-buttons { - position: absolute; - right: 40px; + position: relative; + right: 0px; + display: inline-flex; top: 0; & > button:nth-child(2)::before { content: ''; - position: absolute; left: 0px; top: 5px; border-right: 1px solid #ddd; @@ -50,10 +52,13 @@ } igo-search-selector { - display: inline-block; background-color: #fff; - position: absolute; - right: 0; + top: 0; + border-radius: 0; +} + +igo-search-settings { + background-color: #fff; top: 0; border-radius: 0; } diff --git a/packages/geo/src/lib/search/search-bar/search-bar.module.ts b/packages/geo/src/lib/search/search-bar/search-bar.module.ts index 61c27f84d1..ba08efa4e2 100644 --- a/packages/geo/src/lib/search/search-bar/search-bar.module.ts +++ b/packages/geo/src/lib/search/search-bar/search-bar.module.ts @@ -15,6 +15,7 @@ import { import { IgoLanguageModule } from '@igo2/core'; import { IgoSearchSelectorModule } from '../search-selector/search-selector.module'; +import { IgoSearchSettingsModule } from '../search-settings/search-settings.module'; import { SearchBarComponent } from './search-bar.component'; import { SearchUrlParamDirective } from './search-url-param.directive'; @@ -33,11 +34,11 @@ import { SearchUrlParamDirective } from './search-url-param.directive'; MatFormFieldModule, MatInputModule, IgoLanguageModule, - IgoSearchSelectorModule + IgoSearchSelectorModule, + IgoSearchSettingsModule ], exports: [ SearchBarComponent, - SearchBarComponent ], declarations: [ SearchBarComponent, diff --git a/packages/geo/src/lib/search/search-selector/search-selector.component.html b/packages/geo/src/lib/search/search-selector/search-selector.component.html index bd985846f2..fad9b790c7 100644 --- a/packages/geo/src/lib/search/search-selector/search-selector.component.html +++ b/packages/geo/src/lib/search/search-selector/search-selector.component.html @@ -13,7 +13,6 @@ + diff --git a/packages/geo/src/lib/search/search-selector/search-selector.component.ts b/packages/geo/src/lib/search/search-selector/search-selector.component.ts index 4ccaaf8c62..fa6f557ee8 100644 --- a/packages/geo/src/lib/search/search-selector/search-selector.component.ts +++ b/packages/geo/src/lib/search/search-selector/search-selector.component.ts @@ -9,6 +9,7 @@ import { import { SEARCH_TYPES } from '../shared/search.enums'; import { SearchSourceService } from '../shared/search-source.service'; +import { SearchSource } from '../shared/sources/source'; /** * This component allows a user to select a search type yo enable. In it's diff --git a/packages/geo/src/lib/search/search-settings/search-settings.component.html b/packages/geo/src/lib/search/search-settings/search-settings.component.html new file mode 100644 index 0000000000..84efd38e34 --- /dev/null +++ b/packages/geo/src/lib/search/search-settings/search-settings.component.html @@ -0,0 +1,63 @@ +
+ + + + + + + + + + + + + {{settingValue.title}} + + + + + {{settingValue.title}} + + + + + + + +
diff --git a/packages/geo/src/lib/search/search-settings/search-settings.component.scss b/packages/geo/src/lib/search/search-settings/search-settings.component.scss new file mode 100644 index 0000000000..dd230b40af --- /dev/null +++ b/packages/geo/src/lib/search/search-settings/search-settings.component.scss @@ -0,0 +1,14 @@ +@import '../../../../../core/src/style/partial/core.variables'; + +.igo-search-settings-button ::ng-deep div.mat-button-ripple-round { + border-radius: 0; +} + +.igo-search-settings-radio-group { + display: inline-flex; + flex-direction: column; +} + +.igo-search-settings-radio-group mat-radio-button { + margin: $igo-margin; +} diff --git a/packages/geo/src/lib/search/search-settings/search-settings.component.spec.ts b/packages/geo/src/lib/search/search-settings/search-settings.component.spec.ts new file mode 100644 index 0000000000..3e15623a2b --- /dev/null +++ b/packages/geo/src/lib/search/search-settings/search-settings.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SearchSettingsComponent } from './search-settings.component'; + +describe('SearchSettingsComponent', () => { + let component: SearchSettingsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SearchSettingsComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SearchSettingsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/packages/geo/src/lib/search/search-settings/search-settings.component.ts b/packages/geo/src/lib/search/search-settings/search-settings.component.ts new file mode 100644 index 0000000000..0566824232 --- /dev/null +++ b/packages/geo/src/lib/search/search-settings/search-settings.component.ts @@ -0,0 +1,81 @@ +import {MatCheckboxChange, MatRadioChange } from '@angular/material'; + +import { + Component, + Input, + Output, + EventEmitter, + OnInit, + ChangeDetectionStrategy +} from '@angular/core'; + +import { SearchSourceService } from '../shared/search-source.service'; +import { SearchSource } from '../shared/sources/source'; +import { SearchSourceSettings, SettingOptions } from '../shared/sources/source.interfaces'; + +/** + * This component allows a user to select a search type yo enable. In it's + * current version, only one search type can be selected at once (radio). If + * this component were to support more than one search source enabled (checkbox), + * the searchbar component would require a small change to it's + * placeholder getter. The search source service already supports having + * more than one search source enabled. + */ +@Component({ + selector: 'igo-search-settings', + templateUrl: './search-settings.component.html', + styleUrls: ['./search-settings.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class SearchSettingsComponent { + + /** + * Event emitted when the enabled search type changes + */ + @Output() change = new EventEmitter(); + + constructor(private searchSourceService: SearchSourceService) {} + + /** + * Get all search sources + * @internal + */ + getSearchSources(): SearchSource[] { + return this.searchSourceService.getSources(); + } + + /** + * Triggered when a setting is checked (checkbox style) + * @internal + */ + settingsValueCheckedCheckbox( + event: MatCheckboxChange, + source: SearchSource, + setting: SearchSourceSettings, + settingValue: SettingOptions + ) { + settingValue.enabled = event.checked; + source.setParamFromSetting(setting); + } + + /** + * Triggered when a setting is checked (radiobutton style) + * @internal + */ + settingsValueCheckedRadioButton( + event: MatRadioChange, + source: SearchSource, + setting: SearchSourceSettings, + settingValue: SettingOptions + ) { + setting.values.forEach( conf => { + if (conf.value !== settingValue.value) { + conf.enabled = !event.source.checked; + } else { + conf.enabled = event.source.checked; + } + }); + source.setParamFromSetting(setting); + } + +} diff --git a/packages/geo/src/lib/search/search-settings/search-settings.module.ts b/packages/geo/src/lib/search/search-settings/search-settings.module.ts new file mode 100644 index 0000000000..53e48eafde --- /dev/null +++ b/packages/geo/src/lib/search/search-settings/search-settings.module.ts @@ -0,0 +1,35 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SearchSettingsComponent } from './search-settings.component'; +import { + MatTooltipModule, + MatIconModule, + MatButtonModule, + MatMenuModule, + MatRadioModule, + MatCheckboxModule +} from '@angular/material'; + +import { IgoLanguageModule } from '@igo2/core'; + +import { IgoSearchSelectorModule } from '../search-selector/search-selector.module'; + +/** + * @ignore + */ +@NgModule({ + declarations: [SearchSettingsComponent], + imports: [ + CommonModule, + MatTooltipModule, + MatIconModule, + MatButtonModule, + MatMenuModule, + MatRadioModule, + MatCheckboxModule, + IgoSearchSelectorModule, + IgoLanguageModule + ], + exports: [SearchSettingsComponent] +}) +export class IgoSearchSettingsModule { } diff --git a/packages/geo/src/lib/search/search.module.ts b/packages/geo/src/lib/search/search.module.ts index c22be7f770..9b6119dcf5 100644 --- a/packages/geo/src/lib/search/search.module.ts +++ b/packages/geo/src/lib/search/search.module.ts @@ -8,18 +8,21 @@ import { provideDefaultCoordinatesSearchResultFormatter } from './shared/sources import { IgoSearchBarModule } from './search-bar/search-bar.module'; import { IgoSearchSelectorModule } from './search-selector/search-selector.module'; import { IgoSearchResultsModule } from './search-results/search-results.module'; +import { IgoSearchSettingsModule } from './search-settings/search-settings.module'; @NgModule({ imports: [ CommonModule, IgoSearchBarModule, IgoSearchSelectorModule, - IgoSearchResultsModule + IgoSearchResultsModule, + IgoSearchSettingsModule ], exports: [ IgoSearchBarModule, IgoSearchSelectorModule, - IgoSearchResultsModule + IgoSearchResultsModule, + IgoSearchSettingsModule ], declarations: [] }) diff --git a/packages/geo/src/lib/search/shared/search-source.service.ts b/packages/geo/src/lib/search/shared/search-source.service.ts index 13fbb592c9..2f778ba0bb 100644 --- a/packages/geo/src/lib/search/shared/search-source.service.ts +++ b/packages/geo/src/lib/search/shared/search-source.service.ts @@ -1,4 +1,5 @@ import { SearchSource } from './sources/source'; +import { SearchSourceSettings } from './sources/source.interfaces'; /** * Service where all available search sources are registered. @@ -39,4 +40,8 @@ export class SearchSourceService { } }); } + + setParamFromSetting(source: SearchSource, setting: SearchSourceSettings) { + source.setParamFromSetting(setting); + } } diff --git a/packages/geo/src/lib/search/shared/sources/icherche.ts b/packages/geo/src/lib/search/shared/sources/icherche.ts index 9ab182db3a..433798f804 100644 --- a/packages/geo/src/lib/search/shared/sources/icherche.ts +++ b/packages/geo/src/lib/search/shared/sources/icherche.ts @@ -67,7 +67,120 @@ export class IChercheSearchSource extends SearchSource implements TextSearch { protected getDefaultOptions(): SearchSourceOptions { return { title: 'ICherche Québec', - searchUrl: 'https://geoegl.msp.gouv.qc.ca/icherche/geocode' + searchUrl: 'https://geoegl.msp.gouv.qc.ca/icherche/geocode', + settings: [ + { + type: 'checkbox', + title: 'results type', + name: 'type', + values: [ + { + title: 'Adresse', + value: 'adresse', + enabled: true + }, + { + title: 'Ancienne adresse', + value: 'ancienne_adresse', + enabled: true + }, + { + title: 'Code Postal', + value: 'code_postal', + enabled: true + }, + { + title: 'Route', + value: 'route', + enabled: true + }, + { + title: 'Municipalité', + value: 'municipalite', + enabled: true + }, + { + title: 'Ancienne municipalité', + value: 'ancienne_municipalite', + enabled: true + }, + { + title: 'mrc', + value: 'mrc', + enabled: true + }, + { + title: 'Région administrative', + value: 'region_administrative', + enabled: true + } + ] + }, + { + type: 'radiobutton', + title: 'results limit', + name: 'limit', + values: [ + { + title: '1', + value: 1, + enabled: false + }, + { + title: '5', + value: 5, + enabled: true + }, + { + title: '10', + value: 10, + enabled: false + }, + { + title: '25', + value: 25, + enabled: false + }, + { + title: '50', + value: 50, + enabled: false + } + ] + }, + { + type: 'radiobutton', + title: 'trust level', + name: 'ecmax', + values: [ + { + title: '1', + value: 1, + enabled: false + }, + { + title: '15', + value: 15, + enabled: true + }, + { + title: '50', + value: 50, + enabled: false + }, + { + title: '75', + value: 75, + enabled: false + }, + { + title: '100', + value: 100, + enabled: false + } + ] + } + ] }; } @@ -91,9 +204,7 @@ export class IChercheSearchSource extends SearchSource implements TextSearch { fromObject: Object.assign( { q: term, - geometries: 'geom', - type: - 'adresse,code_postal,route,municipalite,mrc,region_administrative' + geometries: 'geom' }, this.params, options.params || {} @@ -165,8 +276,48 @@ export class IChercheReverseSearchSource extends SearchSource protected getDefaultOptions(): SearchSourceOptions { return { - title: 'ICherche Québec', - searchUrl: 'https://geoegl.msp.gouv.qc.ca/icherche/xy' + title: 'ICherche Québec (Géocodage inversé)', + searchUrl: 'https://geoegl.msp.gouv.qc.ca/icherche/xy', + + settings: [ + { + type: 'checkbox', + title: 'results type', + name: 'type', + values: [ + { + title: 'Adresse', + value: 'adresse', + enabled: true + }, + { + title: 'Route', + value: 'route', + enabled: false + }, + { + title: 'Arrondissement', + value: 'arrondissement', + enabled: false + }, + { + title: 'Municipalité', + value: 'municipalite', + enabled: true + }, + { + title: 'mrc', + value: 'mrc', + enabled: true + }, + { + title: 'Région administrative', + value: 'regadmin', + enabled: true + } + ] + } + ] }; } @@ -197,9 +348,8 @@ export class IChercheReverseSearchSource extends SearchSource fromObject: Object.assign( { loc: lonLat.join(','), - distance: distance ? String(distance) : '', - geometries: 'geom', - type: 'adresse,municipalite,mrc,regadmin' + distance: distance ? String(distance) : '100', + geometries: 'geom' }, this.params, options.params || {} diff --git a/packages/geo/src/lib/search/shared/sources/nominatim.ts b/packages/geo/src/lib/search/shared/sources/nominatim.ts index 09cf953d20..b72ae23196 100644 --- a/packages/geo/src/lib/search/shared/sources/nominatim.ts +++ b/packages/geo/src/lib/search/shared/sources/nominatim.ts @@ -33,7 +33,65 @@ export class NominatimSearchSource extends SearchSource implements TextSearch { protected getDefaultOptions(): SearchSourceOptions { return { title: 'Nominatim (OSM)', - searchUrl: 'https://nominatim.openstreetmap.org/search' + searchUrl: 'https://nominatim.openstreetmap.org/search', + settings: [ + { + type: 'radiobutton', + title: 'results limit', + name: 'limit', + values: [ + { + title: '10', + value: 10, + enabled: true + }, + { + title: '20', + value: 20, + enabled: false + }, + { + title: '50', + value: 50, + enabled: false + } + ] + }, + { + type: 'radiobutton', + title: 'country limitation', + name: 'countrycode', + values: [ + { + title: 'Canada', + value: 'CA', + enabled: true + }, + { + title: 'Le monde', + value: null, + enabled: false + } + ] + }, + { + type: 'radiobutton', + title: 'multiple object', + name: 'dedupe', + values: [ + { + title: 'Oui', + value: 0, + enabled: false + }, + { + title: 'Non', + value: 1, + enabled: true + } + ] + } + ] }; } diff --git a/packages/geo/src/lib/search/shared/sources/source.interfaces.ts b/packages/geo/src/lib/search/shared/sources/source.interfaces.ts index bee31533b3..3be4dcb330 100644 --- a/packages/geo/src/lib/search/shared/sources/source.interfaces.ts +++ b/packages/geo/src/lib/search/shared/sources/source.interfaces.ts @@ -7,8 +7,21 @@ export interface SearchSourceOptions { distance?: number; zoomMaxOnSelect?: number; params?: { [key: string]: string }; + settings?: SearchSourceSettings[]; } +export interface SearchSourceSettings { + type: 'radiobutton'|'checkbox'; + values: SettingOptions[]; + title: string; + name: string; +} + +export interface SettingOptions { + value: string|number; + enabled: boolean; + title: string; +} export interface TextSearchOptions { params?: { [key: string]: string }; } diff --git a/packages/geo/src/lib/search/shared/sources/source.ts b/packages/geo/src/lib/search/shared/sources/source.ts index c7c988dc45..8b36cf78cf 100644 --- a/packages/geo/src/lib/search/shared/sources/source.ts +++ b/packages/geo/src/lib/search/shared/sources/source.ts @@ -4,7 +4,8 @@ import { SearchResult } from '../search.interfaces'; import { SearchSourceOptions, TextSearchOptions, - ReverseSearchOptions + ReverseSearchOptions, + SearchSourceSettings } from './source.interfaces'; /** @@ -83,6 +84,35 @@ export class SearchSource { return this.options.params === undefined ? {} : this.options.params; } + /** + * Search settings + */ + get settings(): SearchSourceSettings[] { + return this.options.settings === undefined ? [] : this.options.settings; + } + + setParamFromSetting(setting: SearchSourceSettings) { + switch (setting.type) { + case 'radiobutton': + setting.values.forEach( conf => { + if (conf.enabled) { + this.options.params = Object.assign( (this.options.params || {}), + { [setting.name] : conf.value } ); + } + }); + break; + case 'checkbox': + const confValue = setting.values + .filter((conf) => conf.enabled) + .map((conf) => conf.value) + .join(','); + + this.options.params = Object.assign( (this.options.params || {}), + { [setting.name] : confValue } ); + break; + } + } + /** * Search results display order */ @@ -92,6 +122,11 @@ export class SearchSource { constructor(options: SearchSourceOptions) { this.options = Object.assign({}, this.getDefaultOptions(), options); + + // Set Default Params from Settings + this.settings.forEach( setting => { + this.setParamFromSetting(setting); + }); } } diff --git a/packages/geo/src/locale/en.geo.json b/packages/geo/src/locale/en.geo.json index e42e86a8d2..84f22e88e4 100644 --- a/packages/geo/src/locale/en.geo.json +++ b/packages/geo/src/locale/en.geo.json @@ -143,7 +143,7 @@ "A4": "A4", "A5": "A5", "Letter": "Letter", - "Legal": "Legal" + "Legal": "Legal" }, "imageFormat": "Image format", "orientation" : "Orientation", @@ -215,7 +215,19 @@ "url": "URL" } }, - "menu.tooltip": "Search Options" + "menu.tooltip": "Search Options", + "settings": { + "title": "Settings" + }, + "searchSources": { + "settings": { + "results type": "Results type", + "trust level": "Trust level", + "results limit": "Limit", + "multiple object": "Multiple object", + "country limitation": "Limitation (country)" + } + } }, "geometry": { "geometry": "Geometry", diff --git a/packages/geo/src/locale/fr.geo.json b/packages/geo/src/locale/fr.geo.json index 94fe6b161e..4c450d93fc 100644 --- a/packages/geo/src/locale/fr.geo.json +++ b/packages/geo/src/locale/fr.geo.json @@ -143,7 +143,7 @@ "A4": "A4", "A5": "A5", "Letter": "Lettre", - "Legal": "Légal" + "Legal": "Légal" }, "imageFormat": "Format de l'image", "orientation" : "Orientation", @@ -216,7 +216,19 @@ "url": "URL" } }, - "menu.tooltip": "Options de recherche" + "menu.tooltip": "Options de recherche", + "settings": { + "title": "Paramètres" + }, + "searchSources": { + "settings": { + "results type": "Type de résultat", + "trust level": "Niveau de confiance", + "results limit": "Limite", + "multiple object": "Objet multiple", + "country limitation": "Limitation (pays)" + } + } }, "geometry": { "geometry": "Geometrie", From e69276eb44ce62cdc155e932a524e3b0ef33d92d Mon Sep 17 00:00:00 2001 From: matrottier Date: Thu, 25 Jul 2019 09:48:28 -0400 Subject: [PATCH 19/38] feat(search): Searchsource hashtag (#371) * issue #349 * add setting nominatim * ajout de conf pour les services, stop propagation click, fix lint * ajout d'une distance pour la recherche * ajout traduction setting * typing and dusting * feat(geo.search) new compoenent search-settings * style(geo.search.search-selector) lint * feat(geo.searchsource) add hashtag * fix(geo.search.search-bar) respect minlength * style(geo.search.search-bar) delete console.log * feat(geo.search) Add hashtag capability * refactor(geo.seach.shared.source.icherche) same thing with less * fix(geo.search.search-bar) respect minlength and no call when just hashtag --- .../search/search-bar/search-bar.component.ts | 5 +- .../search-selector.component.ts | 45 ++++++++++ .../search-selector/search-selector.module.ts | 6 +- .../search-settings.component.ts | 2 +- .../search/shared/search-source.service.ts | 5 ++ .../src/lib/search/shared/sources/icherche.ts | 36 +++++++- .../lib/search/shared/sources/nominatim.ts | 87 ++++++++++++++++++- .../src/lib/search/shared/sources/source.ts | 41 +++++++-- 8 files changed, 213 insertions(+), 14 deletions(-) diff --git a/packages/geo/src/lib/search/search-bar/search-bar.component.ts b/packages/geo/src/lib/search/search-bar/search-bar.component.ts index 8ec0775953..25e3b18e14 100644 --- a/packages/geo/src/lib/search/search-bar/search-bar.component.ts +++ b/packages/geo/src/lib/search/search-bar/search-bar.component.ts @@ -216,7 +216,8 @@ export class SearchBarComponent implements OnInit, OnDestroy { } this.term = term; - if (term.length >= this.minLength || term.length === 0) { + if (term.replace(/(#[^\s]*)/g, '').trim().length >= this.minLength || + term.replace(/(#[^\s]*)/g, '').trim().length === 0) { this.stream$.next(term); } } @@ -252,7 +253,7 @@ export class SearchBarComponent implements OnInit, OnDestroy { * @param term Search term */ private doSearch(term: string | undefined) { - if (term === undefined || term === '') { + if (term === undefined || term.replace(/(#[^\s]*)/g, '').trim() === '') { if (this.store !== undefined) { this.store.clear(); } diff --git a/packages/geo/src/lib/search/search-selector/search-selector.component.ts b/packages/geo/src/lib/search/search-selector/search-selector.component.ts index fa6f557ee8..7c88a7ad6b 100644 --- a/packages/geo/src/lib/search/search-selector/search-selector.component.ts +++ b/packages/geo/src/lib/search/search-selector/search-selector.component.ts @@ -1,3 +1,5 @@ +import {MatCheckboxChange, MatRadioChange } from '@angular/material'; + import { Component, Input, @@ -10,6 +12,7 @@ import { import { SEARCH_TYPES } from '../shared/search.enums'; import { SearchSourceService } from '../shared/search-source.service'; import { SearchSource } from '../shared/sources/source'; +import { SearchSourceSettings, SettingOptions } from '../shared/sources/source.interfaces'; /** * This component allows a user to select a search type yo enable. In it's @@ -83,4 +86,46 @@ export class SearchSelectorComponent implements OnInit { this.change.emit(searchType); } + /** + * Get all search sources + * @internal + */ + getSearchSources(): SearchSource[] { + return this.searchSourceService.getSources(); + } + + /** + * Triggered when a setting is checked (checkbox style) + * @internal + */ + settingsValueCheckedCheckbox( + event: MatCheckboxChange, + source: SearchSource, + setting: SearchSourceSettings, + settingValue: SettingOptions + ) { + settingValue.enabled = event.checked; + source.setParamFromSetting(setting); + } + + /** + * Triggered when a setting is checked (radiobutton style) + * @internal + */ + settingsValueCheckedRadioButton( + event: MatRadioChange, + source: SearchSource, + setting: SearchSourceSettings, + settingValue: SettingOptions + ) { + setting.values.forEach( conf => { + if (conf.value !== settingValue.value) { + conf.enabled = !event.source.checked; + } else { + conf.enabled = event.source.checked; + } + }); + source.setParamFromSetting(setting); + } + } diff --git a/packages/geo/src/lib/search/search-selector/search-selector.module.ts b/packages/geo/src/lib/search/search-selector/search-selector.module.ts index 029f88e66b..d663057729 100644 --- a/packages/geo/src/lib/search/search-selector/search-selector.module.ts +++ b/packages/geo/src/lib/search/search-selector/search-selector.module.ts @@ -6,7 +6,9 @@ import { MatIconModule, MatButtonModule, MatMenuModule, - MatRadioModule + MatRadioModule, + MatTabsModule, + MatCheckboxModule } from '@angular/material'; import { IgoLanguageModule } from '@igo2/core'; @@ -24,6 +26,8 @@ import { SearchSelectorComponent } from './search-selector.component'; MatButtonModule, MatMenuModule, MatRadioModule, + MatTabsModule, + MatCheckboxModule, IgoLanguageModule ], exports: [SearchSelectorComponent], diff --git a/packages/geo/src/lib/search/search-settings/search-settings.component.ts b/packages/geo/src/lib/search/search-settings/search-settings.component.ts index 0566824232..13a6307ba1 100644 --- a/packages/geo/src/lib/search/search-settings/search-settings.component.ts +++ b/packages/geo/src/lib/search/search-settings/search-settings.component.ts @@ -5,7 +5,6 @@ import { Input, Output, EventEmitter, - OnInit, ChangeDetectionStrategy } from '@angular/core'; @@ -27,6 +26,7 @@ import { SearchSourceSettings, SettingOptions } from '../shared/sources/source.i styleUrls: ['./search-settings.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) + export class SearchSettingsComponent { /** diff --git a/packages/geo/src/lib/search/shared/search-source.service.ts b/packages/geo/src/lib/search/shared/search-source.service.ts index 2f778ba0bb..88ef009cd4 100644 --- a/packages/geo/src/lib/search/shared/search-source.service.ts +++ b/packages/geo/src/lib/search/shared/search-source.service.ts @@ -41,6 +41,11 @@ export class SearchSourceService { }); } + /** + * Set Param from the selected settings + * @param source search-source + * @param setting settings + */ setParamFromSetting(source: SearchSource, setting: SearchSourceSettings) { source.setParamFromSetting(setting); } diff --git a/packages/geo/src/lib/search/shared/sources/icherche.ts b/packages/geo/src/lib/search/shared/sources/icherche.ts index 433798f804..fee377938c 100644 --- a/packages/geo/src/lib/search/shared/sources/icherche.ts +++ b/packages/geo/src/lib/search/shared/sources/icherche.ts @@ -203,11 +203,11 @@ export class IChercheSearchSource extends SearchSource implements TextSearch { return new HttpParams({ fromObject: Object.assign( { - q: term, + q: this.computeTerm(term), geometries: 'geom' }, this.params, - options.params || {} + this.computeOptionsParam(term, options || {}).params ) }); } @@ -251,6 +251,38 @@ export class IChercheSearchSource extends SearchSource implements TextSearch { ); return Object.assign(properties, { type: data.doc_type }); } + + /** + * Remove hashtag from query + * @param term Query with hashtag + */ + private computeTerm(term: string): string { + return term.replace(/(#[^\s]*)/g, ''); + } + + /** + * Add hashtag to param if valid + * @param term Query with hashtag + * @param options TextSearchOptions + */ + private computeOptionsParam(term: string, options: TextSearchOptions): TextSearchOptions { + const tags = term.match(/(#[^\s]+)/g); + if ( tags ) { + let typeValue = ''; + let hashtagToAdd = false; + tags.forEach( value => { + if (super.hashtagValid(super.getSettingsValues('type'), value, true)) { + typeValue += value.substring(1) + ','; + hashtagToAdd = true; + } + }); + if (hashtagToAdd) { + options.params = Object.assign( (options.params || {}), + { type : typeValue.slice(0, -1) } ); + } + } + return options; + } } /** diff --git a/packages/geo/src/lib/search/shared/sources/nominatim.ts b/packages/geo/src/lib/search/shared/sources/nominatim.ts index b72ae23196..4bf1b11500 100644 --- a/packages/geo/src/lib/search/shared/sources/nominatim.ts +++ b/packages/geo/src/lib/search/shared/sources/nominatim.ts @@ -8,7 +8,7 @@ import { FEATURE, Feature, FeatureGeometry } from '../../../feature'; import { SearchResult } from '../search.interfaces'; import { SearchSource, TextSearch } from './source'; -import { SearchSourceOptions, TextSearchOptions } from './source.interfaces'; +import { SearchSourceOptions, TextSearchOptions, SearchSourceSettings } from './source.interfaces'; import { NominatimData } from './nominatim.interfaces'; /** @@ -30,11 +30,42 @@ export class NominatimSearchSource extends SearchSource implements TextSearch { return NominatimSearchSource.id; } + /* + * Source : https://wiki.openstreetmap.org/wiki/Key:amenity + */ protected getDefaultOptions(): SearchSourceOptions { return { title: 'Nominatim (OSM)', searchUrl: 'https://nominatim.openstreetmap.org/search', settings: [ + { + type: 'checkbox', + title: 'results type', + name: 'amenity', + values: [ + { + title: 'Restauration', + value: 'bar,bbq,biergaten,cafe,drinking_water,fast_food,food_court,ice_cream,pub,restaurant', + enabled: false + }, + { + title: 'Santé', + value: 'baby_hatch,clinic,dentist,doctors,hospital,nursing_home,pharmacy,social_facility,veterinary', + enabled: false + }, + { + title: 'Divertissement', + value: 'arts_centre,brothel,casino,cinema,community_center_fountain,gambling,nightclub,planetarium \ + ,public_bookcase,social_centre,stripclub,studio,swingerclub,theatre,internet_cafe', + enabled: false + }, + { + title: 'Finance', + value: 'atm,bank,bureau_de_change', + enabled: false + } + ] + }, { type: 'radiobutton', title: 'results limit', @@ -60,7 +91,7 @@ export class NominatimSearchSource extends SearchSource implements TextSearch { { type: 'radiobutton', title: 'country limitation', - name: 'countrycode', + name: 'countrycodes', values: [ { title: 'Canada', @@ -117,7 +148,7 @@ export class NominatimSearchSource extends SearchSource implements TextSearch { return new HttpParams({ fromObject: Object.assign( { - q: term, + q: this.computeTerm(term), format: 'json' }, this.params, @@ -183,4 +214,54 @@ export class NominatimSearchSource extends SearchSource implements TextSearch { parseFloat(data.boundingbox[1]) ]; } + + private computeTerm(term: string): string { + term = this.computeTermTags(term); + return term; + } + + /** + * Add hashtag from query in Nominatim's format (+[]) + * @param term Query with hashtag + */ + private computeTermTags(term: string): string { + const tags = term.match(/(#[^\s]+)/g); + + let addTagsFromSettings = true; + if ( tags ) { + tags.forEach( value => { + term = term.replace(value, ''); + if ( super.hashtagValid(super.getSettingsValues('amenity'), value) ) { + term += '+[' + value.substring(1) + ']'; + addTagsFromSettings = false; + } + }); + addTagsFromSettings = false; + } + + if (addTagsFromSettings) { + term = this.computeTermSettings(term); + } + return term; + } + + /** + * Add hashtag from settings in Nominatim's format (+[]) + * @param term Query + */ + private computeTermSettings(term: string): string { + this.options.settings.forEach( settings => { + if (settings.name === 'amenity') { + settings.values.forEach( conf => { + if (conf.enabled && typeof conf.value === 'string') { + const splitted = conf.value.split(','); + splitted.forEach( value => { + term += '+[' + value + ']'; + }); + } + }); + } + }); + return term; + } } diff --git a/packages/geo/src/lib/search/shared/sources/source.ts b/packages/geo/src/lib/search/shared/sources/source.ts index 8b36cf78cf..d788c5de5b 100644 --- a/packages/geo/src/lib/search/shared/sources/source.ts +++ b/packages/geo/src/lib/search/shared/sources/source.ts @@ -91,6 +91,9 @@ export class SearchSource { return this.options.settings === undefined ? [] : this.options.settings; } + /** + * Set params from selected settings + */ setParamFromSetting(setting: SearchSourceSettings) { switch (setting.type) { case 'radiobutton': @@ -102,11 +105,13 @@ export class SearchSource { }); break; case 'checkbox': - const confValue = setting.values - .filter((conf) => conf.enabled) - .map((conf) => conf.value) - .join(','); - + let confValue = ''; + setting.values.forEach( conf => { + if (conf.enabled) { + confValue += conf.value + ','; + } + }); + confValue = confValue.slice(0, -1); this.options.params = Object.assign( (this.options.params || {}), { [setting.name] : confValue } ); break; @@ -128,6 +133,32 @@ export class SearchSource { this.setParamFromSetting(setting); }); } + + /** + * Check if hashtag is valid + * @param hashtag hashtag from query + * @param completeMatch boolean + */ + hashtagValid(searchSourceSetting: SearchSourceSettings, hashtag: string, completeMatch = false): boolean { + let hashtagIsValid = false; + searchSourceSetting.values.forEach( conf => { + const re = new RegExp('' + hashtag.substring(1) + '', 'g'); + if ( typeof conf.value === 'string') { + if ( (completeMatch && conf.value === hashtag.substring(1)) || + ( !completeMatch && conf.value.match(re)) ) { + hashtagIsValid = true; + } + } + }); + return hashtagIsValid; + } + + getSettingsValues(search: string): SearchSourceSettings { + return this.getDefaultOptions().settings.find( (value: SearchSourceSettings) => { + return value.name === search; + }); + } + } /** From d22d3c29d910955106bef09918d2e277fc3dbe91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Barbeau?= Date: Thu, 25 Jul 2019 10:22:13 -0400 Subject: [PATCH 20/38] feat(datasource): add Cluster datasource (#374) * feat(datasource,layer,style): add Cluster datasource and configurable style for it --- .../lib/share-map/shared/share-map.service.ts | 15 +- .../datasource/shared/datasource.service.ts | 20 ++- .../datasources/any-datasource.interface.ts | 4 +- .../shared/datasources/any-datasource.ts | 4 +- .../cluster-datasource.interface.ts | 11 ++ .../shared/datasources/cluster-datasource.ts | 21 +++ .../datasources/datasource.interface.ts | 5 +- .../datasource/shared/datasources/index.ts | 2 + .../geo/src/lib/layer/shared/clusterParam.ts | 5 + .../geo/src/lib/layer/shared/layer.service.ts | 41 ++++- .../shared/layers/vector-layer.interface.ts | 11 +- .../lib/layer/shared/layers/vector-layer.ts | 7 +- .../layer/shared/layers/vectortile-layer.ts | 6 +- .../geo/src/lib/layer/shared/style.service.ts | 170 +++++++++++++----- 14 files changed, 239 insertions(+), 83 deletions(-) create mode 100644 packages/geo/src/lib/datasource/shared/datasources/cluster-datasource.interface.ts create mode 100644 packages/geo/src/lib/datasource/shared/datasources/cluster-datasource.ts create mode 100644 packages/geo/src/lib/layer/shared/clusterParam.ts diff --git a/packages/context/src/lib/share-map/shared/share-map.service.ts b/packages/context/src/lib/share-map/shared/share-map.service.ts index 49469ab174..cb40b0f757 100644 --- a/packages/context/src/lib/share-map/shared/share-map.service.ts +++ b/packages/context/src/lib/share-map/shared/share-map.service.ts @@ -103,8 +103,8 @@ export class ShareMapService { let zoom = 'zoom=' + map.viewController.getZoom(); const arrayCenter = map.viewController.getCenter('EPSG:4326') || []; - const long = arrayCenter[0].toFixed(5).replace(/\.([^0]+)0+$/,".$1") - const lat = arrayCenter[1].toFixed(5).replace(/\.([^0]+)0+$/,".$1") + const long = arrayCenter[0].toFixed(5).replace(/\.([^0]+)0+$/, '.$1'); + const lat = arrayCenter[1].toFixed(5).replace(/\.([^0]+)0+$/, '.$1'); const center = `center=${long},${lat}`.replace(/.00000/g, ''); let context = ''; if (this.contextService.context$.value) { @@ -112,22 +112,25 @@ export class ShareMapService { context = 'context=' + this.contextService.context$.value.uri; } if (this.contextService.context$.value.map.view.zoom) { - zoom = this.contextService.context$.value.map.view.zoom === map.viewController.getZoom() ? '' : 'zoom=' + map.viewController.getZoom(); + zoom = + this.contextService.context$.value.map.view.zoom === + map.viewController.getZoom() + ? '' + : 'zoom=' + map.viewController.getZoom(); } } let url = `${location.origin}${ location.pathname - }?${context}&${zoom}&${center}&${layersUrl}&${llc}&${routingUrl}` + }?${context}&${zoom}&${center}&${layersUrl}&${llc}&${routingUrl}`; for (let i = 0; i < 5; i++) { url = url.replace(/&&/g, '&'); url = url.endsWith('&') ? url.slice(0, -1) : url; } url = url.endsWith('&') ? url.slice(0, -1) : url; - url = url.replace('?&', '?') + url = url.replace('?&', '?'); return url; } - } diff --git a/packages/geo/src/lib/datasource/shared/datasource.service.ts b/packages/geo/src/lib/datasource/shared/datasource.service.ts index b602b92060..8d7ecc7534 100644 --- a/packages/geo/src/lib/datasource/shared/datasource.service.ts +++ b/packages/geo/src/lib/datasource/shared/datasource.service.ts @@ -27,7 +27,9 @@ import { WebSocketDataSource, AnyDataSourceOptions, MVTDataSource, - MVTDataSourceOptions + MVTDataSourceOptions, + ClusterDataSource, + ClusterDataSourceOptions } from './datasources'; @Injectable({ @@ -86,15 +88,18 @@ export class DataSourceService { ); break; case 'mvt': - dataSource = this.createMVTDataSource( - context as MVTDataSourceOptions - ); + dataSource = this.createMVTDataSource(context as MVTDataSourceOptions); break; case 'tilearcgisrest': dataSource = this.createTileArcGISRestDataSource( context as TileArcGISRestDataSourceOptions ); break; + case 'cluster': + dataSource = this.createClusterDataSource( + context as ClusterDataSourceOptions + ); + break; default: console.error(context); throw new Error('Invalid datasource type'); @@ -208,9 +213,16 @@ export class DataSourceService { ) ); } + private createMVTDataSource( context: MVTDataSourceOptions ): Observable { return new Observable(d => d.next(new MVTDataSource(context))); } + + private createClusterDataSource( + context: ClusterDataSourceOptions + ): Observable { + return new Observable(d => d.next(new ClusterDataSource(context))); + } } diff --git a/packages/geo/src/lib/datasource/shared/datasources/any-datasource.interface.ts b/packages/geo/src/lib/datasource/shared/datasources/any-datasource.interface.ts index c0224f574b..aa4d8c1a3f 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/any-datasource.interface.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/any-datasource.interface.ts @@ -9,6 +9,7 @@ import { CartoDataSourceOptions } from './carto-datasource.interface'; import { ArcGISRestDataSourceOptions } from './arcgisrest-datasource.interface'; import { TileArcGISRestDataSourceOptions } from './tilearcgisrest-datasource.interface'; import { MVTDataSourceOptions } from './mvt-datasource.interface'; +import { ClusterDataSourceOptions } from './cluster-datasource.interface'; export type AnyDataSourceOptions = | DataSourceOptions @@ -21,4 +22,5 @@ export type AnyDataSourceOptions = | CartoDataSourceOptions | ArcGISRestDataSourceOptions | TileArcGISRestDataSourceOptions - | MVTDataSourceOptions; + | MVTDataSourceOptions + | ClusterDataSourceOptions; diff --git a/packages/geo/src/lib/datasource/shared/datasources/any-datasource.ts b/packages/geo/src/lib/datasource/shared/datasources/any-datasource.ts index 338fe925a0..616f9accae 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/any-datasource.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/any-datasource.ts @@ -10,6 +10,7 @@ import { ArcGISRestDataSource } from './arcgisrest-datasource'; import { TileArcGISRestDataSource } from './tilearcgisrest-datasource'; import { WebSocketDataSource } from './websocket-datasource'; import { MVTDataSource } from './mvt-datasource'; +import { ClusterDataSource } from './cluster-datasource'; export type AnyDataSource = | DataSource @@ -23,4 +24,5 @@ export type AnyDataSource = | ArcGISRestDataSource | TileArcGISRestDataSource | WebSocketDataSource - | MVTDataSource; + | MVTDataSource + | ClusterDataSource; diff --git a/packages/geo/src/lib/datasource/shared/datasources/cluster-datasource.interface.ts b/packages/geo/src/lib/datasource/shared/datasources/cluster-datasource.interface.ts new file mode 100644 index 0000000000..180147b9a3 --- /dev/null +++ b/packages/geo/src/lib/datasource/shared/datasources/cluster-datasource.interface.ts @@ -0,0 +1,11 @@ +import olSourceVector from 'ol/source/Vector'; + +import { FeatureDataSource } from './feature-datasource'; +import { FeatureDataSourceOptions } from './feature-datasource.interface'; + +export interface ClusterDataSourceOptions extends FeatureDataSourceOptions { + // type?: 'cluster'; + distance?: number; + source?: FeatureDataSource; + ol?: olSourceVector; +} diff --git a/packages/geo/src/lib/datasource/shared/datasources/cluster-datasource.ts b/packages/geo/src/lib/datasource/shared/datasources/cluster-datasource.ts new file mode 100644 index 0000000000..4f3a0925b1 --- /dev/null +++ b/packages/geo/src/lib/datasource/shared/datasources/cluster-datasource.ts @@ -0,0 +1,21 @@ +import olSourceCluster from 'ol/source/Cluster'; + +import { uuid } from '@igo2/utils'; + +import { FeatureDataSource } from './feature-datasource'; +import { ClusterDataSourceOptions } from './cluster-datasource.interface'; + +export class ClusterDataSource extends FeatureDataSource { + public options: ClusterDataSourceOptions; + public ol: olSourceCluster; + + protected createOlSource(): olSourceCluster { + this.options.format = this.getSourceFormatFromOptions(this.options); + this.options.source = super.createOlSource(); + return new olSourceCluster(this.options); + } + + protected generateId() { + return uuid(); + } +} diff --git a/packages/geo/src/lib/datasource/shared/datasources/datasource.interface.ts b/packages/geo/src/lib/datasource/shared/datasources/datasource.interface.ts index e3f24cc3d1..6ecc901281 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/datasource.interface.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/datasource.interface.ts @@ -1,6 +1,4 @@ import olSource from 'ol/source/Source'; - -import { DataSource } from './datasource'; import { DownloadOptions } from '../../../download/shared/download.interface'; export interface DataSourceOptions { @@ -15,7 +13,8 @@ export interface DataSourceOptions { | 'arcgisrest' | 'tilearcgisrest' | 'websocket' - | 'mvt'; + | 'mvt' + | 'cluster'; legend?: DataSourceLegendOptions; optionsFromCapabilities?: boolean; // title: string; diff --git a/packages/geo/src/lib/datasource/shared/datasources/index.ts b/packages/geo/src/lib/datasource/shared/datasources/index.ts index ea7a9f82b7..6ce8d08533 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/index.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/index.ts @@ -24,5 +24,7 @@ export * from './websocket-datasource'; export * from './websocket-datasource.interface'; export * from './mvt-datasource'; export * from './mvt-datasource.interface'; +export * from './cluster-datasource'; +export * from './cluster-datasource.interface'; export * from './any-datasource'; export * from './any-datasource.interface'; diff --git a/packages/geo/src/lib/layer/shared/clusterParam.ts b/packages/geo/src/lib/layer/shared/clusterParam.ts new file mode 100644 index 0000000000..989a69f6ef --- /dev/null +++ b/packages/geo/src/lib/layer/shared/clusterParam.ts @@ -0,0 +1,5 @@ +export interface ClusterParam { + clusterRange?: Array; // utiliser lorsqu'on veux une symbologie active pour une source cluster. + clusterIcon?: string; + clusterScale?: number; +} diff --git a/packages/geo/src/lib/layer/shared/layer.service.ts b/packages/geo/src/lib/layer/shared/layer.service.ts index 409b418055..da5723d74b 100644 --- a/packages/geo/src/lib/layer/shared/layer.service.ts +++ b/packages/geo/src/lib/layer/shared/layer.service.ts @@ -16,7 +16,8 @@ import { ArcGISRestDataSource, TileArcGISRestDataSource, WebSocketDataSource, - MVTDataSource + MVTDataSource, + ClusterDataSource } from '../../datasource'; import { DataSourceService } from '../../datasource/shared/datasource.service'; @@ -62,7 +63,8 @@ export class LayerService { layerOptions.source.options.optionsFromCapabilities ) { layerOptions = ObjectUtils.mergeDeep( - (layerOptions.source.options as any)._layerOptionsFromCapabilities || {}, + (layerOptions.source.options as any)._layerOptionsFromCapabilities || + {}, layerOptions || {} ); } @@ -80,13 +82,16 @@ export class LayerService { case WFSDataSource: case ArcGISRestDataSource: case WebSocketDataSource: + case ClusterDataSource: layer = this.createVectorLayer(layerOptions as VectorLayerOptions); break; case WMSDataSource: layer = this.createImageLayer(layerOptions as ImageLayerOptions); break; case MVTDataSource: - layer = this.createVectorTileLayer(layerOptions as VectorTileLayerOptions); + layer = this.createVectorTileLayer( + layerOptions as VectorTileLayerOptions + ); break; default: break; @@ -131,11 +136,24 @@ export class LayerService { if (layerOptions.source instanceof ArcGISRestDataSource) { const source = layerOptions.source as ArcGISRestDataSource; style = source.options.params.style; - } else if (layerOptions.styleByAttribute) { const serviceStyle = this.styleService; - layerOptions.style = (feature) => { - return serviceStyle.createStyleByAttribute(feature, layerOptions.styleByAttribute); + layerOptions.style = feature => { + return serviceStyle.createStyleByAttribute( + feature, + layerOptions.styleByAttribute + ); + }; + return new VectorLayer(layerOptions); + } + + if (layerOptions.source instanceof ClusterDataSource) { + const serviceStyle = this.styleService; + layerOptions.style = feature => { + return serviceStyle.createClusterStyle( + feature, + layerOptions.clusterParam + ); }; return new VectorLayer(layerOptions); } @@ -147,7 +165,9 @@ export class LayerService { return new VectorLayer(layerOptionsOl); } - private createVectorTileLayer(layerOptions: VectorTileLayerOptions): VectorTileLayer { + private createVectorTileLayer( + layerOptions: VectorTileLayerOptions + ): VectorTileLayer { let style; if (layerOptions.style !== undefined) { style = this.styleService.createStyle(layerOptions.style); @@ -155,8 +175,11 @@ export class LayerService { if (layerOptions.styleByAttribute) { const serviceStyle = this.styleService; - layerOptions.style = (feature) => { - return serviceStyle.createStyleByAttribute(feature, layerOptions.styleByAttribute); + layerOptions.style = feature => { + return serviceStyle.createStyleByAttribute( + feature, + layerOptions.styleByAttribute + ); }; return new VectorTileLayer(layerOptions); } diff --git a/packages/geo/src/lib/layer/shared/layers/vector-layer.interface.ts b/packages/geo/src/lib/layer/shared/layers/vector-layer.interface.ts index f4c24ee579..eaf2dd4cab 100644 --- a/packages/geo/src/lib/layer/shared/layers/vector-layer.interface.ts +++ b/packages/geo/src/lib/layer/shared/layers/vector-layer.interface.ts @@ -8,11 +8,15 @@ import { FeatureDataSource } from '../../../datasource/shared/datasources/featur import { WFSDataSource } from '../../../datasource/shared/datasources/wfs-datasource'; import { ArcGISRestDataSource } from '../../../datasource/shared/datasources/arcgisrest-datasource'; import { WebSocketDataSource } from '../../../datasource/shared/datasources/websocket-datasource'; +import { ClusterDataSource } from '../../../datasource/shared/datasources/cluster-datasource'; import { FeatureDataSourceOptions } from '../../../datasource/shared/datasources/feature-datasource.interface'; import { WFSDataSourceOptions } from '../../../datasource/shared/datasources/wfs-datasource.interface'; import { ArcGISRestDataSourceOptions } from '../../../datasource/shared/datasources/arcgisrest-datasource.interface'; import { WebSocketDataSourceOptions } from '../../../datasource/shared/datasources/websocket-datasource.interface'; +import { ClusterDataSourceOptions } from '../../../datasource/shared/datasources/cluster-datasource.interface'; + +import { ClusterParam } from '../clusterParam'; import { StyleByAttribute } from '../stylebyattribute'; @@ -21,18 +25,21 @@ export interface VectorLayerOptions extends LayerOptions { | FeatureDataSource | WFSDataSource | ArcGISRestDataSource - | WebSocketDataSource; + | WebSocketDataSource + | ClusterDataSource; sourceOptions?: | FeatureDataSourceOptions | WFSDataSourceOptions | ArcGISRestDataSourceOptions - | WebSocketDataSourceOptions; + | WebSocketDataSourceOptions + | ClusterDataSourceOptions; style?: { [key: string]: any } | olStyle | olStyle[]; browsable?: boolean; exportable?: boolean; ol?: olLayerVector; animation?: VectorAnimation; styleByAttribute?: StyleByAttribute; + clusterParam?: ClusterParam; } export interface VectorAnimation { diff --git a/packages/geo/src/lib/layer/shared/layers/vector-layer.ts b/packages/geo/src/lib/layer/shared/layers/vector-layer.ts index dbeecf2cb1..597c69aac7 100644 --- a/packages/geo/src/lib/layer/shared/layers/vector-layer.ts +++ b/packages/geo/src/lib/layer/shared/layers/vector-layer.ts @@ -8,16 +8,13 @@ import { FeatureDataSource } from '../../../datasource/shared/datasources/featur import { WFSDataSource } from '../../../datasource/shared/datasources/wfs-datasource'; import { ArcGISRestDataSource } from '../../../datasource/shared/datasources/arcgisrest-datasource'; import { WebSocketDataSource } from '../../../datasource/shared/datasources/websocket-datasource'; +import { ClusterDataSource } from '../../../datasource/shared/datasources/cluster-datasource'; import { Layer } from './layer'; import { VectorLayerOptions } from './vector-layer.interface'; export class VectorLayer extends Layer { - public dataSource: - | FeatureDataSource - | WFSDataSource - | ArcGISRestDataSource - | WebSocketDataSource; + public dataSource: FeatureDataSource | WFSDataSource | ArcGISRestDataSource | WebSocketDataSource | ClusterDataSource; public options: VectorLayerOptions; public ol: olLayerVector; diff --git a/packages/geo/src/lib/layer/shared/layers/vectortile-layer.ts b/packages/geo/src/lib/layer/shared/layers/vectortile-layer.ts index 2bf337bb4f..d950cca00e 100644 --- a/packages/geo/src/lib/layer/shared/layers/vectortile-layer.ts +++ b/packages/geo/src/lib/layer/shared/layers/vectortile-layer.ts @@ -7,8 +7,7 @@ import { Layer } from './layer'; import { VectorTileLayerOptions } from './vectortile-layer.interface'; export class VectorTileLayer extends Layer { - public dataSource: - | MVTDataSource; + public dataSource: MVTDataSource; public options: VectorTileLayerOptions; public ol: olLayerVectorTile; @@ -19,11 +18,8 @@ export class VectorTileLayer extends Layer { protected createOlLayer(): olLayerVectorTile { const olOptions = Object.assign({}, this.options, { source: this.options.source.ol as olSourceVectorTile - }); return new olLayerVectorTile(olOptions); } } - - diff --git a/packages/geo/src/lib/layer/shared/style.service.ts b/packages/geo/src/lib/layer/shared/style.service.ts index ff1e1fbe8b..8ffb5b2ae0 100644 --- a/packages/geo/src/lib/layer/shared/style.service.ts +++ b/packages/geo/src/lib/layer/shared/style.service.ts @@ -3,6 +3,8 @@ import { Injectable } from '@angular/core'; import * as olstyle from 'ol/style'; import { StyleByAttribute } from './stylebyattribute'; +import { ClusterParam } from './clusterParam'; + @Injectable({ providedIn: 'root' }) @@ -51,7 +53,6 @@ export class StyleService { return olCls; } - createStyleByAttribute(feature, styleByAttribute: StyleByAttribute) { let style; const type = styleByAttribute.type; @@ -70,46 +71,53 @@ export class StyleService { for (let i = 0; i < size; i++) { if (feature.get(attribute) === data[i]) { if (icon) { - style = [new olstyle.Style({ - image: new olstyle.Icon({ - src: icon[i], - scale: scale ? scale[i] : 1 - }) - })]; - return style; + style = [ + new olstyle.Style({ + image: new olstyle.Icon({ + src: icon[i], + scale: scale ? scale[i] : 1 + }) + }) + ]; + return style; } - style = [new olstyle.Style({ + style = [ + new olstyle.Style({ + image: new olstyle.Circle({ + radius: radius ? radius[i] : 4, + stroke: new olstyle.Stroke({ + color: stroke ? stroke[i] : 'black' + }), + fill: new olstyle.Fill({ + color: fill ? fill[i] : 'black' + }) + }) + }) + ]; + return style; + } + } + if (!feature.getStyle()) { + style = [ + new olstyle.Style({ image: new olstyle.Circle({ - radius: radius ? radius[i] : 4, + radius: 4, stroke: new olstyle.Stroke({ - color: stroke ? stroke[i] : 'black' + color: 'black' }), fill: new olstyle.Fill({ - color: fill ? fill[i] : 'black' + color: '#bbbbf2' }) }) - })]; - return style; - } + }) + ]; + return style; } - if (!feature.getStyle()) { - style = [new olstyle.Style({ - image: new olstyle.Circle({ - radius: 4, - stroke: new olstyle.Stroke({ - color: 'black' - }), - fill: new olstyle.Fill({ - color: '#bbbbf2' - }) - }) - })]; - return style; - } - } else if (type === 'regular') { - for (let i = 0; i < size; i++) { - if (feature.get(attribute) === data[i]) { - style = [new olstyle.Style({ + } else if (type === 'regular') { + for (let i = 0; i < size; i++) { + if (feature.get(attribute) === data[i]) { + style = [ + new olstyle.Style({ stroke: new olstyle.Stroke({ color: stroke ? stroke[i] : 'black', width: width ? width[i] : 1 @@ -123,25 +131,93 @@ export class StyleService { color: 'black' }) }) - })]; - return style; - } - } - if (!feature.getStyle()) { - if (baseStyle) { - style = this.createStyle(baseStyle); - return style; - } - style = [new olstyle.Style({ + }) + ]; + return style; + } + } + if (!feature.getStyle()) { + if (baseStyle) { + style = this.createStyle(baseStyle); + return style; + } + style = [ + new olstyle.Style({ stroke: new olstyle.Stroke({ color: 'black' }), fill: new olstyle.Fill({ color: '#bbbbf2' }) - })]; - return style; + }) + ]; + return style; + } + } + } + + createClusterStyle(feature, clusterParam: ClusterParam) { + let style; + const range = clusterParam.clusterRange; + const icon = clusterParam.clusterIcon; + const scale = clusterParam.clusterScale; + const size = feature.get('features').length; + let color; + if (size !== 1) { + if (range) { + if (size >= range[1]) { + color = 'red'; + } else if (size < range[1] && size >= range[0]) { + color = 'orange'; + } else if (size < range[0]) { + color = 'green'; } - } - } + } + style = [ + new olstyle.Style({ + image: new olstyle.Circle({ + radius: 2 * size + 3.4, + stroke: new olstyle.Stroke({ + color: 'black' + }), + fill: new olstyle.Fill({ + color: range ? color : 'blue' + }) + }), + text: new olstyle.Text({ + text: size.toString(), + fill: new olstyle.Fill({ + color: '#fff' + }) + }) + }) + ]; + } else { + if (icon) { + style = [ + new olstyle.Style({ + image: new olstyle.Icon({ + src: icon, + scale + }) + }) + ]; + } else { + style = [ + new olstyle.Style({ + image: new olstyle.Circle({ + radius: 2 * size + 3.4, + stroke: new olstyle.Stroke({ + color: 'black' + }), + fill: new olstyle.Fill({ + color: 'blue' + }) + }) + }) + ]; + } + } + return style; + } } From e0e0a0ac032817b6ca253ebdf8080e281f45a52d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Barbeau?= Date: Thu, 25 Jul 2019 13:50:23 -0400 Subject: [PATCH 21/38] feat(icherche): icherche v2 / territoire --- .../app/context/context/context.component.ts | 8 +- demo/src/environments/environment.ts | 37 +-- .../search-results-item.component.scss | 3 + .../search-results-item.component.ts | 20 +- .../shared/sources/icherche.interfaces.ts | 14 +- .../src/lib/search/shared/sources/icherche.ts | 252 ++++++++++-------- proxy.conf.json | 4 +- 7 files changed, 192 insertions(+), 146 deletions(-) create mode 100644 packages/geo/src/lib/search/search-results/search-results-item.component.scss diff --git a/demo/src/app/context/context/context.component.ts b/demo/src/app/context/context/context.component.ts index 39ef759357..2657613594 100644 --- a/demo/src/app/context/context/context.component.ts +++ b/demo/src/app/context/context/context.component.ts @@ -1,7 +1,8 @@ import { Component } from '@angular/core'; import { LanguageService } from '@igo2/core'; -import { IgoMap, OverlayService } from '@igo2/geo'; +import { IgoMap, OverlayService, MapService } from '@igo2/geo'; + @Component({ selector: 'app-context', templateUrl: './context.component.html', @@ -23,6 +24,9 @@ export class AppContextComponent { constructor( private languageService: LanguageService, + private mapService: MapService, private overlayService: OverlayService - ) {} + ) { + this.mapService.setMap(this.map); + } } diff --git a/demo/src/environments/environment.ts b/demo/src/environments/environment.ts index ebccd9553d..519535e4dc 100644 --- a/demo/src/environments/environment.ts +++ b/demo/src/environments/environment.ts @@ -15,7 +15,8 @@ export const environment: Environment = { projections: [ { code: 'EPSG:32198', - def: '+proj=lcc +lat_1=60 +lat_2=46 +lat_0=44 +lon_0=-68.5 +x_0=0 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs', + def: + '+proj=lcc +lat_1=60 +lat_2=46 +lat_0=44 +lon_0=-68.5 +x_0=0 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs', extent: [-886251.0296, 180252.9126, 897177.3418, 2106143.8139] } ], @@ -40,7 +41,10 @@ export const environment: Environment = { url: 'https://ws.mapserver.transports.gouv.qc.ca/swtq', queryFormat: { html: '*', - 'application/json': ['stations_meteoroutieres', 'histo_stations_meteoroutieres'] + 'application/json': [ + 'stations_meteoroutieres', + 'histo_stations_meteoroutieres' + ] }, queryHtmlTarget: 'iframe', count: 30 @@ -50,7 +54,6 @@ export const environment: Environment = { title: 'Filtered catalog by regex', url: 'https://ws.mapserver.transports.gouv.qc.ca/swtq', regFilters: ['zpegt'] - }, { id: 'catalogwithtooltipcontrol', @@ -62,7 +65,8 @@ export const environment: Environment = { }, searchSources: { nominatim: { - enabled: false + enabled: false, + disabled: true }, reseautq: { searchUrl: 'https://ws.mapserver.transports.gouv.qc.ca/swtq', @@ -71,26 +75,25 @@ export const environment: Environment = { locateLimit: 15, zoomMaxOnSelect: 8, enabled: false, - propertiesAlias: - [ - {name: 'title', alias: 'Titre'}, - {name: 'etiquette', alias: 'Informations'}, - {name: 'nommun', alias: 'Municipalité'}, - {name: 'messagpan', alias: 'Message'}, - {name: 'noroute', alias: '# de route'}, - {name: 'nosortie', alias: '# de sortie'}, - {name: 'direction', alias: 'Direction'}, - {name: 'typesort', alias: 'Type de sortie'} + propertiesAlias: [ + { name: 'title', alias: 'Titre' }, + { name: 'etiquette', alias: 'Informations' }, + { name: 'nommun', alias: 'Municipalité' }, + { name: 'messagpan', alias: 'Message' }, + { name: 'noroute', alias: '# de route' }, + { name: 'nosortie', alias: '# de sortie' }, + { name: 'direction', alias: 'Direction' }, + { name: 'typesort', alias: 'Type de sortie' } ], - distance : 0.5 + distance: 0.5 }, icherche: { - searchUrl: '/icherche/geocode', + searchUrl: '/apis/icherche/geocode', zoomMaxOnSelect: 10, enabled: true }, icherchereverse: { - searchUrl: '/icherche/xy', + searchUrl: '/apis/territoires/locate', enabled: true }, datasource: { diff --git a/packages/geo/src/lib/search/search-results/search-results-item.component.scss b/packages/geo/src/lib/search/search-results/search-results-item.component.scss new file mode 100644 index 0000000000..34d59dc0ac --- /dev/null +++ b/packages/geo/src/lib/search/search-results/search-results-item.component.scss @@ -0,0 +1,3 @@ +h4 :ng-deep small { + color: "#8C8C8C"; +} diff --git a/packages/geo/src/lib/search/search-results/search-results-item.component.ts b/packages/geo/src/lib/search/search-results/search-results-item.component.ts index e4fe382c6e..4258f62b08 100644 --- a/packages/geo/src/lib/search/search-results/search-results-item.component.ts +++ b/packages/geo/src/lib/search/search-results/search-results-item.component.ts @@ -1,6 +1,10 @@ import { Component, Input, ChangeDetectionStrategy } from '@angular/core'; -import { getEntityTitle, getEntityTitleHtml, getEntityIcon } from '@igo2/common'; +import { + getEntityTitle, + getEntityTitleHtml, + getEntityIcon +} from '@igo2/common'; import { SearchResult } from '../shared/search.interfaces'; @@ -10,10 +14,10 @@ import { SearchResult } from '../shared/search.interfaces'; @Component({ selector: 'igo-search-results-item', templateUrl: './search-results-item.component.html', + styleUrls: ['./search-results-item.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) export class SearchResultsItemComponent { - /** * Search result */ @@ -23,19 +27,25 @@ export class SearchResultsItemComponent { * Search result title * @internal */ - get title(): string { return getEntityTitle(this.result); } + get title(): string { + return getEntityTitle(this.result); + } /** * Search result HTML title * @internal */ - get titleHtml(): string { return getEntityTitleHtml(this.result); } + get titleHtml(): string { + return getEntityTitleHtml(this.result); + } /** * Search result icon * @internal */ - get icon(): string { return getEntityIcon(this.result); } + get icon(): string { + return getEntityIcon(this.result); + } constructor() {} } diff --git a/packages/geo/src/lib/search/shared/sources/icherche.interfaces.ts b/packages/geo/src/lib/search/shared/sources/icherche.interfaces.ts index 3997547629..b7adb321b0 100644 --- a/packages/geo/src/lib/search/shared/sources/icherche.interfaces.ts +++ b/packages/geo/src/lib/search/shared/sources/icherche.interfaces.ts @@ -1,13 +1,17 @@ import { FeatureGeometry } from '../../../feature'; export interface IChercheData { - _id: string; - doc_type: string; - recherche: string; - highlight: string; + index: string; geometry: FeatureGeometry; bbox: [number, number, number, number]; properties: { [key: string]: any }; + highlight: { + title: string; + title1?: string; + title2?: string; + title3?: string; + title4?: string; + }; } export interface IChercheResponse { @@ -25,5 +29,5 @@ export interface IChercheReverseData { } export interface IChercheReverseResponse { - features: IChercheData[]; + features: IChercheReverseData[]; } diff --git a/packages/geo/src/lib/search/shared/sources/icherche.ts b/packages/geo/src/lib/search/shared/sources/icherche.ts index fee377938c..bab25df038 100644 --- a/packages/geo/src/lib/search/shared/sources/icherche.ts +++ b/packages/geo/src/lib/search/shared/sources/icherche.ts @@ -40,16 +40,7 @@ export class IChercheSearchResultFormatter { export class IChercheSearchSource extends SearchSource implements TextSearch { static id = 'icherche'; static type = FEATURE; - static propertiesBlacklist: string[] = [ - '@timestamp', - '@version', - 'recherche', - 'id', - 'idrte', - 'cote', - 'geometry', - 'bbox' - ]; + static propertiesBlacklist: string[] = []; constructor( private http: HttpClient, @@ -67,54 +58,69 @@ export class IChercheSearchSource extends SearchSource implements TextSearch { protected getDefaultOptions(): SearchSourceOptions { return { title: 'ICherche Québec', - searchUrl: 'https://geoegl.msp.gouv.qc.ca/icherche/geocode', + searchUrl: 'https://geoegl.msp.gouv.qc.ca/apis/icherche/geocode', settings: [ { - type: 'checkbox', - title: 'results type', - name: 'type', - values: [ - { - title: 'Adresse', - value: 'adresse', - enabled: true - }, - { - title: 'Ancienne adresse', - value: 'ancienne_adresse', - enabled: true - }, - { - title: 'Code Postal', - value: 'code_postal', - enabled: true - }, - { - title: 'Route', - value: 'route', - enabled: true - }, - { - title: 'Municipalité', - value: 'municipalite', - enabled: true - }, - { - title: 'Ancienne municipalité', - value: 'ancienne_municipalite', - enabled: true - }, - { - title: 'mrc', - value: 'mrc', - enabled: true - }, - { - title: 'Région administrative', - value: 'region_administrative', - enabled: true - } - ] + type: 'checkbox', + title: 'results type', + name: 'type', + values: [ + { + title: 'Adresse', + value: 'adresses', + enabled: true + }, + // { + // title: 'Ancienne adresse', + // value: 'ancienne_adresse', + // enabled: true + // }, + { + title: 'Code Postal', + value: 'codes-postaux', + enabled: true + }, + { + title: 'Route', + value: 'routes', + enabled: false + }, + { + title: 'Municipalité', + value: 'municipalites', + enabled: true + }, + // { + // title: 'Ancienne municipalité', + // value: 'ancienne_municipalite', + // enabled: true + // }, + { + title: 'mrc', + value: 'mrc', + enabled: true + }, + { + title: 'Région administrative', + value: 'regadmin', + enabled: true + }, + { + title: 'Lieu', + value: 'lieux', + enabled: true + }, + { + title: 'Borne', + value: 'bornes', + enabled: false + }, + { + title: 'Entreprise', + value: 'entreprises', + enabled: false + } + ] }, { type: 'radiobutton', @@ -154,13 +160,13 @@ export class IChercheSearchSource extends SearchSource implements TextSearch { name: 'ecmax', values: [ { - title: '1', - value: 1, + title: '10', + value: 10, enabled: false }, { - title: '15', - value: 15, + title: '30', + value: 30, enabled: true }, { @@ -199,15 +205,20 @@ export class IChercheSearchSource extends SearchSource implements TextSearch { .pipe(map((response: IChercheResponse) => this.extractResults(response))); } - private computeRequestParams(term: string, options: TextSearchOptions): HttpParams { + private computeRequestParams( + term: string, + options: TextSearchOptions + ): HttpParams { return new HttpParams({ fromObject: Object.assign( { q: this.computeTerm(term), - geometries: 'geom' + geometry: true, + type: + 'adresses,codes-postaux,municipalites,mrc,regadmin,lieux,entreprises,bornes' }, this.params, - this.computeOptionsParam(term, options || {}).params + this.computeOptionsParam(term, options || {}).params ) }); } @@ -220,7 +231,12 @@ export class IChercheSearchSource extends SearchSource implements TextSearch { private dataToResult(data: IChercheData): SearchResult { const properties = this.computeProperties(data); - const id = [this.getId(), properties.type, data._id].join('.'); + const id = [this.getId(), properties.type, properties.code].join('.'); + + const subtitleHtml = data.highlight.title2 + ? ' ' + data.highlight.title2 + '' + : ''; + return { source: this, data: { @@ -231,14 +247,14 @@ export class IChercheSearchSource extends SearchSource implements TextSearch { properties, meta: { id, - title: data.properties.recherche + title: data.properties.nom } }, meta: { dataType: FEATURE, id, - title: data.properties.recherche, - titleHtml: data.highlight, + title: data.properties.nom, + titleHtml: data.highlight.title + subtitleHtml, icon: 'map-marker' } }; @@ -249,7 +265,7 @@ export class IChercheSearchSource extends SearchSource implements TextSearch { data.properties, IChercheSearchSource.propertiesBlacklist ); - return Object.assign(properties, { type: data.doc_type }); + return Object.assign(properties, { type: data.index }); } /** @@ -265,20 +281,24 @@ export class IChercheSearchSource extends SearchSource implements TextSearch { * @param term Query with hashtag * @param options TextSearchOptions */ - private computeOptionsParam(term: string, options: TextSearchOptions): TextSearchOptions { + private computeOptionsParam( + term: string, + options: TextSearchOptions + ): TextSearchOptions { const tags = term.match(/(#[^\s]+)/g); - if ( tags ) { + if (tags) { let typeValue = ''; let hashtagToAdd = false; - tags.forEach( value => { + tags.forEach(value => { if (super.hashtagValid(super.getSettingsValues('type'), value, true)) { typeValue += value.substring(1) + ','; hashtagToAdd = true; } }); if (hashtagToAdd) { - options.params = Object.assign( (options.params || {}), - { type : typeValue.slice(0, -1) } ); + options.params = Object.assign(options.params || {}, { + type: typeValue.slice(0, -1) + }); } } return options; @@ -308,46 +328,46 @@ export class IChercheReverseSearchSource extends SearchSource protected getDefaultOptions(): SearchSourceOptions { return { - title: 'ICherche Québec (Géocodage inversé)', - searchUrl: 'https://geoegl.msp.gouv.qc.ca/icherche/xy', + title: 'Territoire (Géocodage inversé)', + searchUrl: 'https://geoegl.msp.gouv.qc.ca/apis/territoires/locate', settings: [ { - type: 'checkbox', - title: 'results type', - name: 'type', - values: [ - { - title: 'Adresse', - value: 'adresse', - enabled: true - }, - { - title: 'Route', - value: 'route', - enabled: false - }, - { - title: 'Arrondissement', - value: 'arrondissement', - enabled: false - }, - { - title: 'Municipalité', - value: 'municipalite', - enabled: true - }, - { - title: 'mrc', - value: 'mrc', - enabled: true - }, - { - title: 'Région administrative', - value: 'regadmin', - enabled: true - } - ] + type: 'checkbox', + title: 'results type', + name: 'type', + values: [ + { + title: 'Adresse', + value: 'adresses', + enabled: true + }, + { + title: 'Route', + value: 'routes', + enabled: false + }, + { + title: 'Arrondissement', + value: 'arrondissements', + enabled: false + }, + { + title: 'Municipalité', + value: 'municipalites', + enabled: true + }, + { + title: 'mrc', + value: 'mrc', + enabled: true + }, + { + title: 'Région administrative', + value: 'regadmin', + enabled: true + } + ] } ] }; @@ -380,8 +400,8 @@ export class IChercheReverseSearchSource extends SearchSource fromObject: Object.assign( { loc: lonLat.join(','), - distance: distance ? String(distance) : '100', - geometries: 'geom' + buffer: distance ? String(distance) : '100', + geometry: true }, this.params, options.params || {} @@ -400,7 +420,7 @@ export class IChercheReverseSearchSource extends SearchSource private dataToResult(data: IChercheReverseData): SearchResult { const properties = this.computeProperties(data); const extent = this.computeExtent(data); - const id = [this.getId(), properties.type, data._id].join('.'); + const id = [this.getId(), properties.type, properties.code].join('.'); return { source: this, @@ -429,10 +449,12 @@ export class IChercheReverseSearchSource extends SearchSource data.properties, IChercheReverseSearchSource.propertiesBlacklist ); - return Object.assign(properties, { type: data.properties.doc_type }); + return properties; } - private computeExtent(data: IChercheReverseData): [number, number, number, number] | undefined { + private computeExtent( + data: IChercheReverseData + ): [number, number, number, number] | undefined { return data.bbox ? [data.bbox[0], data.bbox[2], data.bbox[1], data.bbox[3]] : undefined; diff --git a/proxy.conf.json b/proxy.conf.json index a017b52e97..b1046191c4 100644 --- a/proxy.conf.json +++ b/proxy.conf.json @@ -14,8 +14,8 @@ "secure": false, "changeOrigin": true }, - "/igo2/api/": { - "target": "https://geoegl.msp.gouv.qc.ca", + "/apis/": { + "target": "https://testgeoegl.msp.gouv.qc.ca", "secure": false, "changeOrigin": true }, From 0eb757dc13c074e9a1fc85094457e6b45a23b676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Barbeau?= Date: Thu, 25 Jul 2019 14:03:45 -0400 Subject: [PATCH 22/38] fix(icherche: ajust interface --- .../src/lib/search/shared/sources/icherche.interfaces.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/geo/src/lib/search/shared/sources/icherche.interfaces.ts b/packages/geo/src/lib/search/shared/sources/icherche.interfaces.ts index b7adb321b0..279170f4f7 100644 --- a/packages/geo/src/lib/search/shared/sources/icherche.interfaces.ts +++ b/packages/geo/src/lib/search/shared/sources/icherche.interfaces.ts @@ -7,10 +7,10 @@ export interface IChercheData { properties: { [key: string]: any }; highlight: { title: string; - title1?: string; title2?: string; title3?: string; title4?: string; + title5?: string; }; } @@ -19,10 +19,6 @@ export interface IChercheResponse { } export interface IChercheReverseData { - _id: string; - doc_type: string; - recherche: string; - highlight: string; geometry: FeatureGeometry; bbox: [number, number, number, number]; properties: { [key: string]: any }; From 7054b025a19a007cd21a99edbddbf4b036e12533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Barbeau?= Date: Thu, 25 Jul 2019 14:51:18 -0400 Subject: [PATCH 23/38] fix(context): fix create layerOptions when create context --- .../src/lib/context-manager/shared/context.service.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/context/src/lib/context-manager/shared/context.service.ts b/packages/context/src/lib/context-manager/shared/context.service.ts index 37b9e938d9..759a66932c 100644 --- a/packages/context/src/lib/context-manager/shared/context.service.ts +++ b/packages/context/src/lib/context-manager/shared/context.service.ts @@ -358,9 +358,11 @@ export class ContextService { const layer: any = l; const opts = { id: layer.options.id ? String(layer.options.id) : undefined, - title: layer.options.title, - zIndex: layer.zIndex, - visible: layer.visible, + layerOptions: { + title: layer.options.title, + zIndex: layer.zIndex, + visible: layer.visible + }, sourceOptions: { type: layer.dataSource.options.type, params: layer.dataSource.options.params, From 87cf1cdb2dcda8b9f2846590705bd98d6c97d5d0 Mon Sep 17 00:00:00 2001 From: ameliebernier <45005853+ameliebernier@users.noreply.github.com> Date: Thu, 25 Jul 2019 15:55:00 -0400 Subject: [PATCH 24/38] feat(geometry-form-field): allow to set symbol (#373) * feat{geometry-form-field}: allow to set symbol * feat(geometry-form-field): better symbol control --- .../app/geo/geometry/geometry.component.ts | 21 ++++- .../geometry-form-field-input.component.ts | 83 ++++++++++++++----- .../geometry-form-field.component.html | 4 +- .../geometry-form-field.component.ts | 15 +++- .../src/lib/geometry/shared/geometry.utils.ts | 14 ++-- 5 files changed, 106 insertions(+), 31 deletions(-) diff --git a/demo/src/app/geo/geometry/geometry.component.ts b/demo/src/app/geo/geometry/geometry.component.ts index de27aa5da8..0e68ebe2d2 100644 --- a/demo/src/app/geo/geometry/geometry.component.ts +++ b/demo/src/app/geo/geometry/geometry.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit, OnDestroy } from '@angular/core'; import { Validators } from '@angular/forms'; import { BehaviorSubject, Subscription } from 'rxjs'; +import * as olstyle from 'ol/style'; import { LanguageService } from '@igo2/core'; import { Form, FormService } from '@igo2/common'; @@ -70,7 +71,25 @@ export class AppGeometryComponent implements OnInit, OnDestroy { geometryType: 'Polygon', drawGuideField: true, drawGuide: 50, - drawGuidePlaceholder: 'Draw Guide' + drawGuidePlaceholder: 'Draw Guide', + drawStyle: new olstyle.Style({ + stroke: new olstyle.Stroke({ + color: [255, 0, 0, 1], + width: 2 + }), + fill: new olstyle.Fill({ + color: [255, 0, 0, 0.2] + }), + image: new olstyle.Circle({ + radius: 8, + stroke: new olstyle.Stroke({ + color: [255, 0, 0, 1] + }), + fill: new olstyle.Fill({ + color: [255, 0, 0, 0.2] + }) + }) + }) } }, { diff --git a/packages/geo/src/lib/geometry/geometry-form-field/geometry-form-field-input.component.ts b/packages/geo/src/lib/geometry/geometry-form-field/geometry-form-field-input.component.ts index aece77d017..ee9734e52e 100644 --- a/packages/geo/src/lib/geometry/geometry-form-field/geometry-form-field-input.component.ts +++ b/packages/geo/src/lib/geometry/geometry-form-field/geometry-form-field-input.component.ts @@ -12,7 +12,7 @@ import { NgControl, ControlValueAccessor } from '@angular/forms'; import { Subscription } from 'rxjs'; -import { Style as OlStyle } from 'ol/style'; +import * as OlStyle from 'ol/style'; import OlGeoJSON from 'ol/format/GeoJSON'; import OlGeometry from 'ol/geom/Geometry'; import OlGeometryType from 'ol/geom/GeometryType'; @@ -24,7 +24,6 @@ import OlOverlay from 'ol/Overlay'; import { IgoMap } from '../../map'; import { MeasureLengthUnit, - clearOlGeometryMidpoints, updateOlGeometryMidpoints, formatMeasure, measureOlGeometry @@ -52,11 +51,9 @@ export class GeometryFormFieldInputComponent implements OnInit, OnDestroy, Contr private drawControl: DrawControl; private modifyControl: ModifyControl; - private drawInteractionStyle: OlStyle; private defaultDrawStyleRadius: number; private olGeometryEnds$$: Subscription; private olGeometryChanges$$: Subscription; - private olTooltip = OlOverlay; /** @@ -89,13 +86,41 @@ export class GeometryFormFieldInputComponent implements OnInit, OnDestroy, Contr /** * The drawGuide around the mouse pointer to help drawing */ - @Input() drawGuide = 0; + @Input() drawGuide: number = null; /** * Whether a measure tooltip should be displayed */ @Input() measure: boolean = false; + /** + * Style for the draw control (applies while the geometry is being drawn) + */ + @Input() + set drawStyle(value: OlStyle) { + this._drawStyle = value || createDrawInteractionStyle(); + if (this.isStyleWithRadius(this.drawStyle)) { + this.defaultDrawStyleRadius = this.drawStyle.getImage().getRadius(); + } else { + this.defaultDrawStyleRadius = null; + } + } + get drawStyle(): OlStyle { return this._drawStyle; } + private _drawStyle: OlStyle; + + /** + * Style for the overlay layer (applies once the geometry is added to the map) + * If not specified, drawStyle applies + */ + @Input() + set overlayStyle(value: OlStyle) { + this._overlayStyle = value; + } + get overlayStyle(): OlStyle { + return this._overlayStyle || this.drawStyle; + } + private _overlayStyle: OlStyle; + /** * The geometry value (GeoJSON) * Implemented as part of ControlValueAccessor. @@ -148,8 +173,6 @@ export class GeometryFormFieldInputComponent implements OnInit, OnDestroy, Contr ngOnInit() { this.addOlOverlayLayer(); this.createMeasureTooltip(); - this.drawInteractionStyle = createDrawInteractionStyle(); - this.defaultDrawStyleRadius = this.drawInteractionStyle.getImage().getRadius(); this.createDrawControl(); this.createModifyControl(); if (this.value) { @@ -200,7 +223,8 @@ export class GeometryFormFieldInputComponent implements OnInit, OnDestroy, Contr private addOlOverlayLayer(): OlVectorLayer { this.olOverlayLayer = new OlVectorLayer({ source: new OlVectorSource(), - zIndex: 500 + zIndex: 500, + style: null }); this.map.ol.addLayer(this.olOverlayLayer); } @@ -210,10 +234,10 @@ export class GeometryFormFieldInputComponent implements OnInit, OnDestroy, Contr */ private createDrawControl() { this.drawControl = new DrawControl({ - geometryType: this.geometryType, + geometryType: this.geometryType || 'Point', layer: this.olOverlayLayer, drawStyle: (olFeature: OlFeature, resolution: number) => { - const style = this.drawInteractionStyle; + const style = this.drawStyle; this.updateDrawStyleWithDrawGuide(style, resolution); return style; } @@ -227,7 +251,7 @@ export class GeometryFormFieldInputComponent implements OnInit, OnDestroy, Contr this.modifyControl = new ModifyControl({ layer: this.olOverlayLayer, drawStyle: (olFeature: OlFeature, resolution: number) => { - const style = this.drawInteractionStyle; + const style = this.drawStyle; this.updateDrawStyleWithDrawGuide(style, resolution); return style; } @@ -239,7 +263,7 @@ export class GeometryFormFieldInputComponent implements OnInit, OnDestroy, Contr */ private toggleControl() { this.deactivateControl(); - if (!this.value) { + if (!this.value && this.geometryType) { this.activateControl(this.drawControl); } else { this.activateControl(this.modifyControl); @@ -322,7 +346,10 @@ export class GeometryFormFieldInputComponent implements OnInit, OnDestroy, Contr dataProjection: 'EPSG:4326', featureProjection: this.map.projection }); - const olFeature = new OlFeature({geometry: olGeometry}); + const olFeature = new OlFeature({ + geometry: olGeometry + }); + olFeature.setStyle(this.overlayStyle); this.olOverlaySource.clear(); this.olOverlaySource.addFeature(olFeature); } @@ -377,21 +404,35 @@ export class GeometryFormFieldInputComponent implements OnInit, OnDestroy, Contr * Remove the measure tooltip from the map */ private removeMeasureTooltip() { - if (this.olTooltip.getMap() !== undefined) { + if (this.olTooltip.getMap && this.olTooltip.getMap() !== undefined) { this.map.ol.removeOverlay(this.olTooltip); this.olTooltip.setMap(undefined); } } + /** + * Adjust the draw style with the specified draw guide distance, if possible + * @param olStyle Draw style to update + * @param resolution Resolution (to make the screen size of symbol fit the drawGuide value) + */ private updateDrawStyleWithDrawGuide(olStyle: OlStyle, resolution: number) { - const drawGuide = this.drawGuide; - let radius; - if (drawGuide === undefined || drawGuide < 0) { - radius = this.defaultDrawStyleRadius; - } else { - radius = drawGuide > 0 ? drawGuide / resolution : drawGuide; + if (this.isStyleWithRadius(olStyle)) { + const drawGuide = this.drawGuide; + let radius; + if (drawGuide === null || drawGuide < 0) { + radius = this.defaultDrawStyleRadius; + } else { + radius = drawGuide > 0 ? drawGuide / resolution : drawGuide; + } + olStyle.getImage().setRadius(radius); } - olStyle.getImage().setRadius(radius); } + /** + * Returns wether a given Open Layers style has a radius property that can be set (used to set draw guide) + * @param olStyle The style on which to perform the check + */ + private isStyleWithRadius(olStyle: OlStyle): boolean { + return olStyle.getImage && olStyle.getImage().setRadius; + } } diff --git a/packages/geo/src/lib/geometry/geometry-form-field/geometry-form-field.component.html b/packages/geo/src/lib/geometry/geometry-form-field/geometry-form-field.component.html index 5a860c3a6a..cea7af14e5 100644 --- a/packages/geo/src/lib/geometry/geometry-form-field/geometry-form-field.component.html +++ b/packages/geo/src/lib/geometry/geometry-form-field/geometry-form-field.component.html @@ -3,7 +3,9 @@ [map]="map" [geometryType]="geometryType$ | async" [drawGuide]="drawGuide$ | async" - [measure]="measure"> + [measure]="measure" + [drawStyle]="drawStyle" + [overlayStyle]="overlayStyle">
diff --git a/packages/geo/src/lib/geometry/geometry-form-field/geometry-form-field.component.ts b/packages/geo/src/lib/geometry/geometry-form-field/geometry-form-field.component.ts index 731041ca27..34925291cf 100644 --- a/packages/geo/src/lib/geometry/geometry-form-field/geometry-form-field.component.ts +++ b/packages/geo/src/lib/geometry/geometry-form-field/geometry-form-field.component.ts @@ -11,6 +11,7 @@ import { FormControl } from '@angular/forms'; import { BehaviorSubject, Subscription } from 'rxjs'; import OlGeometryType from 'ol/geom/GeometryType'; +import { Style as OlStyle } from 'ol/style'; import { FormFieldComponent } from '@igo2/common'; @@ -69,7 +70,7 @@ export class GeometryFormFieldComponent implements OnInit, OnDestroy { /** * The drawGuide around the mouse pointer to help drawing */ - @Input() drawGuide: number = 0; + @Input() drawGuide: number = null; /** * Draw guide placeholder @@ -81,6 +82,17 @@ export class GeometryFormFieldComponent implements OnInit, OnDestroy { */ @Input() measure: boolean = false; + /** + * Style for the draw control (applies while the geometry is being drawn) + */ + @Input() drawStyle: OlStyle; + + /** + * Style for the overlay layer (applies once the geometry is added to the map) + * If not specified, drawStyle applies + */ + @Input() overlayStyle: OlStyle; + /** * The geometry type model */ @@ -126,5 +138,4 @@ export class GeometryFormFieldComponent implements OnInit, OnDestroy { onDrawGuideChange(value: number) { this.drawGuide$.next(value); } - } diff --git a/packages/geo/src/lib/geometry/shared/geometry.utils.ts b/packages/geo/src/lib/geometry/shared/geometry.utils.ts index 9ddcc19a10..0c22fc2e86 100644 --- a/packages/geo/src/lib/geometry/shared/geometry.utils.ts +++ b/packages/geo/src/lib/geometry/shared/geometry.utils.ts @@ -14,24 +14,26 @@ import { /** * Create a default style for draw and modify interactions + * @param color Style color (R, G, B) * @returns OL style */ -export function createDrawInteractionStyle(): olstyle.Style { +export function createDrawInteractionStyle(color?: [number, number, number]): olstyle.Style { + color = color || [0, 153, 255]; return new olstyle.Style({ stroke: new olstyle.Stroke({ - color: [0, 153, 255, 1], + color: color.concat([1]), width: 2 }), fill: new olstyle.Fill({ - color: [0, 153, 255, 0.2] + color: color.concat([0.2]) }), image: new olstyle.Circle({ - radius: 5, + radius: 8, stroke: new olstyle.Stroke({ - color: [0, 153, 255, 1], + color: color.concat([1]) }), fill: new olstyle.Fill({ - color: [0, 153, 255, 0.2] + color: color.concat([0.2]) }) }) }); From 47761f68f9aa456c62dfd3b01777a8b97ec4e06d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Barbeau?= Date: Fri, 26 Jul 2019 09:18:47 -0400 Subject: [PATCH 25/38] test(search): remove inused test --- .../search-settings.component.spec.ts | 25 ------------------- 1 file changed, 25 deletions(-) delete mode 100644 packages/geo/src/lib/search/search-settings/search-settings.component.spec.ts diff --git a/packages/geo/src/lib/search/search-settings/search-settings.component.spec.ts b/packages/geo/src/lib/search/search-settings/search-settings.component.spec.ts deleted file mode 100644 index 3e15623a2b..0000000000 --- a/packages/geo/src/lib/search/search-settings/search-settings.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { SearchSettingsComponent } from './search-settings.component'; - -describe('SearchSettingsComponent', () => { - let component: SearchSettingsComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ SearchSettingsComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(SearchSettingsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); From a30c7c86f3a584baa322ec5d95b7a39f9c6d2469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Barbeau?= Date: Fri, 26 Jul 2019 09:31:47 -0400 Subject: [PATCH 26/38] 1.0.0-alpha.3 --- CHANGELOG.md | 46 +++++++++++++++++++++++++++++++ package-lock.json | 2 +- package.json | 2 +- packages/auth/package.json | 6 ++-- packages/common/package.json | 6 ++-- packages/context/package.json | 12 ++++---- packages/core/package.json | 4 +-- packages/geo/package.json | 8 +++--- packages/integration/package.json | 6 ++-- packages/utils/package.json | 2 +- 10 files changed, 70 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cb5ec3d7a..b1a190ce8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,49 @@ +# [1.0.0-alpha.3](https://github.com/infra-geo-ouverte/igo2-lib/compare/1.0.0-alpha.2...1.0.0-alpha.3) (2019-07-26) + + +### Bug Fixes + +* fix icon, harmonizing crossOrigin syntax and Allow IE 11 to manage some object properly ([#372](https://github.com/infra-geo-ouverte/igo2-lib/issues/372)) ([e9d9f31](https://github.com/infra-geo-ouverte/igo2-lib/commit/e9d9f31)) +* **catalog:** fix add layer icon ([0a4e591](https://github.com/infra-geo-ouverte/igo2-lib/commit/0a4e591)) +* **context:** fix create layerOptions when create context ([7054b02](https://github.com/infra-geo-ouverte/igo2-lib/commit/7054b02)) +* **demo:** fix action demo ([bcd2a11](https://github.com/infra-geo-ouverte/igo2-lib/commit/bcd2a11)) +* **entity-table:** fix check for button click functions ([af7b60b](https://github.com/infra-geo-ouverte/igo2-lib/commit/af7b60b)) +* **form:** fix disabled form fields ([43d88a2](https://github.com/infra-geo-ouverte/igo2-lib/commit/43d88a2)) +* **geometry input:** fix buffer of size 0 behavior ([71fac4b](https://github.com/infra-geo-ouverte/igo2-lib/commit/71fac4b)) +* **icons:** fix a few missing icons (post font upgrade) ([308bac4](https://github.com/infra-geo-ouverte/igo2-lib/commit/308bac4)) +* **icons:** fix icons ([4d98eb7](https://github.com/infra-geo-ouverte/igo2-lib/commit/4d98eb7)) +* **print:** fix print undefined comment ([f537a95](https://github.com/infra-geo-ouverte/igo2-lib/commit/f537a95)) +* **sharemap:** Limit sharemap url length, Coord precision & skip default context ([#367](https://github.com/infra-geo-ouverte/igo2-lib/issues/367)) ([4c4fb3c](https://github.com/infra-geo-ouverte/igo2-lib/commit/4c4fb3c)) +* **workspace-selector:** change many attr to multi ([4564e91](https://github.com/infra-geo-ouverte/igo2-lib/commit/4564e91)) +* **zoom:** remove minResolution ([758fb0b](https://github.com/infra-geo-ouverte/igo2-lib/commit/758fb0b)) + + +### Features + +* **catalog:** allow ctalogs to define query params and source options ([83d61ce](https://github.com/infra-geo-ouverte/igo2-lib/commit/83d61ce)) +* **catalog:** optionnally force a user to expand a group of layers before adding it to the map ([e502a52](https://github.com/infra-geo-ouverte/igo2-lib/commit/e502a52)) +* **catalog tool:** allow catalog tool to define the toggle group input ([b5bc01a](https://github.com/infra-geo-ouverte/igo2-lib/commit/b5bc01a)) +* **datasource:** add Cluster datasource ([#374](https://github.com/infra-geo-ouverte/igo2-lib/issues/374)) ([d22d3c2](https://github.com/infra-geo-ouverte/igo2-lib/commit/d22d3c2)) +* **datasource,layer:** add MVT datasource, vectortile layer and style by attribute ([#368](https://github.com/infra-geo-ouverte/igo2-lib/issues/368)) ([5ff9239](https://github.com/infra-geo-ouverte/igo2-lib/commit/5ff9239)) +* **entity selector:** support multiple selections ([3d30520](https://github.com/infra-geo-ouverte/igo2-lib/commit/3d30520)) +* **entity-selector:** support multiple selections ([fc89dd7](https://github.com/infra-geo-ouverte/igo2-lib/commit/fc89dd7)) +* **form:** add utility method to retrieve a form's fields ([1329282](https://github.com/infra-geo-ouverte/igo2-lib/commit/1329282)) +* **form:** dynamic form fields can now have a disable switch, useful for batch editing, for example ([d7d7fb4](https://github.com/infra-geo-ouverte/igo2-lib/commit/d7d7fb4)) +* **form:** dynamic forms now support textareas ([bf8d081](https://github.com/infra-geo-ouverte/igo2-lib/commit/bf8d081)) +* **geometry-form-field:** allow to set symbol ([#373](https://github.com/infra-geo-ouverte/igo2-lib/issues/373)) ([87cf1cd](https://github.com/infra-geo-ouverte/igo2-lib/commit/87cf1cd)) +* **icherche:** icherche v2 / territoire ([e0e0a0a](https://github.com/infra-geo-ouverte/igo2-lib/commit/e0e0a0a)) +* **query:** Force a geometry to html getfeatureinfo ([#363](https://github.com/infra-geo-ouverte/igo2-lib/issues/363)) ([d2e33ae](https://github.com/infra-geo-ouverte/igo2-lib/commit/d2e33ae)) +* **query:** keep wms title ([9575f30](https://github.com/infra-geo-ouverte/igo2-lib/commit/9575f30)) +* **rotation-button:** Set option to always show even if no rotation ([#312](https://github.com/infra-geo-ouverte/igo2-lib/issues/312)) ([58dd071](https://github.com/infra-geo-ouverte/igo2-lib/commit/58dd071)) +* **search:** add Searchsource settings ([#370](https://github.com/infra-geo-ouverte/igo2-lib/issues/370)) ([0a01898](https://github.com/infra-geo-ouverte/igo2-lib/commit/0a01898)), closes [#349](https://github.com/infra-geo-ouverte/igo2-lib/issues/349) +* **search:** rainbow of possibilities for searching coordinate ([#365](https://github.com/infra-geo-ouverte/igo2-lib/issues/365)) ([e8c2147](https://github.com/infra-geo-ouverte/igo2-lib/commit/e8c2147)), closes [#288](https://github.com/infra-geo-ouverte/igo2-lib/issues/288) +* **search:** Searchsource hashtag ([#371](https://github.com/infra-geo-ouverte/igo2-lib/issues/371)) ([e69276e](https://github.com/infra-geo-ouverte/igo2-lib/commit/e69276e)), closes [#349](https://github.com/infra-geo-ouverte/igo2-lib/issues/349) +* **search-results-tool:** add feature details in tool ([753cb23](https://github.com/infra-geo-ouverte/igo2-lib/commit/753cb23)) +* **store:** add an empty$ and count$ observables ([f0de496](https://github.com/infra-geo-ouverte/igo2-lib/commit/f0de496)) +* **view:** add a count and empty observables to entity views ([4a0444c](https://github.com/infra-geo-ouverte/igo2-lib/commit/4a0444c)) + + + # [1.0.0-alpha.2](https://github.com/infra-geo-ouverte/igo2-lib/compare/1.0.0-alpha.1...1.0.0-alpha.2) (2019-06-07) diff --git a/package-lock.json b/package-lock.json index 0263dc821d..4469b48361 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@igo/demo", - "version": "1.0.0-alpha.2", + "version": "1.0.0-alpha.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 8cfe43a163..dc69616f82 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@igo/demo", - "version": "1.0.0-alpha.2", + "version": "1.0.0-alpha.3", "description": "IGO Library", "author": "MSP", "license": "MIT", diff --git a/packages/auth/package.json b/packages/auth/package.json index 1ae5f039d9..4fa080579c 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,6 +1,6 @@ { "name": "@igo2/auth", - "version": "1.0.0-alpha.2", + "version": "1.0.0-alpha.3", "description": "IGO Library", "author": "MSP", "keywords": ["igo"], @@ -22,8 +22,8 @@ "@angular/core": "^7.2.6", "@angular/forms": "^7.2.6", "@angular/material": "^7.3.3", - "@igo2/core": "^1.0.0-alpha.2", - "@igo2/utils": "^1.0.0-alpha.2", + "@igo2/core": "^1.0.0-alpha.3", + "@igo2/utils": "^1.0.0-alpha.3", "rxjs": "^6.4.0" } } \ No newline at end of file diff --git a/packages/common/package.json b/packages/common/package.json index d035897e5b..f4fa9a7c4f 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -1,6 +1,6 @@ { "name": "@igo2/common", - "version": "1.0.0-alpha.2", + "version": "1.0.0-alpha.3", "description": "IGO Library", "author": "MSP", "keywords": ["igo"], @@ -17,8 +17,8 @@ "@angular/common": "^7.2.6", "@angular/core": "^7.2.6", "@angular/material": "^7.3.3", - "@igo2/core": "^1.0.0-alpha.2", - "@igo2/utils": "^1.0.0-alpha.2", + "@igo2/core": "^1.0.0-alpha.3", + "@igo2/utils": "^1.0.0-alpha.3", "typy": "^2.0.1" } } \ No newline at end of file diff --git a/packages/context/package.json b/packages/context/package.json index 84a8efc8af..e91f093415 100644 --- a/packages/context/package.json +++ b/packages/context/package.json @@ -1,6 +1,6 @@ { "name": "@igo2/context", - "version": "1.0.0-alpha.2", + "version": "1.0.0-alpha.3", "description": "IGO Library", "author": "MSP", "keywords": ["igo"], @@ -19,11 +19,11 @@ "@angular/core": "^7.2.6", "@angular/forms": "^7.2.6", "@angular/material": "^7.3.3", - "@igo2/auth": "^1.0.0-alpha.2", - "@igo2/common": "^1.0.0-alpha.2", - "@igo2/core": "^1.0.0-alpha.2", - "@igo2/utils": "^1.0.0-alpha.2", - "@igo2/geo": "^1.0.0-alpha.2", + "@igo2/auth": "^1.0.0-alpha.3", + "@igo2/common": "^1.0.0-alpha.3", + "@igo2/core": "^1.0.0-alpha.3", + "@igo2/utils": "^1.0.0-alpha.3", + "@igo2/geo": "^1.0.0-alpha.3", "ol": "^5.3.0", "rxjs": "^6.4.0" } diff --git a/packages/core/package.json b/packages/core/package.json index 2d74b3a2dc..6e5c45e26f 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@igo2/core", - "version": "1.0.0-alpha.2", + "version": "1.0.0-alpha.3", "description": "IGO Library", "author": "MSP", "keywords": ["igo"], @@ -21,7 +21,7 @@ "@angular/common": "^7.2.6", "@angular/core": "^7.2.6", "@angular/cdk": "^7.3.3", - "@igo2/utils": "^1.0.0-alpha.2", + "@igo2/utils": "^1.0.0-alpha.3", "rxjs": "^6.4.0" } } \ No newline at end of file diff --git a/packages/geo/package.json b/packages/geo/package.json index c795519792..6f210c04c0 100644 --- a/packages/geo/package.json +++ b/packages/geo/package.json @@ -1,6 +1,6 @@ { "name": "@igo2/geo", - "version": "1.0.0-alpha.2", + "version": "1.0.0-alpha.3", "description": "IGO Library", "author": "MSP", "keywords": ["igo"], @@ -24,9 +24,9 @@ "@angular/core": "^7.2.6", "@angular/forms": "^7.2.6", "@angular/material": "^7.3.3", - "@igo2/common": "^1.0.0-alpha.2", - "@igo2/core": "^1.0.0-alpha.2", - "@igo2/utils": "^1.0.0-alpha.2", + "@igo2/common": "^1.0.0-alpha.3", + "@igo2/core": "^1.0.0-alpha.3", + "@igo2/utils": "^1.0.0-alpha.3", "ol": "^5.3.0", "proj4": "^2.5.0", "rxjs": "^6.4.0" diff --git a/packages/integration/package.json b/packages/integration/package.json index 5f3a68f644..ed7a9d43f4 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,6 +1,6 @@ { "name": "@igo2/integration", - "version": "1.0.0-alpha.2", + "version": "1.0.0-alpha.3", "description": "IGO Library", "author": "MSP", "keywords": ["igo"], @@ -16,8 +16,8 @@ "peerDependencies": { "@angular/common": "^7.2.6", "@angular/core": "^7.2.6", - "@igo2/geo": "1.0.0-alpha.2", - "@igo2/context": "1.0.0-alpha.2", + "@igo2/geo": "1.0.0-alpha.3", + "@igo2/context": "1.0.0-alpha.3", "rxjs": "^6.4.0" } } \ No newline at end of file diff --git a/packages/utils/package.json b/packages/utils/package.json index fe3911d495..331ba81dac 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@igo2/utils", - "version": "1.0.0-alpha.2", + "version": "1.0.0-alpha.3", "description": "IGO Library", "author": "MSP", "keywords": ["igo"], From 7771fb92928cf9f6c681ce79952dfbd503ae4fa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Barbeau?= Date: Mon, 29 Jul 2019 09:54:06 -0400 Subject: [PATCH 27/38] fix(context): fix minors issues context module --- .../context-edit/context-edit.component.ts | 9 +++++++-- .../context-form/context-form.component.scss | 4 ++++ .../src/lib/context-manager/shared/context.service.ts | 2 +- .../context-editor-tool/context-editor-tool.component.ts | 4 +--- .../context-manager-tool.component.ts | 2 +- .../context-permission-manager-tool.component.ts | 6 ++---- 6 files changed, 16 insertions(+), 11 deletions(-) diff --git a/packages/context/src/lib/context-manager/context-edit/context-edit.component.ts b/packages/context/src/lib/context-manager/context-edit/context-edit.component.ts index 2d1132e572..1149376e10 100644 --- a/packages/context/src/lib/context-manager/context-edit/context-edit.component.ts +++ b/packages/context/src/lib/context-manager/context-edit/context-edit.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { Component, Input, Output, EventEmitter, ChangeDetectorRef } from '@angular/core'; import { Context } from '../shared/context.interface'; @@ -13,10 +13,15 @@ export class ContextEditComponent { } set context(value: Context) { this._context = value; + this.refresh(); } private _context: Context; @Output() submitForm: EventEmitter = new EventEmitter(); - constructor() {} + constructor(private cd: ChangeDetectorRef) {} + + refresh() { + this.cd.detectChanges(); + } } diff --git a/packages/context/src/lib/context-manager/context-form/context-form.component.scss b/packages/context/src/lib/context-manager/context-form/context-form.component.scss index a10eda725b..7a16acee87 100644 --- a/packages/context/src/lib/context-manager/context-form/context-form.component.scss +++ b/packages/context/src/lib/context-manager/context-form/context-form.component.scss @@ -1,3 +1,7 @@ +form { + margin: 0 10px; +} + .full-width { width: 100%; } diff --git a/packages/context/src/lib/context-manager/shared/context.service.ts b/packages/context/src/lib/context-manager/shared/context.service.ts index 759a66932c..667dbb995b 100644 --- a/packages/context/src/lib/context-manager/shared/context.service.ts +++ b/packages/context/src/lib/context-manager/shared/context.service.ts @@ -372,7 +372,7 @@ export class ContextService { context.layers.push(opts); } - context.tools = this.tools.map(tool => String(tool.id)); + context.tools = this.tools.map(tool => {id: String(tool.id)}); return context; } diff --git a/packages/integration/src/lib/context/context-editor-tool/context-editor-tool.component.ts b/packages/integration/src/lib/context/context-editor-tool/context-editor-tool.component.ts index 2ed2abcdd8..050b3c0864 100644 --- a/packages/integration/src/lib/context/context-editor-tool/context-editor-tool.component.ts +++ b/packages/integration/src/lib/context/context-editor-tool/context-editor-tool.component.ts @@ -11,6 +11,4 @@ import { ToolComponent } from '@igo2/common'; selector: 'igo-context-editor-tool', templateUrl: './context-editor-tool.component.html' }) -export class ContextEditorToolComponent { - constructor() {} -} +export class ContextEditorToolComponent {} diff --git a/packages/integration/src/lib/context/context-manager-tool/context-manager-tool.component.ts b/packages/integration/src/lib/context/context-manager-tool/context-manager-tool.component.ts index 60449460dc..04834f4e0b 100644 --- a/packages/integration/src/lib/context/context-manager-tool/context-manager-tool.component.ts +++ b/packages/integration/src/lib/context/context-manager-tool/context-manager-tool.component.ts @@ -22,6 +22,6 @@ export class ContextManagerToolComponent { } managePermissions() { - this.toolState.toolbox.activateTool('permissionsContextManager'); + this.toolState.toolbox.activateTool('contextPermissionManager'); } } diff --git a/packages/integration/src/lib/context/context-permission-manager-tool/context-permission-manager-tool.component.ts b/packages/integration/src/lib/context/context-permission-manager-tool/context-permission-manager-tool.component.ts index f777c5438a..7d17ccb501 100644 --- a/packages/integration/src/lib/context/context-permission-manager-tool/context-permission-manager-tool.component.ts +++ b/packages/integration/src/lib/context/context-permission-manager-tool/context-permission-manager-tool.component.ts @@ -1,4 +1,4 @@ -import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { Component } from '@angular/core'; import { ToolComponent } from '@igo2/common'; @@ -11,6 +11,4 @@ import { ToolComponent } from '@igo2/common'; selector: 'igo-context-permission-manager-tool', templateUrl: './context-permission-manager-tool.component.html' }) -export class ContextPermissionManagerToolComponent { - constructor() {} -} +export class ContextPermissionManagerToolComponent {} From b1083e849ee5f1f82aea1317617500a54a7702df Mon Sep 17 00:00:00 2001 From: PhilippeLafreniere18 <53181414+PhilippeLafreniere18@users.noreply.github.com> Date: Tue, 30 Jul 2019 13:32:08 -0400 Subject: [PATCH 28/38] feat(catalog): add metadata button (#377) --- .../catalog-browser/catalog-browser-layer.component.html | 5 ++++- .../lib/catalog/catalog-browser/catalog-browser.module.ts | 4 +++- packages/geo/src/lib/catalog/shared/catalog.interface.ts | 5 +++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/geo/src/lib/catalog/catalog-browser/catalog-browser-layer.component.html b/packages/geo/src/lib/catalog/catalog-browser/catalog-browser-layer.component.html index cae32eddb9..38b997d72a 100644 --- a/packages/geo/src/lib/catalog/catalog-browser/catalog-browser-layer.component.html +++ b/packages/geo/src/lib/catalog/catalog-browser/catalog-browser-layer.component.html @@ -1,7 +1,9 @@

{{title}}

- + + + +
diff --git a/packages/geo/src/lib/catalog/catalog-browser/catalog-browser.module.ts b/packages/geo/src/lib/catalog/catalog-browser/catalog-browser.module.ts index d66b5bf7b6..7b476fb7f6 100644 --- a/packages/geo/src/lib/catalog/catalog-browser/catalog-browser.module.ts +++ b/packages/geo/src/lib/catalog/catalog-browser/catalog-browser.module.ts @@ -14,6 +14,7 @@ import { IgoListModule } from '@igo2/common'; +import { IgoMetadataModule } from './../../metadata/metadata.module'; import { CatalogBrowserComponent } from './catalog-browser.component'; import { CatalogBrowserLayerComponent } from './catalog-browser-layer.component'; import { CatalogBrowserGroupComponent } from './catalog-browser-group.component'; @@ -30,7 +31,8 @@ import { CatalogBrowserGroupComponent } from './catalog-browser-group.component' MatTooltipModule, IgoLanguageModule, IgoListModule, - IgoCollapsibleModule + IgoCollapsibleModule, + IgoMetadataModule ], exports: [ CatalogBrowserComponent diff --git a/packages/geo/src/lib/catalog/shared/catalog.interface.ts b/packages/geo/src/lib/catalog/shared/catalog.interface.ts index cd13accfec..ec1c40cf09 100644 --- a/packages/geo/src/lib/catalog/shared/catalog.interface.ts +++ b/packages/geo/src/lib/catalog/shared/catalog.interface.ts @@ -1,6 +1,7 @@ import { EntityState } from '@igo2/common'; -import { LayerOptions, TooltipType } from '../../layer'; +import { MetadataLayerOptions } from './../../metadata/shared/metadata.interface'; +import { TooltipType } from '../../layer'; import { TimeFilterOptions } from '../../filter'; import { QueryFormat, QueryHtmlTarget } from '../../query'; @@ -33,7 +34,7 @@ export interface CatalogItem { type: CatalogItemType; } -export interface CatalogItemLayer extends CatalogItem { +export interface CatalogItemLayer extends CatalogItem { options: L; } From d0d4aaee30f5f6a42a8080a039c31bc881468539 Mon Sep 17 00:00:00 2001 From: Amelie Bernier Date: Mon, 5 Aug 2019 09:28:04 -0400 Subject: [PATCH 29/38] feat(entity-table): add option to choose button style --- .../entity-table/entity-table.component.html | 23 +++++++++++++------ .../lib/entity/shared/entity.interfaces.ts | 1 + 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/common/src/lib/entity/entity-table/entity-table.component.html b/packages/common/src/lib/entity/entity-table/entity-table.component.html index f5e4985ee3..fe6b91c57e 100644 --- a/packages/common/src/lib/entity/entity-table/entity-table.component.html +++ b/packages/common/src/lib/entity/entity-table/entity-table.component.html @@ -66,13 +66,22 @@ - + + + + diff --git a/packages/common/src/lib/entity/shared/entity.interfaces.ts b/packages/common/src/lib/entity/shared/entity.interfaces.ts index 44300920b9..cfff5a0022 100644 --- a/packages/common/src/lib/entity/shared/entity.interfaces.ts +++ b/packages/common/src/lib/entity/shared/entity.interfaces.ts @@ -92,4 +92,5 @@ export interface EntityTableButton { icon: string; click: (entity: object) => void; color?: 'primary' | 'accent' | 'warn'; + style?: 'mat-mini-fab' | 'mat-icon-button'; } From 2e37a44b1579a28898dfa7086d51b2f01e1843a5 Mon Sep 17 00:00:00 2001 From: matrottier Date: Wed, 7 Aug 2019 10:02:23 -0400 Subject: [PATCH 30/38] feat(vector-layer-watcher) add watcher to vector layer (#378) * feat(vector-layer-watcher) add watcher to vector layer * refactor(demo-layer) set back demo * feat(datasource) add unwatch method * fix(VectorAnimation) color optional. default red --- .../datasources/arcgisrest-datasource.ts | 2 ++ .../shared/datasources/carto-datasource.ts | 2 ++ .../shared/datasources/cluster-datasource.ts | 2 ++ .../shared/datasources/datasource.ts | 2 ++ .../shared/datasources/feature-datasource.ts | 2 ++ .../shared/datasources/mvt-datasource.ts | 2 ++ .../shared/datasources/osm-datasource.ts | 2 ++ .../datasources/tilearcgisrest-datasource.ts | 2 ++ .../datasources/websocket-datasource.ts | 4 +++ .../shared/datasources/wfs-datasource.ts | 2 ++ .../shared/datasources/wms-datasource.ts | 2 ++ .../shared/datasources/wmts-datasource.ts | 2 ++ .../shared/datasources/xyz-datasource.ts | 2 ++ .../shared/layers/vector-layer.interface.ts | 2 +- .../lib/layer/shared/layers/vector-layer.ts | 28 +++++++++++++++++++ packages/geo/src/lib/layer/utils/index.ts | 1 + .../geo/src/lib/layer/utils/vector-watcher.ts | 25 +++++++++++++++++ .../geo/src/lib/map/utils/layer-watcher.ts | 1 + 18 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 packages/geo/src/lib/layer/utils/vector-watcher.ts diff --git a/packages/geo/src/lib/datasource/shared/datasources/arcgisrest-datasource.ts b/packages/geo/src/lib/datasource/shared/datasources/arcgisrest-datasource.ts index e3acebbb3c..79146e12db 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/arcgisrest-datasource.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/arcgisrest-datasource.ts @@ -81,4 +81,6 @@ export class ArcGISRestDataSource extends DataSource { htmlString += ''; return [{ html: htmlString }]; } + + public onUnwatch() {} } diff --git a/packages/geo/src/lib/datasource/shared/datasources/carto-datasource.ts b/packages/geo/src/lib/datasource/shared/datasources/carto-datasource.ts index 509e5ddf9a..a78ef481f5 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/carto-datasource.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/carto-datasource.ts @@ -113,4 +113,6 @@ export class CartoDataSource extends DataSource { return [{ html: htmlString }]; } } + + public onUnwatch() {} } diff --git a/packages/geo/src/lib/datasource/shared/datasources/cluster-datasource.ts b/packages/geo/src/lib/datasource/shared/datasources/cluster-datasource.ts index 4f3a0925b1..87aeb4cf73 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/cluster-datasource.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/cluster-datasource.ts @@ -18,4 +18,6 @@ export class ClusterDataSource extends FeatureDataSource { protected generateId() { return uuid(); } + + public onUnwatch() {} } diff --git a/packages/geo/src/lib/datasource/shared/datasources/datasource.ts b/packages/geo/src/lib/datasource/shared/datasources/datasource.ts index 9711c04f7e..8f908b2767 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/datasource.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/datasource.ts @@ -30,4 +30,6 @@ export abstract class DataSource { getLegend(scale?: number): DataSourceLegendOptions[] { return this.options.legend ? [this.options.legend] : []; } + + protected abstract onUnwatch(); } diff --git a/packages/geo/src/lib/datasource/shared/datasources/feature-datasource.ts b/packages/geo/src/lib/datasource/shared/datasources/feature-datasource.ts index 178e475351..5cd9c6c1f8 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/feature-datasource.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/feature-datasource.ts @@ -40,4 +40,6 @@ export class FeatureDataSource extends DataSource { return format; } + + public onUnwatch() {} } diff --git a/packages/geo/src/lib/datasource/shared/datasources/mvt-datasource.ts b/packages/geo/src/lib/datasource/shared/datasources/mvt-datasource.ts index f2513aaac3..2c46738480 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/mvt-datasource.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/mvt-datasource.ts @@ -25,4 +25,6 @@ export class MVTDataSource extends DataSource { const chain = 'mvt' + this.options.url; return Md5.hashStr(chain) as string; } + + public onUnwatch() {} } diff --git a/packages/geo/src/lib/datasource/shared/datasources/osm-datasource.ts b/packages/geo/src/lib/datasource/shared/datasources/osm-datasource.ts index ce9c79a636..a596326e50 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/osm-datasource.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/osm-datasource.ts @@ -11,4 +11,6 @@ export class OSMDataSource extends DataSource { this.options.url = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'; return new olSourceOSM(this.options); } + + public onUnwatch() {} } diff --git a/packages/geo/src/lib/datasource/shared/datasources/tilearcgisrest-datasource.ts b/packages/geo/src/lib/datasource/shared/datasources/tilearcgisrest-datasource.ts index 4b65cfbf4b..7b1b629299 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/tilearcgisrest-datasource.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/tilearcgisrest-datasource.ts @@ -55,4 +55,6 @@ export class TileArcGISRestDataSource extends DataSource { htmlString += ''; return [{ html: htmlString }]; } + + public onUnwatch() {} } diff --git a/packages/geo/src/lib/datasource/shared/datasources/websocket-datasource.ts b/packages/geo/src/lib/datasource/shared/datasources/websocket-datasource.ts index 7509e79c0a..177c9013c0 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/websocket-datasource.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/websocket-datasource.ts @@ -70,4 +70,8 @@ export class WebSocketDataSource extends FeatureDataSource { onOpen(event) { // thrown message to user ? } + + public onUnwatch() { + this.ws.close(); + } } diff --git a/packages/geo/src/lib/datasource/shared/datasources/wfs-datasource.ts b/packages/geo/src/lib/datasource/shared/datasources/wfs-datasource.ts index 2445363a8d..12d8a94e21 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/wfs-datasource.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/wfs-datasource.ts @@ -140,4 +140,6 @@ export class WFSDataSource extends DataSource { return new olFormatCls(); } + + public onUnwatch() {} } diff --git a/packages/geo/src/lib/datasource/shared/datasources/wms-datasource.ts b/packages/geo/src/lib/datasource/shared/datasources/wms-datasource.ts index 97f80bdca8..033ee3bcd0 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/wms-datasource.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/wms-datasource.ts @@ -193,4 +193,6 @@ export class WMSDataSource extends DataSource { return legend; } + + public onUnwatch() {} } diff --git a/packages/geo/src/lib/datasource/shared/datasources/wmts-datasource.ts b/packages/geo/src/lib/datasource/shared/datasources/wmts-datasource.ts index 4dfa6d3c8d..a573fb0b62 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/wmts-datasource.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/wmts-datasource.ts @@ -23,4 +23,6 @@ export class WMTSDataSource extends DataSource { return new olSourceWMTS(sourceOptions); } + public onUnwatch() {} + } diff --git a/packages/geo/src/lib/datasource/shared/datasources/xyz-datasource.ts b/packages/geo/src/lib/datasource/shared/datasources/xyz-datasource.ts index 1d777f1acb..4c4ea8f88a 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/xyz-datasource.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/xyz-datasource.ts @@ -11,4 +11,6 @@ export class XYZDataSource extends DataSource { return new olSourceXYZ(this.options); } + public onUnwatch() {} + } diff --git a/packages/geo/src/lib/layer/shared/layers/vector-layer.interface.ts b/packages/geo/src/lib/layer/shared/layers/vector-layer.interface.ts index eaf2dd4cab..8b111147da 100644 --- a/packages/geo/src/lib/layer/shared/layers/vector-layer.interface.ts +++ b/packages/geo/src/lib/layer/shared/layers/vector-layer.interface.ts @@ -44,5 +44,5 @@ export interface VectorLayerOptions extends LayerOptions { export interface VectorAnimation { duration?: number; - color: olColor; + color?: olColor; } diff --git a/packages/geo/src/lib/layer/shared/layers/vector-layer.ts b/packages/geo/src/lib/layer/shared/layers/vector-layer.ts index 597c69aac7..64acea4998 100644 --- a/packages/geo/src/lib/layer/shared/layers/vector-layer.ts +++ b/packages/geo/src/lib/layer/shared/layers/vector-layer.ts @@ -10,6 +10,8 @@ import { ArcGISRestDataSource } from '../../../datasource/shared/datasources/arc import { WebSocketDataSource } from '../../../datasource/shared/datasources/websocket-datasource'; import { ClusterDataSource } from '../../../datasource/shared/datasources/cluster-datasource'; +import { VectorWatcher } from '../../utils'; +import { IgoMap } from '../../../map'; import { Layer } from './layer'; import { VectorLayerOptions } from './vector-layer.interface'; @@ -17,6 +19,7 @@ export class VectorLayer extends Layer { public dataSource: FeatureDataSource | WFSDataSource | ArcGISRestDataSource | WebSocketDataSource | ClusterDataSource; public options: VectorLayerOptions; public ol: olLayerVector; + private watcher: VectorWatcher; get browsable(): boolean { return this.options.browsable !== false; @@ -28,6 +31,8 @@ export class VectorLayer extends Layer { constructor(options: VectorLayerOptions) { super(options); + this.watcher = new VectorWatcher(this); + this.status$ = this.watcher.status$; } protected createOlLayer(): olLayerVector { @@ -126,4 +131,27 @@ export class VectorLayer extends Layer { this.map.ol.render(); } } + + public setMap(map: IgoMap | undefined) { + if (map === undefined) { + this.watcher.unsubscribe(); + } else { + this.watcher.subscribe(() => {}); + } + super.setMap(map); + } + + public onUnwatch() { + this.dataSource.onUnwatch(); + this.stopAnimation(); + } + + public stopAnimation() { + this.dataSource.ol.un( + 'addfeature', + function(e) { + this.flash(e.feature); + }.bind(this) + ); + } } diff --git a/packages/geo/src/lib/layer/utils/index.ts b/packages/geo/src/lib/layer/utils/index.ts index eee0ac603a..a4cb2c76a2 100644 --- a/packages/geo/src/lib/layer/utils/index.ts +++ b/packages/geo/src/lib/layer/utils/index.ts @@ -1,3 +1,4 @@ export * from './image-watcher'; export * from './tile-watcher'; +export * from './vector-watcher'; export * from './legend'; diff --git a/packages/geo/src/lib/layer/utils/vector-watcher.ts b/packages/geo/src/lib/layer/utils/vector-watcher.ts new file mode 100644 index 0000000000..6d985bca35 --- /dev/null +++ b/packages/geo/src/lib/layer/utils/vector-watcher.ts @@ -0,0 +1,25 @@ +import olSourceVector from 'ol/source/Vector'; +import { uuid, Watcher, SubjectStatus } from '@igo2/utils'; + +import { VectorLayer } from '../shared/layers/vector-layer'; + +export class VectorWatcher extends Watcher { + private id: string; + private loaded = 0; + private loading = 0; + + private layer: VectorLayer; + + constructor(layer: VectorLayer) { + super(); + this.layer = layer; + this.id = uuid(); + } + + protected watch() { + } + + protected unwatch() { + this.layer.onUnwatch(); + } +} diff --git a/packages/geo/src/lib/map/utils/layer-watcher.ts b/packages/geo/src/lib/map/utils/layer-watcher.ts index a266e4533d..fc06b457c8 100644 --- a/packages/geo/src/lib/map/utils/layer-watcher.ts +++ b/packages/geo/src/lib/map/utils/layer-watcher.ts @@ -59,6 +59,7 @@ export class LayerWatcher extends Watcher { this.subscriptions[index].unsubscribe(); this.subscriptions.splice(index, 1); this.layers.splice(index, 1); + (layer as any).watcher.unwatch(); } } } From 1466996792bc2a7ba2d3a260ce7c0863431ea6f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-=C3=89tienne=20Lord?= <7397743+pelord@users.noreply.github.com> Date: Wed, 7 Aug 2019 10:07:43 -0400 Subject: [PATCH 31/38] feat(ogcFilters): OgcFilters simplification, PushButtons and fields & operator control (#361) * refactor(ogc-filter) Manage ogcfilterwriter usage. * refactor(ogc-filter) interface modified * refactor(wfs-datasource) simplification * feat(wfs-datasource) only call needed fields * refactor(wms-wfs-datasource)Filters to igoFilters * refactor(*) geometry name for filters * refactor(wms-wfs) Moving common utils/methods * refactor(wms-wfs) using shared method and const. * refactor(*) various minor fix * feat(pushButtons)Adding pushbutton over ogcfilters * refactor(*) ogcfilters result from buildFilter * wip(ogc-filters) * feat (wfs,ogcfilters)Enable wfs to have pushbutton * refactor(wfs) build url on empty pushbutton * typo(togglebuttons) * refactor(ogc-filter) switch to mdi SVG icon * feat(ogc-filters) exclude field from sourceFields * refactor(ogc-filter)remove geometry as overlay * refactor(ogc-filter) Minor fixes * feat(ogc-filter) Limit operators's list by type * feat(demo) Add ogcfilters pushButtons example * refactor(*) PR 631 simple reviews part 1 * refactor(ogc) place setter before getter * refactor(ogc) remove mention to showfeatureonmap * refactor(*) PR 631 simple reviews part 2 * refactor(wms & wfs & ogcfilters) defaut values * refactor(wfs) removing mention to wfscapabilities * refactor(wfs) default options for filters. * refactor(wms) way to manage empty sourcefields & pushbuttons * refactor(wfs-service) Simplification * refactor(ogc-filter-toggle-button.component) Simplification by binding a boolean. * refactor(ogc-filter) removing usage of feature extent --- .../geo/ogc-filter/ogc-filter.component.ts | 116 ++++++- .../datasources/datasource.interface.ts | 1 + .../datasource/shared/datasources/index.ts | 1 + .../datasources/wfs-datasource.interface.ts | 6 - .../shared/datasources/wfs-datasource.ts | 147 +++------ .../shared/datasources/wfs.service.ts | 305 +++--------------- .../shared/datasources/wms-datasource.ts | 100 ++---- .../shared/datasources/wms-wfs.utils.ts | 107 ++++++ .../lib/download/shared/download.service.ts | 31 +- packages/geo/src/lib/filter/filter.module.ts | 9 +- packages/geo/src/lib/filter/index.ts | 1 + .../ogc-filter-button.component.html | 4 +- .../ogc-filter-button.component.ts | 51 +-- .../ogc-filter-form.component.html | 8 +- .../ogc-filter-form.component.ts | 178 ++++------ .../filter/ogc-filter-toggle-button/index.ts | 1 + .../ogc-filter-toggle-button.component.html | 9 + .../ogc-filter-toggle-button.component.scss | 10 + .../ogc-filter-toggle-button.component.ts | 108 +++++++ .../ogc-filterable-form.component.html | 10 +- .../ogc-filterable-form.component.ts | 36 +-- .../ogc-filterable-item.component.html | 25 +- .../ogc-filterable-item.component.ts | 159 +++------ .../ogc-filterable-list.component.ts | 25 +- packages/geo/src/lib/filter/shared/index.ts | 1 + .../src/lib/filter/shared/ogc-filter.enum.ts | 7 + .../lib/filter/shared/ogc-filter.interface.ts | 25 +- .../lib/filter/shared/ogc-filter.service.ts | 9 +- .../geo/src/lib/filter/shared/ogc-filter.ts | 98 +++++- .../ogc-filter/ogc-filter.component.ts | 2 - packages/geo/src/locale/en.geo.json | 3 +- packages/geo/src/locale/fr.geo.json | 3 +- .../ogc-filter-tool.component.ts | 2 +- 33 files changed, 806 insertions(+), 792 deletions(-) create mode 100644 packages/geo/src/lib/datasource/shared/datasources/wms-wfs.utils.ts create mode 100644 packages/geo/src/lib/filter/ogc-filter-toggle-button/index.ts create mode 100644 packages/geo/src/lib/filter/ogc-filter-toggle-button/ogc-filter-toggle-button.component.html create mode 100644 packages/geo/src/lib/filter/ogc-filter-toggle-button/ogc-filter-toggle-button.component.scss create mode 100644 packages/geo/src/lib/filter/ogc-filter-toggle-button/ogc-filter-toggle-button.component.ts create mode 100644 packages/geo/src/lib/filter/shared/ogc-filter.enum.ts diff --git a/demo/src/app/geo/ogc-filter/ogc-filter.component.ts b/demo/src/app/geo/ogc-filter/ogc-filter.component.ts index 539af51b76..25cb02ff5c 100644 --- a/demo/src/app/geo/ogc-filter/ogc-filter.component.ts +++ b/demo/src/app/geo/ogc-filter/ogc-filter.component.ts @@ -9,7 +9,8 @@ import { WFSDataSourceOptions, WFSDataSourceOptionsParams, OgcFilterableDataSourceOptions, - AnyBaseOgcFilterOptions + AnyBaseOgcFilterOptions, + OgcFilterOperatorType } from '@igo2/geo'; @Component({ @@ -66,12 +67,13 @@ export class AppOgcFilterComponent { }, sourceFields: [ { name: 'code_municipalite', alias: '# de la municipalitée' }, - { name: 'date_observation' }, + { name: 'date_observation', excludeFromOgcFilters: true }, { name: 'urgence', values: ['immédiate', 'inconnue'] } ], ogcFilters: { enabled: true, editable: true, + allowedOperatorsType: OgcFilterOperatorType.All, filters: { logical: 'Or', filters: [ @@ -117,6 +119,116 @@ export class AppOgcFilterComponent { extends WMSDataSourceOptions, OgcFilterableDataSourceOptions {} + const filterableWMSwithPushButtons: WMSoptions = { + type: 'wms', + url: 'https://ws.mapserver.transports.gouv.qc.ca/swtq', + urlWfs: 'https://ws.mapserver.transports.gouv.qc.ca/swtq', + params: { + layers: 'radars_photos', + version: '1.3.0' + }, + ogcFilters: { + enabled: true, + editable: true, + pushButtons: [{ + logical: 'Or', + ogcPushButtons: [{ + title: 'Radar photo fixe', + enabled: true, + color: '0,0,255', + tooltip: 'Here a tooltip explaning ...', + filters: { + operator: 'PropertyIsEqualTo', + propertyName: 'typeAppareil', + expression: 'Radar photo fixe' + } + }, + { + title: 'Radar photo mobile', + enabled: false, + color: '255,200,0', + tooltip: 'Here a tooltip explaning ...', + filters: { + operator: 'PropertyIsEqualTo', + propertyName: 'typeAppareil', + expression: 'Radar photo mobile' + } + }, + { + title: 'Radar photo fixe + feu rouge', + enabled: false, + color: '0,200,0', + tooltip: 'Here a tooltip explaning ...', + filters: { + operator: 'PropertyIsEqualTo', + propertyName: 'typeAppareil', + expression: 'Radar photo fixe et surveillance au feu rouge' + } + }, + { + title: 'Radar feu rouge', + enabled: false, + color: '255,0,0', + tooltip: 'Here a tooltip explaning ...', + filters: { + operator: 'PropertyIsEqualTo', + propertyName: 'typeAppareil', + expression: 'Appareil de surveillance au feu rouge' + } + } + ] + }, + { + logical: 'Or', + vertical: true, + ogcPushButtons: [{ + title: 'Montréal & Laval', + enabled: false, + tooltip: 'Here a tooltip explaning ...', + filters: { + logical: 'Or', + filters: [ + { operator: 'PropertyIsEqualTo', propertyName: 'region', expression: 'Montréal'}, + { operator: 'PropertyIsEqualTo', propertyName: 'region', expression: 'Laval'}] + } + }, + { + title: 'Outside Montréal & Laval', + enabled: false, + tooltip: 'Here a tooltip explaning ...', + filters: { + logical: 'And', + filters: [ + { operator: 'PropertyIsNotEqualTo', propertyName: 'region', expression: 'Montréal'}, + { operator: 'PropertyIsNotEqualTo', propertyName: 'region', expression: 'Laval'}] + } + } + ] + } + ], + allowedOperatorsType: OgcFilterOperatorType.Basic, + }, + paramsWFS: { + featureTypes: 'radars_photos', + fieldNameGeometry: 'geometry', + maxFeatures: 10000, + version: '1.1.0', + outputFormat: 'geojson', + outputFormatDownload: 'shp' + } as WFSDataSourceOptionsParams + }; + + this.dataSourceService + .createAsyncDataSource(filterableWMSwithPushButtons) + .subscribe(dataSource => { + this.map.addLayer( + this.layerService.createLayer({ + title: 'Filterable WMS layers with predefined filters (buttons)', + source: dataSource + }) + ); + }); + const datasourceWmsWith2Layers: WMSoptions = { type: 'wms', url: 'https://ws.mapserver.transports.gouv.qc.ca/swtq', diff --git a/packages/geo/src/lib/datasource/shared/datasources/datasource.interface.ts b/packages/geo/src/lib/datasource/shared/datasources/datasource.interface.ts index 6ecc901281..62289cd48f 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/datasource.interface.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/datasource.interface.ts @@ -40,4 +40,5 @@ export interface SourceFieldsOptionsParams { name: any; alias?: any; values?: any; + excludeFromOgcFilters?: boolean; } diff --git a/packages/geo/src/lib/datasource/shared/datasources/index.ts b/packages/geo/src/lib/datasource/shared/datasources/index.ts index 6ce8d08533..cc9fe66db8 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/index.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/index.ts @@ -12,6 +12,7 @@ export * from './wfs-datasource.interface'; export * from './wfs.service'; export * from './wms-datasource'; export * from './wms-datasource.interface'; +export * from './wms-wfs.utils'; export * from './wmts-datasource'; export * from './wmts-datasource.interface'; export * from './carto-datasource'; diff --git a/packages/geo/src/lib/datasource/shared/datasources/wfs-datasource.interface.ts b/packages/geo/src/lib/datasource/shared/datasources/wfs-datasource.interface.ts index 836c3395c9..5e2dcb2cdd 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/wfs-datasource.interface.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/wfs-datasource.interface.ts @@ -18,10 +18,4 @@ export interface WFSDataSourceOptionsParams { outputFormatDownload?: string; srsName?: string; xmlFilter?: string; - wfsCapabilities?: WFSCapabilitiesParams; -} - -export interface WFSCapabilitiesParams { - xmlBody?: string; - GetPropertyValue?: boolean; } diff --git a/packages/geo/src/lib/datasource/shared/datasources/wfs-datasource.ts b/packages/geo/src/lib/datasource/shared/datasources/wfs-datasource.ts index 12d8a94e21..a5e572457c 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/wfs-datasource.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/wfs-datasource.ts @@ -2,139 +2,90 @@ import olSourceVector from 'ol/source/Vector'; import * as OlLoadingStrategy from 'ol/loadingstrategy'; import * as OlFormat from 'ol/format'; -import { uuid } from '@igo2/utils'; - import { DataSource } from './datasource'; import { WFSDataSourceOptions } from './wfs-datasource.interface'; import { WFSService } from './wfs.service'; import { OgcFilterWriter } from '../../../filter/shared/ogc-filter'; import { OgcFilterableDataSourceOptions } from '../../../filter/shared/ogc-filter.interface'; +import { + formatWFSQueryString, + defaultFieldNameGeometry, + gmlRegex, + jsonRegex, + checkWfsParams +} from './wms-wfs.utils'; export class WFSDataSource extends DataSource { public ol: olSourceVector; - public ogcFilterWriter: OgcFilterWriter; constructor( public options: WFSDataSourceOptions, protected wfsService: WFSService ) { - super(options); - this.options = this.wfsService.checkWfsOptions(options); - this.ogcFilterWriter = new OgcFilterWriter(); - this.wfsService.getSourceFieldsFromWFS(this.options); + super(checkWfsParams(options, 'wfs')); + + const ogcFilters = (this.options as OgcFilterableDataSourceOptions).ogcFilters; + const fieldNameGeometry = this.options.paramsWFS.fieldNameGeometry || defaultFieldNameGeometry; + const ogcFilterWriter = new OgcFilterWriter(); + (this.options as OgcFilterableDataSourceOptions).ogcFilters = ogcFilterWriter.defineOgcFiltersDefaultOptions(ogcFilters, fieldNameGeometry); + if ((this.options as OgcFilterableDataSourceOptions).ogcFilters.enabled) { + this.wfsService.getSourceFieldsFromWFS(this.options); + } } protected createOlSource(): olSourceVector { - // reassignation of params to paramsWFS and url to urlWFS to have a common interface with wms-wfs datasources - this.options.paramsWFS = this.options.params; - this.options.urlWfs = this.options.url; - // default wfs version - this.options.paramsWFS.version = this.options.paramsWFS.version - ? this.options.paramsWFS.version - : '2.0.0'; - const ogcFiltersDefaultValue = true; // default values for wfs. - (this.options as OgcFilterableDataSourceOptions).ogcFilters = - (this.options as OgcFilterableDataSourceOptions).ogcFilters === undefined - ? {} - : (this.options as OgcFilterableDataSourceOptions).ogcFilters; - (this.options as OgcFilterableDataSourceOptions).ogcFilters.enabled = - (this.options as OgcFilterableDataSourceOptions).ogcFilters.enabled === - undefined - ? ogcFiltersDefaultValue - : (this.options as OgcFilterableDataSourceOptions).ogcFilters.enabled; - (this.options as OgcFilterableDataSourceOptions).ogcFilters.editable = - (this.options as OgcFilterableDataSourceOptions).ogcFilters.editable === - undefined - ? ogcFiltersDefaultValue - : (this.options as OgcFilterableDataSourceOptions).ogcFilters.editable; - - const baseWfsQuery = 'service=WFS&request=GetFeature'; - // Mandatory - const url = this.options.urlWfs; - // Optional - const outputFormat = this.options.paramsWFS.outputFormat - ? 'outputFormat=' + this.options.paramsWFS.outputFormat - : ''; - const wfsVersion = this.options.paramsWFS.version - ? 'version=' + this.options.paramsWFS.version - : 'version=' + '2.0.0'; - - let paramTypename = 'typename'; - let paramMaxFeatures = 'maxFeatures'; - if ( - this.options.paramsWFS.version === '2.0.0' || - !this.options.paramsWFS.version - ) { - paramTypename = 'typenames'; - paramMaxFeatures = 'count'; - } - - const featureTypes = - paramTypename + '=' + this.options.paramsWFS.featureTypes; - - const maxFeatures = this.options.paramsWFS.maxFeatures - ? paramMaxFeatures + '=' + this.options.paramsWFS.maxFeatures - : paramMaxFeatures + '=5000'; - - let downloadBaseUrl = `${url}?${baseWfsQuery}&${wfsVersion}&${featureTypes}&`; - downloadBaseUrl += `${outputFormat}&${maxFeatures}`; - - this.options.download = Object.assign({}, this.options.download, { - dynamicUrl: downloadBaseUrl - }); return new olSourceVector({ format: this.getFormatFromOptions(), overlaps: false, url: (extent, resolution, proj) => { - const srsname = this.options.paramsWFS.srsName - ? 'srsname=' + this.options.paramsWFS.srsName - : 'srsname=' + proj.getCode(); - - let filters; - if ( - (this.options as OgcFilterableDataSourceOptions).ogcFilters && - (this.options as OgcFilterableDataSourceOptions).ogcFilters.enabled - ) { - filters = (this.options as OgcFilterableDataSourceOptions).ogcFilters.filters; - } - this.options.paramsWFS.xmlFilter = this.ogcFilterWriter.buildFilter( - filters, + return this.buildUrl( extent, proj, - this.options.paramsWFS.fieldNameGeometry - ); - - let baseUrl = `${url}?${baseWfsQuery}&${wfsVersion}&${featureTypes}&`; - baseUrl += `${outputFormat}&${srsname}&${maxFeatures}`; - - const patternFilter = /(filter|bbox)=.*/gi; - if (patternFilter.test(this.options.paramsWFS.xmlFilter)) { - baseUrl += `&${this.options.paramsWFS.xmlFilter}`; - } - - this.options.download = Object.assign({}, this.options.download, { - dynamicUrl: baseUrl - }); - - return baseUrl; + (this.options as OgcFilterableDataSourceOptions).ogcFilters); }, strategy: OlLoadingStrategy.bbox }); } + private buildUrl(extent, proj, ogcFilters): string { + const paramsWFS = this.options.paramsWFS; + const queryStringValues = formatWFSQueryString(this.options, undefined, proj.getCode()); + let igoFilters; + if (ogcFilters && ogcFilters.enabled) { + igoFilters = ogcFilters.filters; + } + const ogcFilterWriter = new OgcFilterWriter(); + const filterOrBox = ogcFilterWriter.buildFilter(igoFilters, extent, proj, ogcFilters.geometryName); + let filterOrPush = ogcFilterWriter.handleOgcFiltersAppliedValue(this.options, ogcFilters.geometryName); + + let prefix = 'filter'; + if (!filterOrPush) { + prefix = 'bbox'; + filterOrPush = extent.join(',') + ',' + proj.getCode(); + } + + paramsWFS.xmlFilter = ogcFilters.advancedOgcFilters ? filterOrBox : `${prefix}=${filterOrPush}`; + let baseUrl = queryStringValues.find(f => f.name === 'getfeature').value; + const patternFilter = /(filter|bbox)=.*/gi; + baseUrl = patternFilter.test(paramsWFS.xmlFilter) ? `${baseUrl}&${paramsWFS.xmlFilter}` : baseUrl; + this.options.download = Object.assign({}, this.options.download, { dynamicUrl: baseUrl }); + return baseUrl.replace(/&&/g, '&'); + } + private getFormatFromOptions() { let olFormatCls; - const outputFormat = this.options.paramsWFS.outputFormat.toLowerCase(); - const patternGml3 = new RegExp('.*?gml.*?'); - const patternGeojson = new RegExp('.*?json.*?'); + let outputFormat; + if (this.options.paramsWFS.outputFormat) { + outputFormat = this.options.paramsWFS.outputFormat.toLowerCase(); + } - if (patternGeojson.test(outputFormat)) { + if (jsonRegex.test(outputFormat)) { olFormatCls = OlFormat.GeoJSON; } - if (patternGml3.test(outputFormat)) { + if (gmlRegex.test(outputFormat) || !outputFormat) { olFormatCls = OlFormat.WFS; } diff --git a/packages/geo/src/lib/datasource/shared/datasources/wfs.service.ts b/packages/geo/src/lib/datasource/shared/datasources/wfs.service.ts index 0d59c62bf8..0067995696 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/wfs.service.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/wfs.service.ts @@ -7,6 +7,7 @@ import * as olformat from 'ol/format'; import { WFSDataSourceOptions } from './wfs-datasource.interface'; import { DataService } from './data.service'; +import { formatWFSQueryString, gmlRegex, defaultEpsg, defaultMaxFeatures} from './wms-wfs.utils'; @Injectable({ providedIn: 'root' @@ -22,199 +23,40 @@ export class WFSService extends DataService { } public getSourceFieldsFromWFS(datasource) { - if ( - datasource.sourceFields === undefined || - Object.keys(datasource.sourceFields).length === 0 - ) { + if (!datasource.sourceFields || datasource.sourceFields.length === 0 ) { datasource.sourceFields = []; - this.wfsGetCapabilities(datasource).subscribe(wfsCapabilities => { - datasource.paramsWFS.wfsCapabilities = { - xmlBody: wfsCapabilities.body, - GetPropertyValue: /GetPropertyValue/gi.test(wfsCapabilities.body) - ? true - : false - }; - - this.defineFieldAndValuefromWFS(datasource).subscribe(sourceFields => { - datasource.sourceFields = sourceFields; - }); - }); - } else { - datasource.sourceFields.forEach(sourcefield => { - if (sourcefield.alias === undefined) { - sourcefield.alias = sourcefield.name; // to allow only a list of sourcefield with names - } + this.defineFieldAndValuefromWFS(datasource).subscribe(getfeatureSourceField => { + datasource.sourceFields = getfeatureSourceField; }); - datasource.sourceFields - .filter( - field => field.values === undefined || field.values.length === 0 - ) - .forEach(f => { - this.getValueFromWfsGetPropertyValues( - datasource, - f.name, - 200, - 0, - 0 - ).subscribe(rep => (f.values = rep)); + } else { + this.defineFieldAndValuefromWFS(datasource).subscribe(getfeatureSourceField => { + datasource.sourceFields.forEach(sourcefield => { + if (sourcefield.alias === undefined) { + sourcefield.alias = sourcefield.name; // to allow only a list of sourcefield with names + } + if (sourcefield.values === undefined || sourcefield.values.length === 0) { + sourcefield.values = getfeatureSourceField.find(sf => sf.name === sourcefield.name).values + } }); + }); } } - public checkWfsOptions(wfsDataSourceOptions) { - // Look at https://github.com/openlayers/openlayers/pull/6400 - const patternGml = new RegExp(/.*?gml.*?/gi); - - if (patternGml.test(wfsDataSourceOptions.paramsWFS.outputFormat)) { - wfsDataSourceOptions.paramsWFS.version = '1.1.0'; - } - return Object.assign({}, wfsDataSourceOptions, { - wfsCapabilities: { xmlBody: '', GetPropertyValue: false } - }); - } - - public buildBaseWfsUrl( - wfsDataSourceOptions: WFSDataSourceOptions, - wfsQuery: string - ): string { - let paramTypename = 'typename'; - if ( - wfsDataSourceOptions.paramsWFS.version === '2.0.0' || - !wfsDataSourceOptions.paramsWFS.version - ) { - paramTypename = 'typenames'; - } - const baseWfsQuery = 'service=wfs&request=' + wfsQuery; - const wfsTypeName = - paramTypename + '=' + wfsDataSourceOptions.paramsWFS.featureTypes; - const wfsVersion = wfsDataSourceOptions.paramsWFS.version - ? 'version=' + wfsDataSourceOptions.paramsWFS.version - : 'version=' + '2.0.0'; - - return `${ - wfsDataSourceOptions.urlWfs - }?${baseWfsQuery}&${wfsVersion}&${wfsTypeName}`; - } - - public wfsGetFeature( + private wfsGetFeature( wfsDataSourceOptions: WFSDataSourceOptions, - nb = 5000, - epsgCode = 3857, - propertyname = '' + nb: number = defaultMaxFeatures, + epsgCode: string = defaultEpsg, + propertyName?: string ): Observable { - const baseUrl = this.buildBaseWfsUrl(wfsDataSourceOptions, 'GetFeature'); - const outputFormat = wfsDataSourceOptions.paramsWFS.outputFormat - ? 'outputFormat=' + wfsDataSourceOptions.paramsWFS.outputFormat - : ''; - const srsname = wfsDataSourceOptions.paramsWFS.srsName - ? 'srsname=' + wfsDataSourceOptions.paramsWFS.srsName - : 'srsname=EPSG:' + epsgCode; - const wfspropertyname = - propertyname === '' ? propertyname : '&propertyname=' + propertyname; - let paramMaxFeatures = 'maxFeatures'; - if ( - wfsDataSourceOptions.paramsWFS.version === '2.0.0' || - !wfsDataSourceOptions.paramsWFS.version - ) { - paramMaxFeatures = 'count'; - } - - let maxFeatures; - if (nb !== 5000) { - maxFeatures = paramMaxFeatures + '=' + nb; + const queryStringValues = formatWFSQueryString(wfsDataSourceOptions, nb, epsgCode, propertyName); + const baseUrl = queryStringValues.find(f => f.name === 'getfeature').value; + const outputFormat = wfsDataSourceOptions.paramsWFS.outputFormat; + if (gmlRegex.test(outputFormat) || !outputFormat) { + return this.http.get(baseUrl, { responseType: 'text' }); } else { - maxFeatures = wfsDataSourceOptions.paramsWFS.maxFeatures - ? paramMaxFeatures + '=' + wfsDataSourceOptions.paramsWFS.maxFeatures - : paramMaxFeatures + '=' + nb; + return this.http.get(baseUrl); } - const urlWfs = `${baseUrl}&${outputFormat}&${srsname}&${maxFeatures}${wfspropertyname}`; - const patternGml = new RegExp('.*?gml.*?'); - if ( - patternGml.test(wfsDataSourceOptions.paramsWFS.outputFormat.toLowerCase()) - ) { - return this.http.get(urlWfs, { responseType: 'text' }); - } else { - return this.http.get(urlWfs); - } - } - - public getValueFromWfsGetPropertyValues( - wfsDataSourceOptions: WFSDataSourceOptions, - field, - maxFeatures = 30, - startIndex = 0, - retry = 0 - ): Observable { - return new Observable(d => { - const nbRetry = 2; - const valueList = []; - - this.wfsGetPropertyValue( - wfsDataSourceOptions, - field, - maxFeatures, - startIndex - ).subscribe( - str => { - str = str.replace(/'/gi, "'"); // tslint:disable-line - const regexExcp = /exception/gi; - if (regexExcp.test(str)) { - retry++; - if (retry < nbRetry) { - this.getValueFromWfsGetPropertyValues( - wfsDataSourceOptions, - field, - maxFeatures, - startIndex, - retry - ).subscribe(rep => d.next(rep)); - } - } else { - const valueReferenceRegex = new RegExp( - '<(.+?)' + field + '>(.+?)', - 'gi' - ); - let n = valueReferenceRegex.exec(str); - while (n !== null) { - if (n.index === valueReferenceRegex.lastIndex) { - valueReferenceRegex.lastIndex++; - } - if (valueList.indexOf(n[2]) === -1) { - valueList.push(n[2]); - } - n = valueReferenceRegex.exec(str); - } - d.next(valueList); - d.complete(); - } - }, - err => { - if (retry < nbRetry) { - retry++; - this.getValueFromWfsGetPropertyValues( - wfsDataSourceOptions, - field, - maxFeatures, - startIndex, - retry - ).subscribe(rep => d.next(rep)); - } - } - ); - }); - } - - wfsGetCapabilities(options): Observable { - const baseWfsQuery = 'service=wfs&request=GetCapabilities'; - const wfsVersion = options.version - ? 'version=' + options.version - : 'version=' + '2.0.0'; - const wfsGcUrl = `${options.urlWfs}?${baseWfsQuery}&${wfsVersion}`; - return this.http.get(wfsGcUrl, { - observe: 'response', - responseType: 'text' - }); } defineFieldAndValuefromWFS( @@ -226,87 +68,42 @@ export class WFSService extends DataService { let fieldListWoGeom; let fieldListWoGeomStr; let olFormats; - const patternGml3 = /gml/gi; - if (wfsDataSourceOptions.paramsWFS.outputFormat.match(patternGml3)) { + const outputFormat = wfsDataSourceOptions.paramsWFS.outputFormat; + + if (gmlRegex.test(outputFormat) || !outputFormat) { olFormats = olformat.WFS; - } else { + } else { olFormats = olformat.GeoJSON; } - if (wfsDataSourceOptions.paramsWFS.wfsCapabilities.GetPropertyValue) { - this.wfsGetFeature(wfsDataSourceOptions, 1).subscribe(oneFeature => { - const features = new olFormats().readFeatures(oneFeature); - fieldList = features[0].getKeys(); - fieldListWoGeom = fieldList.filter( - field => - field !== features[0].getGeometryName() && - !field.match(/boundedby/gi) - ); - fieldListWoGeomStr = fieldListWoGeom.join(','); - fieldListWoGeom.forEach(element => { - const fieldType = - typeof features[0].get(element) === 'object' - ? undefined - : typeof features[0].get(element); - this.getValueFromWfsGetPropertyValues( - wfsDataSourceOptions, - element, - 200 - ).subscribe(valueList => { - sourceFields.push({ - name: element, - alias: element, - values: valueList - }); - d.next(sourceFields); - }); - }); - }); - } else { - this.wfsGetFeature(wfsDataSourceOptions, 1).subscribe(oneFeature => { - const features = new olFormats().readFeatures(oneFeature); - fieldList = features[0].getKeys(); - fieldListWoGeom = fieldList.filter( - field => - field !== features[0].getGeometryName() && - !field.match(/boundedby/gi) - ); - fieldListWoGeomStr = fieldListWoGeom.join(','); - this.wfsGetFeature( - wfsDataSourceOptions, - 200, - 3857, - fieldListWoGeomStr - ).subscribe(manyFeatures => { - const mfeatures = new olFormats().readFeatures(manyFeatures); - this.built_properties_value(mfeatures).forEach(element => { - sourceFields.push(element); - }); - d.next(sourceFields); - d.complete(); + + this.wfsGetFeature(wfsDataSourceOptions, 1).subscribe(oneFeature => { + const features = new olFormats().readFeatures(oneFeature); + fieldList = features[0].getKeys(); + fieldListWoGeom = fieldList.filter( + field => + field !== features[0].getGeometryName() && + !field.match(/boundedby/gi) + ); + fieldListWoGeomStr = fieldListWoGeom.join(','); + this.wfsGetFeature( + wfsDataSourceOptions, + wfsDataSourceOptions.paramsWFS.maxFeatures || defaultMaxFeatures, + undefined, + fieldListWoGeomStr + ).subscribe(manyFeatures => { + const mfeatures = new olFormats().readFeatures(manyFeatures); + this.built_properties_value(mfeatures).forEach(element => { + sourceFields.push(element); }); + d.next(sourceFields); + d.complete(); }); - } + }); + }); } - public wfsGetPropertyValue( - wfsDataSourceOptions: WFSDataSourceOptions, - field, - maxFeatures = 30, - startIndex = 0 - ): Observable { - const baseWfsQuery = - 'service=wfs&request=GetPropertyValue&count=' + maxFeatures; - const wfsTypeName = - 'typenames=' + wfsDataSourceOptions.paramsWFS.featureTypes; - const wfsValueReference = 'valueReference=' + field; - const wfsVersion = 'version=' + '2.0.0'; - const gfvUrl = `${ - wfsDataSourceOptions.urlWfs - }?${baseWfsQuery}&${wfsVersion}&${wfsTypeName}&${wfsValueReference}`; - return this.http.get(gfvUrl, { responseType: 'text' }); - } private built_properties_value(features: olFeature[]): string[] { const kv = Object.assign({}, features[0].getProperties()); diff --git a/packages/geo/src/lib/datasource/shared/datasources/wms-datasource.ts b/packages/geo/src/lib/datasource/shared/datasources/wms-datasource.ts index 033ee3bcd0..3ca5018b97 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/wms-datasource.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/wms-datasource.ts @@ -8,10 +8,12 @@ import { WFSService } from './wfs.service'; import { OgcFilterWriter } from '../../../filter/shared/ogc-filter'; import { OgcFilterableDataSourceOptions } from '../../../filter/shared/ogc-filter.interface'; import { QueryHtmlTarget } from '../../../query/shared/query.enums'; +import { formatWFSQueryString, defaultWfsVersion, checkWfsParams, defaultFieldNameGeometry } from './wms-wfs.utils'; + +import { ObjectUtils } from '@igo2/utils'; export class WMSDataSource extends DataSource { public ol: olSourceImageWMS; - public ogcFilterWriter: OgcFilterWriter; get params(): any { return this.options.params as any; @@ -64,98 +66,62 @@ export class WMSDataSource extends DataSource { }, options.refreshIntervalSec * 1000); // Convert seconds to MS } + let fieldNameGeometry = defaultFieldNameGeometry; + // #### START if paramsWFS if (options.paramsWFS) { - const wfsCheckup = this.wfsService.checkWfsOptions(options); - options.paramsWFS.version = wfsCheckup.paramsWFS.version; - options.paramsWFS.wfsCapabilities = wfsCheckup.params.wfsCapabilities; + const wfsCheckup = checkWfsParams(options, 'wms'); + ObjectUtils.mergeDeep(options.paramsWFS, wfsCheckup.paramsWFS); - this.wfsService.getSourceFieldsFromWFS(options); + fieldNameGeometry = options.paramsWFS.fieldNameGeometry || fieldNameGeometry; - this.options.download = Object.assign({}, this.options.download, { - dynamicUrl: this.buildDynamicDownloadUrlFromParamsWFS(this.options) + options.download = Object.assign({}, options.download, { + dynamicUrl: this.buildDynamicDownloadUrlFromParamsWFS(options) }); } // #### END if paramsWFS - this.ogcFilterWriter = new OgcFilterWriter(); if (!options.sourceFields || options.sourceFields.length === 0) { options.sourceFields = []; + } else { + options.sourceFields.forEach(sourceField => { + sourceField.alias = sourceField.alias ? sourceField.alias : sourceField.name + // to allow only a list of sourcefield with names + }); } - const initOgcFilters = (this.options as OgcFilterableDataSourceOptions).ogcFilters; - if (sourceParams.layers.split(',').length > 1 && this.options && initOgcFilters && initOgcFilters.enabled) { + const initOgcFilters = (options as OgcFilterableDataSourceOptions).ogcFilters; + const ogcFilterWriter = new OgcFilterWriter(); + + if (!initOgcFilters) { + (options as OgcFilterableDataSourceOptions).ogcFilters = + ogcFilterWriter.defineOgcFiltersDefaultOptions(initOgcFilters, fieldNameGeometry, 'wms'); + } else { + initOgcFilters.advancedOgcFilters = initOgcFilters.pushButtons ? false : true; + } + if (sourceParams.layers.split(',').length > 1 && options && initOgcFilters.enabled) { console.log('*******************************'); console.log('BE CAREFULL, YOUR WMS LAYERS (' + sourceParams.layers + ') MUST SHARE THE SAME FIELDS TO ALLOW ogcFilters TO WORK !! '); console.log('*******************************'); } - if (this.options && initOgcFilters && initOgcFilters.enabled && initOgcFilters.filters) { - const filters = initOgcFilters.filters; - const rebuildFilter = this.ogcFilterWriter.buildFilter(filters); - const appliedFilter = this.formatProcessedOgcFilter(rebuildFilter, sourceParams.layers); - const wmsFilterValue = appliedFilter.length > 0 - ? appliedFilter.replace('filter=', '') - : undefined; - this.ol.updateParams({ filter: wmsFilterValue }); - } + if (options.paramsWFS && initOgcFilters.enabled) { + this.wfsService.getSourceFieldsFromWFS(options); + } + const filterQueryString = ogcFilterWriter.handleOgcFiltersAppliedValue(options, fieldNameGeometry); + this.ol.updateParams({ filter: filterQueryString }); } refresh() { this.ol.updateParams({ igoRefresh: Math.random() }); } - public formatProcessedOgcFilter(processedFilter, layers): string { - let appliedFilter = ''; - if (processedFilter.length === 0 && layers.indexOf(',') === -1) { - appliedFilter = processedFilter; - } else { - layers.split(',').forEach(layerName => { - appliedFilter = `${appliedFilter}(${processedFilter.replace('filter=', '')})`; - }); - } - return `filter=${appliedFilter}`; - } - private buildDynamicDownloadUrlFromParamsWFS(asWFSDataSourceOptions) { - const outputFormat = - asWFSDataSourceOptions.paramsWFS.outputFormat !== undefined - ? 'outputFormat=' + asWFSDataSourceOptions.paramsWFS.outputFormat - : ''; - - let paramMaxFeatures = 'maxFeatures'; - if ( - asWFSDataSourceOptions.paramsWFS.version === '2.0.0' || - !asWFSDataSourceOptions.paramsWFS.version - ) { - paramMaxFeatures = 'count'; - } - const maxFeatures = asWFSDataSourceOptions.paramsWFS.maxFeatures - ? paramMaxFeatures + '=' + asWFSDataSourceOptions.paramsWFS.maxFeatures - : paramMaxFeatures + '=5000'; - const srsname = asWFSDataSourceOptions.paramsWFS.srsName - ? 'srsname=' + asWFSDataSourceOptions.paramsWFS.srsName - : 'srsname=EPSG:3857'; - const baseWfsQuery = this.wfsService.buildBaseWfsUrl( - asWFSDataSourceOptions, - 'GetFeature' - ); - return `${baseWfsQuery}&${outputFormat}&${srsname}&${maxFeatures}`; + const queryStringValues = formatWFSQueryString(asWFSDataSourceOptions); + const downloadUrl = queryStringValues.find(f => f.name === 'getfeature').value; + return downloadUrl; } protected createOlSource(): olSourceImageWMS { - if (this.options.paramsWFS) { - this.options.urlWfs = this.options.urlWfs - ? this.options.urlWfs - : this.options.url; - this.options.paramsWFS.version = this.options.paramsWFS.version - ? this.options.paramsWFS.version - : '2.0.0'; - } - let initOgcFilters = (this.options as OgcFilterableDataSourceOptions).ogcFilters; - const ogcFiltersDefaultValue = false; // default values for wms. - initOgcFilters = initOgcFilters === undefined ? {} : initOgcFilters; - initOgcFilters.enabled = initOgcFilters.enabled === undefined ? ogcFiltersDefaultValue : initOgcFilters.enabled; - initOgcFilters.editable = initOgcFilters.editable === undefined ? ogcFiltersDefaultValue : initOgcFilters.editable; return new olSourceImageWMS(this.options); } diff --git a/packages/geo/src/lib/datasource/shared/datasources/wms-wfs.utils.ts b/packages/geo/src/lib/datasource/shared/datasources/wms-wfs.utils.ts new file mode 100644 index 0000000000..61feebd164 --- /dev/null +++ b/packages/geo/src/lib/datasource/shared/datasources/wms-wfs.utils.ts @@ -0,0 +1,107 @@ +import { WFSDataSourceOptions } from './wfs-datasource.interface'; +import { OgcFilterWriter } from '../../../filter/shared/ogc-filter'; +import { OgcFilterableDataSourceOptions } from '../../../filter/shared/ogc-filter.interface'; + +export const defaultEpsg = 'EPSG:3857'; +export const defaultMaxFeatures = 5000; +export const defaultWfsVersion = '2.0.0'; +export const defaultFieldNameGeometry = 'geometry'; +export const gmlRegex = new RegExp(/.*?gml.*?/gi); +export const jsonRegex = new RegExp(/.*?json.*?/gi); + +/** + * This method build/standardize WFS call query params based on the layer property. + * @param wfsDataSourceOptions WFSDataSourceOptions The common wfs datasource options interface + * @param count Number: Used to control the number of feature. Used to bypass whe wfs datasource options interface (maxFeatures) + * @param epsg String: Used to control the EPSG code (es: 'EPSG3857'). Used to bypass whe wfs datasource options interface (srsName) + * @param properties String: Used to control the queried fields (WFS service). + * @returns An array array of {name: '', value: ''} of predefined query params. + */ +export function formatWFSQueryString( + wfsDataSourceOptions: WFSDataSourceOptions, + count?: number, + epsg?: string, + properties?: string): { name: string, value: string }[] { + + const versionWfs200 = '2.0.0'; // not the same usage as defaultWfsVersion. + const url = wfsDataSourceOptions.urlWfs; + const paramsWFS = wfsDataSourceOptions.paramsWFS; + const effectiveCount = count || defaultMaxFeatures; + const epsgCode = epsg || defaultEpsg; + const outputFormat = paramsWFS.outputFormat ? `outputFormat=${paramsWFS.outputFormat}` : ''; + const version = paramsWFS.version ? `version=${paramsWFS.version}` : `version=${defaultWfsVersion}`; + const paramTypename = paramsWFS.version === versionWfs200 ? 'typenames' : 'typename'; + const featureTypes = `${paramTypename}=${paramsWFS.featureTypes}`; + const paramMaxFeatures = paramsWFS.version === versionWfs200 ? 'count' : 'maxFeatures'; + const cnt = count ? `${paramMaxFeatures}=${effectiveCount}` : + paramsWFS.maxFeatures ? `${paramMaxFeatures}=${paramsWFS.maxFeatures}` : `${paramMaxFeatures}=${effectiveCount}`; + const srs = epsg ? `srsname=${epsgCode}` : paramsWFS.srsName ? 'srsname=' + paramsWFS.srsName : `srsname=${epsgCode}`; + + let propertyName = ''; + let valueReference = ''; + if (properties) { + propertyName = `propertyName=${properties}`; + valueReference = `valueReference=${properties}`; + } + const sourceFields = wfsDataSourceOptions.sourceFields; + if (!propertyName && sourceFields && sourceFields.length > 0) { + const fieldsNames = []; + wfsDataSourceOptions.sourceFields.forEach(sourcefield => { + fieldsNames.push(sourcefield.name); + }); + propertyName = `propertyName=${fieldsNames.join(',')},${paramsWFS.fieldNameGeometry}`; + } + + const getCapabilities = `${url}?service=wfs&request=GetCapabilities&${version}`; + let getFeature = `${url}?service=wfs&request=GetFeature&${version}&${featureTypes}&`; + getFeature += `${outputFormat}&${srs}&${cnt}&${propertyName}`; + + let getpropertyvalue = `${url}?service=wfs&request=GetPropertyValue&version=${versionWfs200}&${featureTypes}&`; + getpropertyvalue += `&${cnt}&${valueReference}`; + + return [ + { name: 'outputformat', value: outputFormat }, + { name: 'version', value: version }, + { name: 'typename', value: featureTypes }, + { name: 'count', value: cnt }, + { name: 'srsname', value: srs }, + { name: 'propertyname', value: propertyName }, + { name: 'valuereference', value: valueReference }, + { name: 'getcapabilities', value: getCapabilities.replace(/&&/g, '&') }, + { name: 'getfeature', value: getFeature.replace(/&&/g, '&') }, + { name: 'getpropertyvalue', value: getpropertyvalue.replace(/&&/g, '&') } + ]; +} + +/** + * Validate/Modify layer's wfs options based on : + * 1- an Openlayers's issue with GML provided from WFS. Refer to + * https://github.com/openlayers/openlayers/pull/6400 + * 2- Set default values for optionals parameters. + * @param wfsDataSourceOptions WFSDataSourceOptions The common wfs datasource options interface + * @returns An array array of {name: '', value: ''} of predefined query params. + */ +export function checkWfsParams(wfsDataSourceOptions, srcType?: string) { + + if (srcType && srcType === 'wfs') { + // reassignation of params to paramsWFS and url to urlWFS to have a common interface with wms-wfs datasources + wfsDataSourceOptions.paramsWFS = wfsDataSourceOptions.params; + } + + const paramsWFS = wfsDataSourceOptions.paramsWFS; + wfsDataSourceOptions.urlWfs = wfsDataSourceOptions.urlWfs || wfsDataSourceOptions.url; + + paramsWFS.version = paramsWFS.version || defaultWfsVersion; + paramsWFS.fieldNameGeometry = paramsWFS.fieldNameGeometry || defaultFieldNameGeometry; + paramsWFS.maxFeatures = paramsWFS.maxFeatures || defaultMaxFeatures; + + let outputFormat; + if (paramsWFS.outputFormat) { + outputFormat = paramsWFS.outputFormat; + } + + if (gmlRegex.test(outputFormat) || !outputFormat) { + paramsWFS.version = '1.1.0'; + } + return Object.assign({}, wfsDataSourceOptions ); +} diff --git a/packages/geo/src/lib/download/shared/download.service.ts b/packages/geo/src/lib/download/shared/download.service.ts index f7f86534b6..a8eb9f578d 100644 --- a/packages/geo/src/lib/download/shared/download.service.ts +++ b/packages/geo/src/lib/download/shared/download.service.ts @@ -5,7 +5,7 @@ import olProjection from 'ol/proj/Projection'; import { MessageService, LanguageService } from '@igo2/core'; import { Layer } from '../../layer/shared'; -import { OgcFilterWriter } from '../../filter/shared'; +import { OgcFilterWriter, OgcFilterableDataSourceOptions } from '../../filter/shared'; import { DataSourceOptions } from '../../datasource/shared/datasources/datasource.interface'; @@ -13,14 +13,11 @@ import { DataSourceOptions } from '../../datasource/shared/datasources/datasourc providedIn: 'root' }) export class DownloadService { - private ogcFilterWriter: OgcFilterWriter; constructor( private messageService: MessageService, private languageService: LanguageService - ) { - this.ogcFilterWriter = new OgcFilterWriter(); - } + ) {} open(layer: Layer) { const translate = this.languageService.translate; @@ -56,14 +53,24 @@ export class DownloadService { .replace(/&?filter=[^&]*/gi, '') .replace(/&?bbox=[^&]*/gi, ''); - const rebuildFilter = this.ogcFilterWriter.buildFilter( - (layer.dataSource.options as any).ogcFilters.filters, - layer.map.getExtent(), - new olProjection({ code: layer.map.projection }), - wfsOptions.fieldNameGeometry - ); + const ogcFilters = (layer.dataSource.options as OgcFilterableDataSourceOptions).ogcFilters; + + let filterQueryString; + filterQueryString = new OgcFilterWriter() + .handleOgcFiltersAppliedValue(layer.dataSource.options, ogcFilters.geometryName); + if (!filterQueryString) { + // Prevent getting all the features for empty filter + filterQueryString = new OgcFilterWriter().buildFilter( + undefined, + layer.map.getExtent(), + new olProjection({ code: layer.map.projection }), + ogcFilters.geometryName + ); + } else { + filterQueryString = 'filter=' + filterQueryString; + } window.open( - `${baseurl}&${rebuildFilter}&${outputFormatDownload}`, + `${baseurl}&${filterQueryString}&${outputFormatDownload}`, '_blank' ); } else if (DSOptions.download) { diff --git a/packages/geo/src/lib/filter/filter.module.ts b/packages/geo/src/lib/filter/filter.module.ts index 02308ac0eb..69e3d2bc25 100644 --- a/packages/geo/src/lib/filter/filter.module.ts +++ b/packages/geo/src/lib/filter/filter.module.ts @@ -5,6 +5,7 @@ import { MatAutocompleteModule, MatIconModule, MatButtonModule, + MatButtonToggleModule, MatSliderModule, MatSlideToggleModule, MatFormFieldModule, @@ -15,7 +16,8 @@ import { MatTooltipModule, MatDatepickerModule, MatNativeDateModule, - MAT_DATE_LOCALE + MAT_DATE_LOCALE, + MatCheckboxModule } from '@angular/material'; // import { @@ -44,6 +46,7 @@ import { OgcFilterableListBindingDirective } from './ogc-filterable-list/ogc-fil import { OgcFilterableListComponent } from './ogc-filterable-list/ogc-filterable-list.component'; import { OgcFilterButtonComponent } from './ogc-filter-button/ogc-filter-button.component'; import { OGCFilterService } from './shared/ogc-filter.service'; +import { OgcFilterToggleButtonComponent } from './ogc-filter-toggle-button/ogc-filter-toggle-button.component'; @NgModule({ imports: [ @@ -53,6 +56,8 @@ import { OGCFilterService } from './shared/ogc-filter.service'; MatAutocompleteModule, MatIconModule, MatButtonModule, + MatButtonToggleModule, + MatCheckboxModule, MatSliderModule, MatSlideToggleModule, MatFormFieldModule, @@ -78,6 +83,7 @@ import { OGCFilterService } from './shared/ogc-filter.service'; TimeFilterListBindingDirective, OgcFilterFormComponent, OgcFilterButtonComponent, + OgcFilterToggleButtonComponent, OgcFilterableFormComponent, OgcFilterableItemComponent, OgcFilterableListComponent, @@ -91,6 +97,7 @@ import { OGCFilterService } from './shared/ogc-filter.service'; TimeFilterListBindingDirective, OgcFilterFormComponent, OgcFilterButtonComponent, + OgcFilterToggleButtonComponent, OgcFilterableFormComponent, OgcFilterableItemComponent, OgcFilterableListComponent, diff --git a/packages/geo/src/lib/filter/index.ts b/packages/geo/src/lib/filter/index.ts index 96861e22e9..a7d1dea03d 100644 --- a/packages/geo/src/lib/filter/index.ts +++ b/packages/geo/src/lib/filter/index.ts @@ -6,4 +6,5 @@ export * from './ogc-filterable-form'; export * from './ogc-filterable-item'; export * from './ogc-filterable-list'; export * from './ogc-filter-form'; +export * from './ogc-filter-toggle-button'; export * from './ogc-filter-button'; diff --git a/packages/geo/src/lib/filter/ogc-filter-button/ogc-filter-button.component.html b/packages/geo/src/lib/filter/ogc-filter-button/ogc-filter-button.component.html index 61fa4d090d..03d18ddb8d 100644 --- a/packages/geo/src/lib/filter/ogc-filter-button/ogc-filter-button.component.html +++ b/packages/geo/src/lib/filter/ogc-filter-button/ogc-filter-button.component.html @@ -8,9 +8,7 @@ [color]="color" (click)="toggleOgcFilter()"> - filter_list - + [ngClass]='{disabled: !layer.isInResolutionsRange}'svgIcon="filter">
+ (selectionChange)="updateField()"> {{field.alias}} @@ -31,7 +31,7 @@
- {{('igo.geo.operators.'+ operator.key) | translate}} + {{('igo.geo.operators.'+ operator.key) | translate}}
@@ -165,7 +165,7 @@
- {{('igo.geo.operators.'+ operator.key) | translate}} + {{('igo.geo.operators.'+ operator.key) | translate}}
@@ -178,7 +178,7 @@ diff --git a/packages/geo/src/lib/filter/ogc-filter-form/ogc-filter-form.component.ts b/packages/geo/src/lib/filter/ogc-filter-form/ogc-filter-form.component.ts index 3afc0ecf2f..422796787e 100644 --- a/packages/geo/src/lib/filter/ogc-filter-form/ogc-filter-form.component.ts +++ b/packages/geo/src/lib/filter/ogc-filter-form/ogc-filter-form.component.ts @@ -1,12 +1,9 @@ import { Component, Input, - ChangeDetectorRef, - AfterContentChecked + OnInit } from '@angular/core'; -import * as olstyle from 'ol/style'; - import { OgcInterfaceFilterOptions, OgcFilterableDataSource, @@ -15,17 +12,15 @@ import { import { OgcFilterWriter } from '../../filter/shared/ogc-filter'; import { WktService } from '../../wkt/shared/wkt.service'; import { IgoMap } from '../../map'; +import { OgcFilterOperatorType } from '../../filter/shared/ogc-filter.enum'; @Component({ selector: 'igo-ogc-filter-form', templateUrl: './ogc-filter-form.component.html', styleUrls: ['./ogc-filter-form.component.scss'] }) -export class OgcFilterFormComponent implements AfterContentChecked { - private ogcFilterWriter: OgcFilterWriter; - private _dataSource: OgcFilterableDataSource; - private _currentFilter: any = {}; - public operators; +export class OgcFilterFormComponent implements OnInit { + public ogcFilterOperators; public igoSpatialSelectors; public value = ''; public inputOperator; @@ -34,45 +29,15 @@ export class OgcFilterFormComponent implements AfterContentChecked { public color = 'primary'; public snrc = ''; public disabled; - private _map: IgoMap; public baseOverlayName = 'ogcFilterOverlay_'; - private _showFeatureOnMap: boolean; - // tslint:disable-next-line:ban-types - @Input() refreshFilters: Function; + @Input() refreshFilters: () => void; - @Input() - get datasource(): OgcFilterableDataSource { - return this._dataSource; - } - set datasource(value: OgcFilterableDataSource) { - this._dataSource = value; - this.cdRef.detectChanges(); - } + @Input() datasource: OgcFilterableDataSource; - @Input() - get showFeatureOnMap(): boolean { - return this._showFeatureOnMap; - } - set showFeatureOnMap(value: boolean) { - this._showFeatureOnMap = value; - } - - @Input() - get map(): IgoMap { - return this._map; - } - set map(value: IgoMap) { - this._map = value; - } + @Input() map: IgoMap; - @Input() - get currentFilter(): any { - return this._currentFilter; - } - set currentFilter(value: any) { - this._currentFilter = value; - } + @Input() currentFilter: any; get activeFilters() { this.updateField(); @@ -82,15 +47,13 @@ export class OgcFilterFormComponent implements AfterContentChecked { } constructor( - private cdRef: ChangeDetectorRef, private wktService: WktService ) { - this.ogcFilterWriter = new OgcFilterWriter(); // TODO: Filter permitted operator based on wfscapabilities // Need to work on regex on XML capabilities because // comaparison operator's name varies between WFS servers... // Ex: IsNull vs PropertyIsNull vs IsNil ... - this.operators = this.ogcFilterWriter.operators; + this.ogcFilterOperators = new OgcFilterWriter().operators; this.igoSpatialSelectors = [ { type: 'fixedExtent' @@ -102,79 +65,79 @@ export class OgcFilterFormComponent implements AfterContentChecked { // TODO: selectFeature & drawFeature } - ngAfterContentChecked() { - if (this.map) { - this.activeFilters - .filter( - af => ['Contains', 'Intersects', 'Within'].indexOf(af.operator) !== -1 - ) - .forEach(activeFilterSpatial => { - if (activeFilterSpatial.wkt_geometry) { - this.addWktAsOverlay( - activeFilterSpatial.wkt_geometry, - activeFilterSpatial.filterid, - this.map.projection - ); - } - }); + ngOnInit() { + this.computeAllowedOperators(); + } + + computeAllowedOperators() { + let allowedOperators = this.datasource.options.ogcFilters.allowedOperatorsType; + let effectiveOperators: {} = {}; + + if (!allowedOperators) { + allowedOperators = OgcFilterOperatorType.BasicAndSpatial; + } + + switch (allowedOperators.toLowerCase()) { + case 'all': + effectiveOperators = this.ogcFilterOperators; + break; + case 'spatial': + effectiveOperators = { + Intersects: { spatial: true, fieldRestrict: [] }, + Within: { spatial: true, fieldRestrict: [] }, + }; + break; + case 'basicandspatial': + effectiveOperators = { + PropertyIsEqualTo: { spatial: false, fieldRestrict: [] }, + PropertyIsNotEqualTo: { spatial: false, fieldRestrict: [] }, + Intersects: { spatial: true, fieldRestrict: [] }, + Within: { spatial: true, fieldRestrict: [] }, + }; + break; + case 'basic': + effectiveOperators = { + PropertyIsEqualTo: { spatial: false, fieldRestrict: [] }, + PropertyIsNotEqualTo: { spatial: false, fieldRestrict: [] } + }; + break; + case 'basicnumeric': + effectiveOperators = { + PropertyIsEqualTo: { spatial: false, fieldRestrict: [] }, + PropertyIsNotEqualTo: { spatial: false, fieldRestrict: [] }, + PropertyIsGreaterThan: { spatial: false, fieldRestrict: ['number'] }, + PropertyIsGreaterThanOrEqualTo: { spatial: false, fieldRestrict: ['number'] }, + PropertyIsLessThan: { spatial: false, fieldRestrict: ['number'] }, + PropertyIsLessThanOrEqualTo: { spatial: false, fieldRestrict: ['number'] }, + }; + break; + default: + effectiveOperators = { + PropertyIsEqualTo: { spatial: false, fieldRestrict: [] }, + PropertyIsNotEqualTo: { spatial: false, fieldRestrict: [] }, + Intersects: { spatial: true, fieldRestrict: [] }, + Within: { spatial: true, fieldRestrict: [] }, + }; } + + this.ogcFilterOperators = effectiveOperators; } - updateField(init = true) { + updateField() { if (!this.datasource.options.sourceFields) { return; } - this.fields = this.datasource.options.sourceFields.sort((a, b) => { - if (a.name < b.name) { - return -1; - } else if (a.name > b.name) { - return 1; - } else { - return 0; - } - }); - this.datasource.options.sourceFields - .filter(f => f.name === this.currentFilter.propertyName) + this.fields = this.datasource.options.sourceFields + .filter(sf => (sf.excludeFromOgcFilters === undefined || !sf.excludeFromOgcFilters)); + this.fields.filter(f => f.name === this.currentFilter.propertyName) .forEach(element => { this.values = element.values !== undefined ? element.values.sort() : []; }); } - private addWktAsOverlay(wkt, filterid, projection) { - const wktAsFeature = this.wktService.wktToFeature(wkt, projection); - wktAsFeature.setId(this.baseOverlayName + filterid); - let opacity = 0; - if (this.showFeatureOnMap) { - opacity = 0.5; - } - - const stroke = new olstyle.Stroke({ - width: 2, - color: [125, 136, 140, opacity] - }); - - return new olstyle.Style({ - stroke, - image: new olstyle.Circle({ - radius: 5, - stroke - }) - }); - - this.map.overlay.addOlFeature(wktAsFeature); - } - toggleFilterState(event, filter: OgcInterfaceFilterOptions, property) { this.updateField(); - const mapProjection = this.map.projection; if (event.checked) { - if (filter.wkt_geometry !== '') { - this.addWktAsOverlay( - filter.wkt_geometry, - filter.filterid, - mapProjection - ); - } this.datasource.options.ogcFilters.interfaceOgcFilters .filter(f => f.filterid === filter.filterid) .forEach(element => { @@ -216,7 +179,7 @@ export class OgcFilterFormComponent implements AfterContentChecked { } changeOperator(filter) { - if (this.operators[filter.operator].spatial === false) { + if (this.ogcFilterOperators[filter.operator].spatial === false) { this.removeOverlayByID(filter.filterid); } this.refreshFilters(); @@ -262,9 +225,6 @@ export class OgcFilterFormComponent implements AfterContentChecked { ).wktPoly; element.wkt_geometry = wktPoly; } - if (wktPoly) { - this.addWktAsOverlay(wktPoly, filter.filterid, mapProjection); - } }); this.refreshFilters(); } diff --git a/packages/geo/src/lib/filter/ogc-filter-toggle-button/index.ts b/packages/geo/src/lib/filter/ogc-filter-toggle-button/index.ts new file mode 100644 index 0000000000..198efe8b5d --- /dev/null +++ b/packages/geo/src/lib/filter/ogc-filter-toggle-button/index.ts @@ -0,0 +1 @@ +export * from './ogc-filter-toggle-button.component'; diff --git a/packages/geo/src/lib/filter/ogc-filter-toggle-button/ogc-filter-toggle-button.component.html b/packages/geo/src/lib/filter/ogc-filter-toggle-button/ogc-filter-toggle-button.component.html new file mode 100644 index 0000000000..df089ce084 --- /dev/null +++ b/packages/geo/src/lib/filter/ogc-filter-toggle-button/ogc-filter-toggle-button.component.html @@ -0,0 +1,9 @@ + + + {{ogcPushButton.title}} + + + diff --git a/packages/geo/src/lib/filter/ogc-filter-toggle-button/ogc-filter-toggle-button.component.scss b/packages/geo/src/lib/filter/ogc-filter-toggle-button/ogc-filter-toggle-button.component.scss new file mode 100644 index 0000000000..3c76dc82c2 --- /dev/null +++ b/packages/geo/src/lib/filter/ogc-filter-toggle-button/ogc-filter-toggle-button.component.scss @@ -0,0 +1,10 @@ + + .mat-button-toggle-checked { + font-weight: bold; + } + + .mat-button-toggle-group { + margin: 5px 5px 5px 5px; + flex-wrap: wrap; + + } diff --git a/packages/geo/src/lib/filter/ogc-filter-toggle-button/ogc-filter-toggle-button.component.ts b/packages/geo/src/lib/filter/ogc-filter-toggle-button/ogc-filter-toggle-button.component.ts new file mode 100644 index 0000000000..9f4c835a17 --- /dev/null +++ b/packages/geo/src/lib/filter/ogc-filter-toggle-button/ogc-filter-toggle-button.component.ts @@ -0,0 +1,108 @@ +import { + Component, + Input, + OnInit +} from '@angular/core'; + +import { + OgcFilterableDataSource, + IgoOgcFilterObject, + OgcPushButton, + OgcPushButtonBundle + +} from '../../filter/shared/ogc-filter.interface'; +import { OgcFilterWriter } from '../../filter/shared/ogc-filter'; +import { IgoMap } from '../../map'; +import { OGCFilterService } from '../shared/ogc-filter.service'; +import { WMSDataSource } from '../../datasource/shared/datasources/wms-datasource'; + +@Component({ + selector: 'igo-ogc-filter-toggle-button', + templateUrl: './ogc-filter-toggle-button.component.html', + styleUrls: ['./ogc-filter-toggle-button.component.scss'] +}) +export class OgcFilterToggleButtonComponent implements OnInit { + + @Input() refreshFilters: () => void; + + @Input() datasource: OgcFilterableDataSource; + + @Input() map: IgoMap; + + private ogcFilterWriter: OgcFilterWriter; + public color = 'primary'; + public pushButtonBundle: OgcPushButtonBundle[] = []; + + constructor( + private ogcFilterService: OGCFilterService + ) { + this.ogcFilterWriter = new OgcFilterWriter(); + } + + ngOnInit() { + + if (this.datasource.options.ogcFilters && + this.datasource.options.ogcFilters.pushButtons) { + this.pushButtonBundle = this.datasource.options.ogcFilters.pushButtons as OgcPushButtonBundle[]; + } + this.applyFilters(); + + } + + getToolTip(pb: OgcPushButton): string { + let tt; + if (pb.tooltip) { + tt = pb.tooltip; + } + return tt || ''; + } + + getButtonColor(pb: OgcPushButton): {} { + + let styles; + if (pb.color) { + styles = { + 'background-color': pb.enabled ? `rgba(${pb.color})` : `rgba(255,255,255,0)`, + + }; + } + return styles; + } + + bundleIsVertical(bundle: OgcPushButtonBundle): boolean { + return bundle.vertical ? bundle.vertical : false; + } + + applyFilters(currentOgcPushButton?: OgcPushButton) { + if (currentOgcPushButton) { + currentOgcPushButton.enabled = !currentOgcPushButton.enabled; + } + let filterQueryString = ''; + const conditions = []; + this.pushButtonBundle.map(buttonBundle => { + const bundleCondition = []; + buttonBundle.ogcPushButtons + .filter(ogcpb => ogcpb.enabled === true) + .forEach(enabledPb => bundleCondition.push(enabledPb.filters)); + if (bundleCondition.length >= 1 ) { + if (bundleCondition.length === 1) { + conditions.push(bundleCondition[0]); + } else { + conditions.push({logical: buttonBundle.logical, filters: bundleCondition}); + } + } + }); + if (conditions.length >= 1) { + filterQueryString = this.ogcFilterWriter + .buildFilter(conditions.length === 1 ? + conditions[0] : {logical: 'And', filters: conditions } as IgoOgcFilterObject); + } + if (this.datasource.options.type === 'wms') { + this.ogcFilterService.filterByOgc(this.datasource as WMSDataSource, filterQueryString ); + } + if (this.datasource.options.type === 'wfs') { + // TODO: Check how to prevent wfs to refresh when filter icon is pushed... + this.datasource.ol.clear(); + } + } +} diff --git a/packages/geo/src/lib/filter/ogc-filterable-form/ogc-filterable-form.component.html b/packages/geo/src/lib/filter/ogc-filterable-form/ogc-filterable-form.component.html index 1bd3e15590..9f785b43b0 100644 --- a/packages/geo/src/lib/filter/ogc-filterable-form/ogc-filterable-form.component.html +++ b/packages/geo/src/lib/filter/ogc-filterable-form/ogc-filterable-form.component.html @@ -1,6 +1,12 @@ - + + + + + + - + diff --git a/packages/geo/src/lib/filter/ogc-filterable-form/ogc-filterable-form.component.ts b/packages/geo/src/lib/filter/ogc-filterable-form/ogc-filterable-form.component.ts index 5126494d19..4ffa834282 100644 --- a/packages/geo/src/lib/filter/ogc-filterable-form/ogc-filterable-form.component.ts +++ b/packages/geo/src/lib/filter/ogc-filterable-form/ogc-filterable-form.component.ts @@ -7,39 +7,23 @@ import { IgoMap } from '../../map'; templateUrl: './ogc-filterable-form.component.html' }) export class OgcFilterableFormComponent { - @Input() - get datasource(): OgcFilterableDataSource { - return this._dataSource; - } - set datasource(value: OgcFilterableDataSource) { - this._dataSource = value; - } - @Input() - get map(): IgoMap { - return this._map; - } - set map(value: IgoMap) { - this._map = value; - } + @Input() datasource: OgcFilterableDataSource; - // tslint:disable-next-line:ban-types - @Input() refreshFilters: Function; + @Input() map: IgoMap; + + @Input() refreshFilters: () => void; get refreshFunc() { return this.refreshFilters; } - @Input() - get showFeatureOnMap(): boolean { - return this._showFeatureOnMap; - } - set showFeatureOnMap(value: boolean) { - this._showFeatureOnMap = value; - } - private _showFeatureOnMap: boolean; - private _map: IgoMap; - private _dataSource: OgcFilterableDataSource; + get advancedOgcFilters(): boolean { + if (this.datasource.options.ogcFilters) { + return this.datasource.options.ogcFilters.advancedOgcFilters; + } + return; + } public color = 'primary'; diff --git a/packages/geo/src/lib/filter/ogc-filterable-item/ogc-filterable-item.component.html b/packages/geo/src/lib/filter/ogc-filterable-item/ogc-filterable-item.component.html index 5a034874e8..f5b8c56aea 100644 --- a/packages/geo/src/lib/filter/ogc-filterable-item/ogc-filterable-item.component.html +++ b/packages/geo/src/lib/filter/ogc-filterable-item/ogc-filterable-item.component.html @@ -6,29 +6,13 @@

{{layer.title}}

- - - - - @@ -40,7 +24,12 @@

- + + + + {{'igo.geo.filter.advancedOgcFilters' | translate}} +
diff --git a/packages/geo/src/lib/filter/ogc-filterable-item/ogc-filterable-item.component.ts b/packages/geo/src/lib/filter/ogc-filterable-item/ogc-filterable-item.component.ts index 6fd4b28edf..2b469678bc 100644 --- a/packages/geo/src/lib/filter/ogc-filterable-item/ogc-filterable-item.component.ts +++ b/packages/geo/src/lib/filter/ogc-filterable-item/ogc-filterable-item.component.ts @@ -3,7 +3,6 @@ import { Component, Input, OnInit } from '@angular/core'; import * as olstyle from 'ol/style'; import { Layer } from '../../layer/shared/layers/layer'; -import { MapService } from '../../map/shared/map.service'; import { DownloadService } from '../../download/shared/download.service'; import { WMSDataSource } from '../../datasource/shared/datasources/wms-datasource'; import { WFSDataSourceOptionsParams } from '../../datasource/shared/datasources/wfs-datasource.interface'; @@ -15,6 +14,7 @@ import { } from '../shared/ogc-filter.interface'; import { OGCFilterService } from '../shared/ogc-filter.service'; import { IgoMap } from '../../map'; +import { OgcFilterWriter } from '../shared/ogc-filter'; @Component({ selector: 'igo-ogc-filterable-item', @@ -28,50 +28,25 @@ export class OgcFilterableItemComponent implements OnInit { public hasActiveSpatialFilter = false; public filtersAreEditable = true; public filtersCollapsed = true; + public hasPushButton: boolean = false; - @Input() - get layer(): Layer { - return this._layer; - } - set layer(value: Layer) { - this._layer = value; - } + @Input() layer: Layer; - @Input() - get map(): IgoMap { - return this._map; - } - set map(value: IgoMap) { - this._map = value; - } + @Input() map: IgoMap; get refreshFunc() { return this.refreshFilters.bind(this); } - @Input() - get showFeatureOnMap(): boolean { - return this._showFeatureOnMap; - } - set showFeatureOnMap(value: boolean) { - this._showFeatureOnMap = value; - } - - public _showFeatureOnMap = false; - private _map: IgoMap; - private _layer: Layer; get datasource(): OgcFilterableDataSource { return this.layer.dataSource as OgcFilterableDataSource; } - @Input() - get ogcFiltersHeaderShown(): boolean { - return this._ogcFiltersHeaderShown; - } - set ogcFiltersHeaderShown(value: boolean) { - this._ogcFiltersHeaderShown = value; + @Input() ogcFiltersHeaderShown: boolean; + + get downloadable() { + return (this.datasource.options as any).download; } - private _ogcFiltersHeaderShown: boolean; constructor( private ogcFilterService: OGCFilterService, @@ -79,6 +54,16 @@ export class OgcFilterableItemComponent implements OnInit { ) {} ngOnInit() { + const ogcFilters = this.datasource.options.ogcFilters; + if ( + ogcFilters.pushButtons && + ogcFilters.pushButtons.length > 0) { + if (ogcFilters.advancedOgcFilters === undefined) { + ogcFilters.advancedOgcFilters = false; + } + this.hasPushButton = true; + } + switch (this.datasource.options.type) { case 'wms': this.ogcFilterService.setOgcWMSFiltersOptions(this.datasource); @@ -90,13 +75,13 @@ export class OgcFilterableItemComponent implements OnInit { break; } - if (this.datasource.options.ogcFilters) { - if (this.datasource.options.ogcFilters.interfaceOgcFilters) { + if (ogcFilters) { + if (ogcFilters.interfaceOgcFilters) { this.lastRunOgcFilter = JSON.parse( - JSON.stringify(this.datasource.options.ogcFilters.interfaceOgcFilters) + JSON.stringify(ogcFilters.interfaceOgcFilters) ); if ( - this.datasource.options.ogcFilters.interfaceOgcFilters.filter( + ogcFilters.interfaceOgcFilters.filter( f => f.wkt_geometry ).length >= 1 ) { @@ -104,66 +89,12 @@ export class OgcFilterableItemComponent implements OnInit { } } - this.filtersAreEditable = this.datasource.options.ogcFilters.editable - ? this.datasource.options.ogcFilters.editable + this.filtersAreEditable = ogcFilters.editable + ? ogcFilters.editable : false; } } - private getOverlayByID(id) { - this.map.overlay.dataSource.ol.getFeatureById(id); - } - - toggleShowFeatureOnMap() { - this.showFeatureOnMap = !this.showFeatureOnMap; - this.datasource.options.ogcFilters.interfaceOgcFilters.forEach(filter => { - let drawnFeature; - let drawnStrokeColor = [125, 136, 140, 0] as [ - number, - number, - number, - number - ]; - let drawStrokeWidth = 2; - let drawnFillColor = [125, 136, 140, 0] as [ - number, - number, - number, - number - ]; - - drawnFeature = this.getOverlayByID('ogcFilterOverlay_' + filter.filterid); - if (this.showFeatureOnMap !== false) { - drawnStrokeColor = [125, 136, 140, 0.5]; - drawStrokeWidth = 2; - drawnFillColor = [125, 136, 140, 0]; - } - - const stroke = new olstyle.Stroke({ - width: drawStrokeWidth, - color: drawnStrokeColor - }); - - const fill = new olstyle.Stroke({ - color: drawnFillColor - }); - - const olStyle = new olstyle.Style({ - stroke, - fill, - image: new olstyle.Circle({ - radius: 5, - stroke, - fill - }) - }); - - if (drawnFeature) { - drawnFeature.setStyle(olStyle); - } - }); - } - addFilterToSequence() { this.filtersCollapsed = false; const interfaceOgcFilters: OgcInterfaceFilterOptions[] = this.datasource.options.ogcFilters.interfaceOgcFilters; @@ -190,13 +121,13 @@ export class OgcFilterableItemComponent implements OnInit { } const status = arr.length === 0 ? true : false; arr.push( - (this.datasource as any).ogcFilterWriter.addInterfaceFilter( + new OgcFilterWriter().addInterfaceFilter( { propertyName: firstFieldName, operator: 'PropertyIsEqualTo', active: status, igoSpatialSelector: 'fixedExtent' - }, + } as OgcInterfaceFilterOptions, fieldNameGeometry, lastLevel, this.defaultLogicalParent @@ -209,8 +140,12 @@ export class OgcFilterableItemComponent implements OnInit { this.downloadService.open(this.layer); } - refreshFilters() { + refreshFilters(force?: boolean) { + if (force === true) { + this.lastRunOgcFilter = undefined; + } const ogcFilters: OgcFiltersOptions = this.datasource.options.ogcFilters; + const ogcFilterWriter = new OgcFilterWriter(); const activeFilters = ogcFilters.interfaceOgcFilters.filter( f => f.active === true ); @@ -237,8 +172,7 @@ export class OgcFilterableItemComponent implements OnInit { if (this.layer.dataSource.options.type === 'wfs') { const ogcDataSource: any = this.layer.dataSource; const ogcLayer: OgcFiltersOptions = ogcDataSource.options.ogcFilters; - const writer = ogcDataSource.ogcFilterWriter; - ogcLayer.filters = writer.rebuiltIgoOgcFilterObjectFromSequence( + ogcLayer.filters = ogcFilterWriter.rebuiltIgoOgcFilterObjectFromSequence( activeFilters ); this.layer.dataSource.ol.clear(); @@ -250,12 +184,10 @@ export class OgcFilterableItemComponent implements OnInit { if (activeFilters.length >= 1) { const ogcDataSource: any = this.layer.dataSource; const ogcLayer: OgcFiltersOptions = ogcDataSource.options.ogcFilters; - const writer = ogcDataSource.ogcFilterWriter; - ogcLayer.filters = writer.rebuiltIgoOgcFilterObjectFromSequence( + ogcLayer.filters = ogcFilterWriter.rebuiltIgoOgcFilterObjectFromSequence( activeFilters ); - rebuildFilter = (this.layer - .dataSource as any).ogcFilterWriter.buildFilter( + rebuildFilter = ogcFilterWriter.buildFilter( ogcLayer.filters, undefined, undefined, @@ -276,11 +208,26 @@ export class OgcFilterableItemComponent implements OnInit { } } - get downloadable() { - return (this.datasource.options as any).download; - } - public setVisible() { this.layer.visible = true; } + + public isAdvancedOgcFilters(): boolean { + return this.datasource.options.ogcFilters.advancedOgcFilters; + } + + public addFilterDisabled(): boolean { + return (!this.datasource.options.sourceFields || this.datasource.options.sourceFields.length === 0); + } + + private changeOgcFiltersAdvancedOgcFilters(value: boolean) { + this.datasource.options.ogcFilters.advancedOgcFilters = value; + } + + changeOgcFilterType(isAdvancedOgcFilters) { + this.changeOgcFiltersAdvancedOgcFilters(isAdvancedOgcFilters.checked); + if (isAdvancedOgcFilters.checked) { + this.refreshFilters(true); + } + } } diff --git a/packages/geo/src/lib/filter/ogc-filterable-list/ogc-filterable-list.component.ts b/packages/geo/src/lib/filter/ogc-filterable-list/ogc-filterable-list.component.ts index 9eb2c742f5..7564892d9c 100644 --- a/packages/geo/src/lib/filter/ogc-filterable-list/ogc-filterable-list.component.ts +++ b/packages/geo/src/lib/filter/ogc-filterable-list/ogc-filterable-list.component.ts @@ -1,8 +1,7 @@ import { Component, Input, - ChangeDetectionStrategy, - ChangeDetectorRef + ChangeDetectionStrategy } from '@angular/core'; import { Layer } from '../../layer/shared/layers/layer'; @@ -14,24 +13,10 @@ import { IgoMap } from '../../map'; changeDetection: ChangeDetectionStrategy.OnPush }) export class OgcFilterableListComponent { - @Input() - get layers(): Layer[] { - return this._layers; - } - set layers(value: Layer[]) { - this._layers = value; - this.cdRef.detectChanges(); - } - @Input() - get map(): IgoMap { - return this._map; - } - set map(value: IgoMap) { - this._map = value; - } - private _map: IgoMap; - private _layers: Layer[] = []; + @Input() layers: Layer[]; - constructor(private cdRef: ChangeDetectorRef) {} + @Input() map: IgoMap; + + constructor() {} } diff --git a/packages/geo/src/lib/filter/shared/index.ts b/packages/geo/src/lib/filter/shared/index.ts index 737f32cc74..9b00c5737d 100644 --- a/packages/geo/src/lib/filter/shared/index.ts +++ b/packages/geo/src/lib/filter/shared/index.ts @@ -1,6 +1,7 @@ export * from './filterable-datasource.pipe'; export * from './time-filter.interface'; export * from './time-filter.service'; +export * from './ogc-filter.enum'; export * from './ogc-filter.interface'; export * from './ogc-filter.service'; export * from './ogc-filter'; diff --git a/packages/geo/src/lib/filter/shared/ogc-filter.enum.ts b/packages/geo/src/lib/filter/shared/ogc-filter.enum.ts new file mode 100644 index 0000000000..a217741094 --- /dev/null +++ b/packages/geo/src/lib/filter/shared/ogc-filter.enum.ts @@ -0,0 +1,7 @@ +export enum OgcFilterOperatorType { + BasicNumericOperator = 'BasicNumericOperator', + Basic = 'Basic', + BasicAndSpatial = 'BasicAndSpatial', + Spatial = 'Spatial', + All = 'All' +} diff --git a/packages/geo/src/lib/filter/shared/ogc-filter.interface.ts b/packages/geo/src/lib/filter/shared/ogc-filter.interface.ts index c75b8d002e..89fabf9b59 100644 --- a/packages/geo/src/lib/filter/shared/ogc-filter.interface.ts +++ b/packages/geo/src/lib/filter/shared/ogc-filter.interface.ts @@ -3,14 +3,13 @@ import olFormatFilter from 'ol/format/filter/Filter'; import { DataSource } from '../../datasource/shared/datasources/datasource'; import { DataSourceOptions } from '../../datasource/shared/datasources/datasource.interface'; +import { OgcFilterOperatorType } from './ogc-filter.enum'; export interface OgcFilter extends olFormatFilter {} export interface WFSWriteGetFeatureOptions { - filter?: olFormatFilter; - - featureNS: string; - featurePrefix: string; + featureNS?: string; + featurePrefix?: string; featureTypes: string[]; srsName?: string; handle?: string; @@ -21,6 +20,7 @@ export interface WFSWriteGetFeatureOptions { startIndex?: number; count?: number; bbox?: [number, number, number, number]; + filter?: olFormatFilter; resultType?: string; } @@ -42,8 +42,25 @@ export interface OgcFiltersOptions { enabled?: boolean; editable?: boolean; filters?: IgoLogicalArrayOptions | AnyBaseOgcFilterOptions; + pushButtons?: OgcPushButtonBundle[]; interfaceOgcFilters?: OgcInterfaceFilterOptions[]; filtered?: boolean; + advancedOgcFilters?: boolean; + geometryName?: string; + allowedOperatorsType?: OgcFilterOperatorType; +} + +export interface OgcPushButtonBundle { + logical?: string; + vertical?: boolean; + ogcPushButtons: OgcPushButton[]; +} +export interface OgcPushButton { + title: string; + tooltip?: string; + enabled: boolean; + filters: IgoOgcFilterObject; + color?: string; } export interface OgcFilterableDataSourceOptions extends DataSourceOptions { diff --git a/packages/geo/src/lib/filter/shared/ogc-filter.service.ts b/packages/geo/src/lib/filter/shared/ogc-filter.service.ts index 16d5ddc852..595fd0d1b7 100644 --- a/packages/geo/src/lib/filter/shared/ogc-filter.service.ts +++ b/packages/geo/src/lib/filter/shared/ogc-filter.service.ts @@ -1,8 +1,6 @@ import { Injectable } from '@angular/core'; -import { BehaviorSubject } from 'rxjs'; import { WMSDataSource } from '../../datasource/shared/datasources/wms-datasource'; -import { WFSDataSourceOptions } from '../../datasource/shared/datasources/wfs-datasource.interface'; import { OgcFilterWriter } from './ogc-filter'; import { OgcFilterableDataSource } from './ogc-filter.interface'; @@ -11,11 +9,8 @@ export class OGCFilterService { constructor() {} public filterByOgc(wmsDatasource: WMSDataSource, filterString: string) { - const appliedFilter = wmsDatasource.formatProcessedOgcFilter(filterString, wmsDatasource.options.params.layers); - const wmsFilterValue = appliedFilter.length > 0 - ? appliedFilter.replace('filter=', '') - : undefined; - wmsDatasource.ol.updateParams({ filter: wmsFilterValue }); + const appliedFilter = new OgcFilterWriter().formatProcessedOgcFilter(filterString, wmsDatasource.options.params.layers); + wmsDatasource.ol.updateParams({ filter: appliedFilter }); } public setOgcWFSFiltersOptions(wfsDatasource: OgcFilterableDataSource) { diff --git a/packages/geo/src/lib/filter/shared/ogc-filter.ts b/packages/geo/src/lib/filter/shared/ogc-filter.ts index 9c6f7f0595..58a22beee8 100644 --- a/packages/geo/src/lib/filter/shared/ogc-filter.ts +++ b/packages/geo/src/lib/filter/shared/ogc-filter.ts @@ -10,7 +10,9 @@ import { IgoOgcFilterObject, WFSWriteGetFeatureOptions, AnyBaseOgcFilterOptions, - OgcInterfaceFilterOptions + OgcInterfaceFilterOptions, + OgcFilterableDataSourceOptions, + OgcFiltersOptions } from './ogc-filter.interface'; export class OgcFilterWriter { @@ -20,10 +22,7 @@ export class OgcFilterWriter { PropertyIsNotEqualTo: { spatial: false, fieldRestrict: [] }, PropertyIsLike: { spatial: false, fieldRestrict: ['string'] }, PropertyIsGreaterThan: { spatial: false, fieldRestrict: ['number'] }, - PropertyIsGreaterThanOrEqualTo: { - spatial: false, - fieldRestrict: ['number'] - }, + PropertyIsGreaterThanOrEqualTo: { spatial: false, fieldRestrict: ['number'] }, PropertyIsLessThan: { spatial: false, fieldRestrict: ['number'] }, PropertyIsLessThanOrEqualTo: { spatial: false, fieldRestrict: ['number'] }, PropertyIsBetween: { spatial: false, fieldRestrict: ['number'] }, @@ -34,8 +33,29 @@ export class OgcFilterWriter { Contains: { spatial: true, fieldRestrict: [] } }; + defineOgcFiltersDefaultOptions( + ogcFiltersOptions: OgcFiltersOptions, + fieldNameGeometry: string, + srcType?: string): OgcFiltersOptions { + let ogcFiltersDefaultValue = true; // default values for wfs. + if (srcType && srcType === 'wms') { + ogcFiltersDefaultValue = false; + } + + ogcFiltersOptions = ogcFiltersOptions || {}; + ogcFiltersOptions.enabled = ogcFiltersOptions.enabled === undefined ? ogcFiltersDefaultValue : ogcFiltersOptions.enabled; + ogcFiltersOptions.editable = ogcFiltersOptions.editable === undefined ? ogcFiltersDefaultValue : ogcFiltersOptions.editable; + ogcFiltersOptions.geometryName = fieldNameGeometry; + + ogcFiltersOptions.advancedOgcFilters = true; + if (ogcFiltersOptions.enabled && ogcFiltersOptions.pushButtons) { + ogcFiltersOptions.advancedOgcFilters = false; + } + return ogcFiltersOptions; + } + public buildFilter( - filters: IgoOgcFilterObject, + filters?: IgoOgcFilterObject, extent?: [number, number, number, number], proj?, fieldNameGeometry?: string @@ -248,11 +268,14 @@ export class OgcFilterWriter { } public addInterfaceFilter( - igoOgcFilterObject = { operator: 'PropertyIsEqualTo' }, + igoOgcFilterObject?, geometryName?, level = 0, parentLogical = 'Or' ): OgcInterfaceFilterOptions { + if (!igoOgcFilterObject) { + igoOgcFilterObject = { operator: 'PropertyIsEqualTo' }; + } const f = { propertyName: '', operator: '', @@ -399,4 +422,65 @@ export class OgcFilterWriter { return undefined; } } + + public handleOgcFiltersAppliedValue(options: OgcFilterableDataSourceOptions, fieldNameGeometry: string) { + const ogcFilters = options.ogcFilters; + if (!ogcFilters) { + return; + } + let filterQueryStringPushButton = ''; + let filterQueryStringAdvancedFilters = ''; + if (ogcFilters.enabled && ogcFilters.pushButtons) { + const pushButtonBundle = ogcFilters.pushButtons; + const conditions = []; + pushButtonBundle.map(buttonBundle => { + const bundleCondition = []; + buttonBundle.ogcPushButtons + .filter(ogcpb => ogcpb.enabled === true) + .forEach(enabledPb => bundleCondition.push(enabledPb.filters)); + if (bundleCondition.length === 1) { + conditions.push(bundleCondition[0]); + } else if (bundleCondition.length > 1) { + conditions.push({ logical: buttonBundle.logical, filters: bundleCondition }); + } + }); + if (conditions.length >= 1) { + filterQueryStringPushButton = this.buildFilter( + conditions.length === 1 ? conditions[0] : { logical: 'And', filters: conditions } + ); + } + } + + if (ogcFilters.enabled && ogcFilters.filters) { + ogcFilters.geometryName = ogcFilters.geometryName || fieldNameGeometry; + const igoFilters = ogcFilters.filters; + filterQueryStringAdvancedFilters = this.buildFilter(igoFilters); + } + + let filterQueryString = ogcFilters.advancedOgcFilters ? filterQueryStringAdvancedFilters : filterQueryStringPushButton; + if (options.type === 'wms') { + filterQueryString = this.formatProcessedOgcFilter(filterQueryString, (options as any).params.layers); + } + if (options.type === 'wfs') { + filterQueryString = this.formatProcessedOgcFilter(filterQueryString, (options as any).params.featureTypes); + } + + return filterQueryString; + + } + + public formatProcessedOgcFilter( + processedFilter: string, + layersOrTypenames: string): string { + let appliedFilter = ''; + if (processedFilter.length === 0 && layersOrTypenames.indexOf(',') === -1) { + appliedFilter = processedFilter; + } else { + layersOrTypenames.split(',').forEach(layerOrTypenames => { + appliedFilter = `${appliedFilter}(${processedFilter.replace('filter=', '')})`; + }); + } + const filterValue = appliedFilter.length > 0 ? appliedFilter.replace('filter=', '') : undefined; + return filterValue; + } } diff --git a/packages/geo/src/lib/workspace/widgets/ogc-filter/ogc-filter.component.ts b/packages/geo/src/lib/workspace/widgets/ogc-filter/ogc-filter.component.ts index 93864aaef2..4994cfe5f8 100644 --- a/packages/geo/src/lib/workspace/widgets/ogc-filter/ogc-filter.component.ts +++ b/packages/geo/src/lib/workspace/widgets/ogc-filter/ogc-filter.component.ts @@ -24,8 +24,6 @@ export class OgcFilterComponent implements OnUpdateInputs, WidgetComponent { @Input() map: IgoMap; - @Input() showFeatureOnMap: boolean = true; - /** * Event emitted on complete */ diff --git a/packages/geo/src/locale/en.geo.json b/packages/geo/src/locale/en.geo.json index 84f22e88e4..20be8ab54b 100644 --- a/packages/geo/src/locale/en.geo.json +++ b/packages/geo/src/locale/en.geo.json @@ -77,8 +77,7 @@ "layerFiltered": "This layer is currently filtered", "layerFilterable": "This layer is filterable", "filterBy": "Filter by", - "showFeatureExtent": "Show feature used as spatial extent", - "hideFeatureExtent": "Hide feature used as spatial extent " + "advancedOgcFilters": "Advanced filters" }, "spatialSelector": { "fixedExtent": "Fixed extent", diff --git a/packages/geo/src/locale/fr.geo.json b/packages/geo/src/locale/fr.geo.json index 4c450d93fc..8182399b0e 100644 --- a/packages/geo/src/locale/fr.geo.json +++ b/packages/geo/src/locale/fr.geo.json @@ -76,8 +76,7 @@ "layerFiltered": "Cette couche d'information est présentement filtrée.", "layerFilterable": "Cette couche d'information est filtrable", "filterBy": "Filtrer par", - "showFeatureExtent": "Montrer l'empreinte des étendues", - "hideFeatureExtent": "Cacher l'empreinte des étendues" + "advancedOgcFilters": "Filtres avancés" }, "spatialSelector": { "fixedExtent": "Étendue fixe", diff --git a/packages/integration/src/lib/filter/ogc-filter-tool/ogc-filter-tool.component.ts b/packages/integration/src/lib/filter/ogc-filter-tool/ogc-filter-tool.component.ts index 393e08d9cd..dc041273cb 100644 --- a/packages/integration/src/lib/filter/ogc-filter-tool/ogc-filter-tool.component.ts +++ b/packages/integration/src/lib/filter/ogc-filter-tool/ogc-filter-tool.component.ts @@ -5,7 +5,7 @@ import { ToolComponent } from '@igo2/common'; @ToolComponent({ name: 'ogcFilter', title: 'igo.integration.tools.ogcFilter', - icon: 'filter-list' + icon: 'filter' }) @Component({ selector: 'igo-ogc-filter-tool', From 67074c690ab1fee9afc30e43518f6c0c0fb5bea2 Mon Sep 17 00:00:00 2001 From: matrottier Date: Wed, 7 Aug 2019 10:19:24 -0400 Subject: [PATCH 32/38] feat(search): Search setting upgrade (#375) * style(geo.search.search-selector) useless line * feat(geo.search.search-setting) enable/disable search source with checkbox * fix(context): fix minors issues context module * test(geo.search.search-settings) fix test * style(geo.search.search-settings) useless code * style(geo.search.search-selector) useless line * feat(geo.search.search-setting) enable/disable search source with checkbox * test(geo.search.search-settings) fix test * style(geo.search.search-settings) useless code * fix (geo.search.source.ilayer) get title when language service not loaded * style (geo.search.search-settings) de-hardcode xposition menu --- package.json | 3 +- .../src/lib/list/list.component.spec.ts | 61 +++++++++++++--- .../search-selector.component.ts | 42 ----------- .../search-settings.component.html | 28 ++++--- .../search-settings.component.scss | 15 ++++ .../search-settings.component.spec.ts | 73 +++++++++++++++++++ .../search-settings.component.ts | 4 + .../search-settings/search-settings.module.ts | 3 - .../src/lib/search/shared/sources/ilayer.ts | 7 +- 9 files changed, 170 insertions(+), 66 deletions(-) create mode 100644 packages/geo/src/lib/search/search-settings/search-settings.component.spec.ts diff --git a/package.json b/package.json index dc69616f82..eb10c4284a 100644 --- a/package.json +++ b/package.json @@ -142,6 +142,7 @@ "tsickle": "0.34.2", "tslint": "^5.12.1", "typescript": "~3.2.4", - "wait-on": "^3.2.0" + "wait-on": "^3.2.0", + "@ngx-translate/http-loader": "^4.0.0" } } diff --git a/packages/common/src/lib/list/list.component.spec.ts b/packages/common/src/lib/list/list.component.spec.ts index eefc0c9182..776392db7d 100644 --- a/packages/common/src/lib/list/list.component.spec.ts +++ b/packages/common/src/lib/list/list.component.spec.ts @@ -1,25 +1,68 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { IgoLanguageModule } from '@igo2/core'; +import { SearchSettingsComponent } from './search-settings.component'; +import { CommonModule } from '@angular/common'; +import { + MatTooltipModule, + MatIconModule, + MatButtonModule, + MatMenuModule, + MatRadioModule, + MatCheckboxModule +} from '@angular/material'; -import { MatListModule } from '@angular/material'; +import { SearchSourceService } from '../shared/search-source.service'; +import { provideDefaultIChercheSearchResultFormatter } from '../shared/sources/icherche.providers'; +import { provideDefaultCoordinatesSearchResultFormatter } from '../shared/sources/coordinates.providers'; +import { SearchSource } from '../shared/sources/source'; +import { HttpClientModule } from '@angular/common/http'; +import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { HttpClient } from '@angular/common/http'; +import {TranslateHttpLoader} from '@ngx-translate/http-loader'; -import { ListComponent } from './list.component'; +// AoT requires an exported function for factories +export function HttpLoaderFactory(httpClient: HttpClient) { + return new TranslateHttpLoader(httpClient); +} -describe('ListComponent', () => { - let component: ListComponent; - let fixture: ComponentFixture; +describe('SearchSettingsComponent', () => { + let component: SearchSettingsComponent; + let fixture: ComponentFixture; beforeEach(async(() => { + + const spy = jasmine.createSpyObj('SearchSourceService', ['getSources']); + TestBed.configureTestingModule({ imports: [ - MatListModule - ], - declarations: [ ListComponent ] + HttpClientModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient] + } + }), + IgoLanguageModule, + CommonModule, + MatTooltipModule, + MatIconModule, + MatButtonModule, + MatMenuModule, + MatRadioModule, + MatCheckboxModule], + declarations: [ SearchSettingsComponent ], + providers: [ + { provide: SearchSourceService, useValue: spy }, + provideDefaultIChercheSearchResultFormatter(), + provideDefaultCoordinatesSearchResultFormatter() + ] }) .compileComponents(); })); beforeEach(() => { - fixture = TestBed.createComponent(ListComponent); + fixture = TestBed.createComponent(SearchSettingsComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/packages/geo/src/lib/search/search-selector/search-selector.component.ts b/packages/geo/src/lib/search/search-selector/search-selector.component.ts index 7c88a7ad6b..39cee64a30 100644 --- a/packages/geo/src/lib/search/search-selector/search-selector.component.ts +++ b/packages/geo/src/lib/search/search-selector/search-selector.component.ts @@ -86,46 +86,4 @@ export class SearchSelectorComponent implements OnInit { this.change.emit(searchType); } - /** - * Get all search sources - * @internal - */ - getSearchSources(): SearchSource[] { - return this.searchSourceService.getSources(); - } - - /** - * Triggered when a setting is checked (checkbox style) - * @internal - */ - settingsValueCheckedCheckbox( - event: MatCheckboxChange, - source: SearchSource, - setting: SearchSourceSettings, - settingValue: SettingOptions - ) { - settingValue.enabled = event.checked; - source.setParamFromSetting(setting); - } - - /** - * Triggered when a setting is checked (radiobutton style) - * @internal - */ - settingsValueCheckedRadioButton( - event: MatRadioChange, - source: SearchSource, - setting: SearchSourceSettings, - settingValue: SettingOptions - ) { - setting.values.forEach( conf => { - if (conf.value !== settingValue.value) { - conf.enabled = !event.source.checked; - } else { - conf.enabled = event.source.checked; - } - }); - source.setParamFromSetting(setting); - } - } diff --git a/packages/geo/src/lib/search/search-settings/search-settings.component.html b/packages/geo/src/lib/search/search-settings/search-settings.component.html index 84efd38e34..2455872f33 100644 --- a/packages/geo/src/lib/search/search-settings/search-settings.component.html +++ b/packages/geo/src/lib/search/search-settings/search-settings.component.html @@ -14,14 +14,25 @@ #searchSettingsMenu="matMenu" class="no-border-radius"> - - + + + + + + +