diff --git a/package-lock.json b/package-lock.json index 3deacc196e..4eefcc3545 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7584,6 +7584,40 @@ "url": "https://opencollective.com/turf" } }, + "node_modules/@turf/boolean-disjoint": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-disjoint/-/boolean-disjoint-7.2.0.tgz", + "integrity": "sha512-xdz+pYKkLMuqkNeJ6EF/3OdAiJdiHhcHCV0ykX33NIuALKIEpKik0+NdxxNsZsivOW6keKwr61SI+gcVtHYcnQ==", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-in-polygon": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/line-intersect": "^7.2.0", + "@turf/meta": "^7.2.0", + "@turf/polygon-to-line": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-intersects": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-intersects/-/boolean-intersects-7.2.0.tgz", + "integrity": "sha512-GLRyLQgK3F14drkK5Qi9Mv7Z9VT1bgQUd9a3DB3DACTZWDSwfh8YZUFn/HBwRkK8dDdgNEXaavggQHcPi1k9ow==", + "license": "MIT", + "dependencies": { + "@turf/boolean-disjoint": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, "node_modules/@turf/boolean-point-in-polygon": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@turf/boolean-point-in-polygon/-/boolean-point-in-polygon-7.2.0.tgz", @@ -7600,6 +7634,25 @@ "url": "https://opencollective.com/turf" } }, + "node_modules/@turf/buffer": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/buffer/-/buffer-7.2.0.tgz", + "integrity": "sha512-QH1FTr5Mk4z1kpQNztMD8XBOZfpOXPOtlsxaSAj2kDIf5+LquA6HtJjZrjUngnGtzG5+XwcfyRL4ImvLnFjm5Q==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "^7.2.0", + "@turf/center": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/jsts": "^2.7.1", + "@turf/meta": "^7.2.0", + "@turf/projection": "^7.2.0", + "@types/geojson": "^7946.0.10", + "d3-geo": "1.7.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, "node_modules/@turf/center": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@turf/center/-/center-7.2.0.tgz", @@ -7672,6 +7725,22 @@ "url": "https://opencollective.com/turf" } }, + "node_modules/@turf/intersect": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/intersect/-/intersect-7.2.0.tgz", + "integrity": "sha512-81GMzKS9pKqLPa61qSlFxLFeAC8XbwyCQ9Qv4z6o5skWk1qmMUbEHeMqaGUTEzk+q2XyhZ0sju1FV4iLevQ/aw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "polyclip-ts": "^0.16.8", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, "node_modules/@turf/invariant": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-7.2.0.tgz", @@ -7686,6 +7755,15 @@ "url": "https://opencollective.com/turf" } }, + "node_modules/@turf/jsts": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@turf/jsts/-/jsts-2.7.2.tgz", + "integrity": "sha512-zAezGlwWHPyU0zxwcX2wQY3RkRpwuoBmhhNE9HY9kWhFDkCxZ3aWK5URKwa/SWKJbj9aztO+8vtdiBA28KVJFg==", + "license": "(EDL-1.0 OR EPL-1.0)", + "dependencies": { + "jsts": "2.7.1" + } + }, "node_modules/@turf/line-intersect": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@turf/line-intersect/-/line-intersect-7.2.0.tgz", @@ -7749,6 +7827,37 @@ "url": "https://opencollective.com/turf" } }, + "node_modules/@turf/polygon-to-line": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/polygon-to-line/-/polygon-to-line-7.2.0.tgz", + "integrity": "sha512-9jeTN3LiJ933I5sd4K0kwkcivOYXXm1emk0dHorwXeSFSHF+nlYesEW3Hd889wb9lZd7/SVLMUeX/h39mX+vCA==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^7.2.0", + "@turf/invariant": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/projection": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@turf/projection/-/projection-7.2.0.tgz", + "integrity": "sha512-/qke5vJScv8Mu7a+fU3RSChBRijE6EVuFHU3RYihMuYm04Vw8dBMIs0enEpoq0ke/IjSbleIrGQNZIMRX9EwZQ==", + "license": "MIT", + "dependencies": { + "@turf/clone": "^7.2.0", + "@turf/helpers": "^7.2.0", + "@turf/meta": "^7.2.0", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -9539,6 +9648,15 @@ "node": "*" } }, + "node_modules/bignumber.js": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.2.1.tgz", + "integrity": "sha512-+NzaKgOUvInq9TIUZ1+DRspzf/HApkCwD4btfuasFTdrfnOxqx853TgDpMolp+uv4RpRp7bPcEU2zKr9+fRmyw==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -11872,6 +11990,21 @@ "node": ">=6" } }, + "node_modules/d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-geo": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.7.1.tgz", + "integrity": "sha512-O4AempWAr+P5qbk2bC2FuN/sDW4z+dN2wDf9QV3bxQt4M5HfOEeXLgJ/UKQW0+o1Dj8BE+L5kiDbdWUMjsmQpw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1" + } + }, "node_modules/dargs": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz", @@ -16725,6 +16858,15 @@ "verror": "1.10.0" } }, + "node_modules/jsts": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/jsts/-/jsts-2.7.1.tgz", + "integrity": "sha512-x2wSZHEBK20CY+Wy+BPE7MrFQHW6sIsdaGUMEqmGAio+3gFzQaBYPwLRonUfQf9Ak8pBieqj9tUofX1+WtAEIg==", + "license": "(EDL-1.0 OR EPL-1.0)", + "engines": { + "node": ">= 12" + } + }, "node_modules/jszip": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", @@ -23272,6 +23414,16 @@ "robust-predicates": "^3.0.2" } }, + "node_modules/polyclip-ts": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/polyclip-ts/-/polyclip-ts-0.16.8.tgz", + "integrity": "sha512-JPtKbDRuPEuAjuTdhR62Gph7Is2BS1Szx69CFOO3g71lpJDFo78k4tFyi+qFOMVPePEzdSKkpGU3NBXPHHjvKQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.1.0", + "splaytree-ts": "^1.0.2" + } + }, "node_modules/postcss": { "version": "8.5.2", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.2.tgz", @@ -25834,6 +25986,12 @@ "wbuf": "^1.7.3" } }, + "node_modules/splaytree-ts": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/splaytree-ts/-/splaytree-ts-1.0.2.tgz", + "integrity": "sha512-0kGecIZNIReCSiznK3uheYB8sbstLjCZLiwcQwbmLhgHJj2gz6OnSPkVzJQCMnmEz1BQ4gPK59ylhBoEWOhGNA==", + "license": "BDS-3-Clause" + }, "node_modules/split": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", @@ -28583,7 +28741,10 @@ "license": "MIT", "dependencies": { "@mat-datetimepicker/core": "^15.0.1", + "@turf/boolean-intersects": "^7.2.0", + "@turf/buffer": "^7.2.0", "@turf/helpers": "^7.2.0", + "@turf/intersect": "^7.2.0", "@turf/line-intersect": "^7.2.0", "@turf/point-on-feature": "^7.2.0", "file-saver": "^2.0.2", diff --git a/packages/geo/ng-package.json b/packages/geo/ng-package.json index 2add21d3a7..cfd7317453 100644 --- a/packages/geo/ng-package.json +++ b/packages/geo/ng-package.json @@ -18,7 +18,13 @@ "nosleep.js", "striptags", "ts-cacheable", - "ts-md5" + "ts-md5", + "@turf/helpers", + "@turf/boolean-intersects", + "@turf/buffer", + "@turf/intersect", + "@turf/line-intersect", + "@turf/point-on-feature" ], "assets": [ { "input": "", "glob": "**/*.them*.scss", "output": "" }, diff --git a/packages/geo/package.json b/packages/geo/package.json index f0c8a28ae9..2de717daec 100644 --- a/packages/geo/package.json +++ b/packages/geo/package.json @@ -39,6 +39,9 @@ }, "dependencies": { "@mat-datetimepicker/core": "^15.0.1", + "@turf/boolean-intersects": "^7.2.0", + "@turf/buffer": "^7.2.0", + "@turf/intersect": "^7.2.0", "@turf/helpers": "^7.2.0", "@turf/line-intersect": "^7.2.0", "@turf/point-on-feature": "^7.2.0", diff --git a/packages/geo/src/lib/geometry/shared/geometry.utils.ts b/packages/geo/src/lib/geometry/shared/geometry.utils.ts index d5747576a1..1469aaecc0 100644 --- a/packages/geo/src/lib/geometry/shared/geometry.utils.ts +++ b/packages/geo/src/lib/geometry/shared/geometry.utils.ts @@ -8,9 +8,13 @@ import OlPoint from 'ol/geom/Point'; import OlPolygon from 'ol/geom/Polygon'; import * as olstyle from 'ol/style'; -import { lineString } from '@turf/helpers'; +import booleanIntersects from '@turf/boolean-intersects'; +import buffer from '@turf/buffer'; +import { Units, lineString } from '@turf/helpers'; +import { feature as turfFeature } from '@turf/helpers'; import lineIntersect from '@turf/line-intersect'; +import { FeatureGeometry } from '../../feature'; import { GeometrySliceLineStringError, GeometrySliceMultiPolygonError, @@ -145,3 +149,27 @@ export function getMousePositionFromOlGeometryEvent(olEvent: BasicEvent) { const olGeometryCast = olGeometry as OlPoint | OlLineString | OlCircle; return olGeometryCast.getFlatCoordinates().slice(-2) as [number, number]; } + +export function doesOlGeometryIntersects( + olGeometry1: OlGeometry, + olGeometry2: OlGeometry +): boolean { + const olGeoJSON = new OlGeoJSON(); + const firstGeom = olGeoJSON.writeGeometryObject(olGeometry1); + const secondGeom = olGeoJSON.writeGeometryObject(olGeometry2); + return booleanIntersects( + firstGeom as FeatureGeometry, + secondGeom as FeatureGeometry + ); +} + +export function bufferOlGeometry( + olGeometry: OlGeometry, + dist: number, + units: Units = 'meters' +): FeatureGeometry { + const olGeoJSON = new OlGeoJSON(); + const bufferedGeom = olGeoJSON.writeGeometryObject(olGeometry); + const buffered = buffer(turfFeature(bufferedGeom), dist, { units }); + return buffered.geometry; +} diff --git a/packages/geo/src/lib/workspace/widgets/interactive-selection/interactive-selection.component.html b/packages/geo/src/lib/workspace/widgets/interactive-selection/interactive-selection.component.html new file mode 100644 index 0000000000..5b974815fa --- /dev/null +++ b/packages/geo/src/lib/workspace/widgets/interactive-selection/interactive-selection.component.html @@ -0,0 +1,29 @@ + +
+ +
+ +
+ + +
+
diff --git a/packages/geo/src/lib/workspace/widgets/interactive-selection/interactive-selection.component.scss b/packages/geo/src/lib/workspace/widgets/interactive-selection/interactive-selection.component.scss new file mode 100644 index 0000000000..25fa2079c3 --- /dev/null +++ b/packages/geo/src/lib/workspace/widgets/interactive-selection/interactive-selection.component.scss @@ -0,0 +1,16 @@ +:host { + .actions-container { + button:not(:last-child) { + margin-right: 8px; + } + } + .form-container { + width: 100%; + padding: 10px; + + igo-form-field { + display: block; + height: auto; + } + } +} diff --git a/packages/geo/src/lib/workspace/widgets/interactive-selection/interactive-selection.component.ts b/packages/geo/src/lib/workspace/widgets/interactive-selection/interactive-selection.component.ts new file mode 100644 index 0000000000..b711f127dd --- /dev/null +++ b/packages/geo/src/lib/workspace/widgets/interactive-selection/interactive-selection.component.ts @@ -0,0 +1,325 @@ +import { CommonModule } from '@angular/common'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + Input, + OnDestroy, + OnInit, + Output +} from '@angular/core'; +import { Validators } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; + +import { OnUpdateInputs } from '@igo2/common/dynamic-component'; +import { + Form, + FormComponent, + FormFieldComponent, + FormService +} from '@igo2/common/form'; +import { WidgetComponent } from '@igo2/common/widget'; +import { IgoLanguageModule, LanguageService } from '@igo2/core/language'; + +import * as olstyle from 'ol/style'; + +import { BehaviorSubject, Observable, Subscription, pairwise } from 'rxjs'; + +import { FeatureStore, featureToOl } from '../../../feature'; +import { FEATURE } from '../../../feature/shared/feature.enums'; +import { + Feature, + FeatureGeometry +} from '../../../feature/shared/feature.interfaces'; +import { + bufferOlGeometry, + doesOlGeometryIntersects +} from '../../../geometry/shared/geometry.utils'; +import { IgoMap } from '../../../map/shared/map'; +import { FeatureWorkspace } from '../../shared/feature-workspace'; +import { WfsWorkspace } from '../../shared/wfs-workspace'; + +interface DataSelectionData { + geometry?: FeatureGeometry; + action?: SelectionAction; + buffer?: string; +} + +enum SelectionAction { + New = 'new', + Add = 'add', + Remove = 'remove' +} + +@Component({ + selector: 'igo-interactive-selection', + templateUrl: './interactive-selection.component.html', + styleUrls: ['./interactive-selection.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + CommonModule, + MatButtonModule, + IgoLanguageModule, + FormComponent, + FormFieldComponent + ] +}) +export class InteractiveSelectionFormComponent + implements OnInit, OnDestroy, OnUpdateInputs, WidgetComponent +{ + public form$ = new BehaviorSubject
(undefined); + public submitButtonText$ = new BehaviorSubject(undefined); + public submitDisabled = true; + private valueChanges$$: Subscription; + public data$ = new BehaviorSubject({ + geometry: undefined, + buffer: undefined, + action: SelectionAction.Add + }); + @Input() map: IgoMap; + @Input() workspace: FeatureWorkspace | WfsWorkspace; + + /** + * Event emitted on complete + */ + @Output() complete = new EventEmitter(); + + /** + * Event emitted on cancel + */ + @Output() cancel = new EventEmitter(); + + constructor( + private cdRef: ChangeDetectorRef, + private formService: FormService, + private languageService: LanguageService + ) {} + ngOnInit(): void { + const fieldConfigs = [ + { + name: 'geometry', + title: '', + type: 'geometry', + options: { + validator: Validators.required + }, + inputs: { + map: this.map, + geometryTypeField: true, + geometryType: 'Polygon', + drawGuideField: false, + drawGuide: 0, + drawGuidePlaceholder: '', + 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] + }) + }) + }), + overlayStyle: new olstyle.Style({ + stroke: new olstyle.Stroke({ + color: [0, 255, 0, 1], + width: 2 + }), + fill: new olstyle.Fill({ + color: [0, 255, 0, 0.2] + }), + image: new olstyle.Circle({ + radius: 8, + stroke: new olstyle.Stroke({ + color: [0, 255, 0, 1] + }), + fill: new olstyle.Fill({ + color: [0, 255, 0, 0.2] + }) + }) + }) + } + }, + { + name: 'action', + title: this.languageService.translate.instant( + 'igo.geo.workspace.widget.interactiveSelection.action.title' + ), + type: 'select', + options: { + cols: 1, + validator: Validators.required + }, + inputs: { + choices: [ + { + value: SelectionAction.New, + title: this.languageService.translate.instant( + 'igo.geo.workspace.widget.interactiveSelection.selection.new' + ) + }, + { + value: SelectionAction.Add, + title: this.languageService.translate.instant( + 'igo.geo.workspace.widget.interactiveSelection.selection.add' + ) + }, + { + value: SelectionAction.Remove, + title: this.languageService.translate.instant( + 'igo.geo.workspace.widget.interactiveSelection.selection.remove' + ) + } + ] + } + }, + { + name: 'buffer', + title: this.languageService.translate.instant( + 'igo.geo.workspace.widget.interactiveSelection.buffer.title' + ), + type: 'text', + options: { + cols: 1, + validator: Validators.pattern(/^\d+$/) + } + } + ]; + this.setAction(SelectionAction.Add); + + const fields = fieldConfigs.map((config) => this.formService.field(config)); + const form = this.formService.form(fields, []); + + this.valueChanges$$ = ( + form.control.valueChanges satisfies Observable + ) + .pipe(pairwise()) + .subscribe(([previous, current]) => { + const currentValues = current; + if (previous?.action !== current.action) { + currentValues.buffer = undefined; + } + this.data$.next(currentValues); + this.submitDisabled = !form.control.valid; + if ( + !currentValues || + !currentValues.action || + currentValues.action === SelectionAction.Add + ) { + this.setAction(SelectionAction.Add); + } else if (currentValues.action === SelectionAction.New) { + this.setAction(SelectionAction.New); + } else if (currentValues.action === SelectionAction.Remove) { + this.setAction(SelectionAction.Remove); + } + }); + + this.form$.next(form); + } + + private setAction(action: SelectionAction) { + this.submitButtonText$.next( + `igo.geo.workspace.widget.interactiveSelection.selection.${action}` + ); + } + + ngOnDestroy() { + this.valueChanges$$.unsubscribe(); + } + + /** + * Implemented as part of OnUpdateInputs + */ + onUpdateInputs() { + this.cdRef.detectChanges(); + } + + /** + * On close, emit the cancel event + */ + onClose() { + this.cancel.emit(); + } + + onSubmit(data: DataSelectionData) { + const featureStore = this.workspace.entityStore as FeatureStore; + const storeFeatures = featureStore.all(); + + const buffer = data.buffer ? +data.buffer : undefined; + + const formFeature: Feature = { + type: FEATURE, + geometry: data.geometry, + projection: 'EPSG:4326', + properties: {} + }; + let olFormFeature = featureToOl(formFeature, 'EPSG:4326'); + if (buffer) { + const bufferedGeom = bufferOlGeometry( + olFormFeature.getGeometry(), + buffer, + 'meters' + ); + const bufferedFeature: Feature = { + type: FEATURE, + geometry: bufferedGeom, + projection: 'EPSG:4326', + properties: {} + }; + olFormFeature = featureToOl(bufferedFeature, 'EPSG:4326'); + } + + const intersectingFeatures = storeFeatures + .map((storeFeature) => { + const doesIntersects = doesOlGeometryIntersects( + featureToOl(storeFeature, 'EPSG:4326').getGeometry(), + olFormFeature.getGeometry() + ); + return doesIntersects ? storeFeature : undefined; + }) + .filter((f) => f); + + let selectedStateToApply = false; + let exclusive = false; + if (intersectingFeatures.length) { + if ([SelectionAction.New, SelectionAction.Add].includes(data.action)) { + selectedStateToApply = true; + exclusive = data.action === SelectionAction.New ? true : false; + } else if (SelectionAction.Remove) { + selectedStateToApply = false; + exclusive = false; + } + featureStore.state.updateMany( + intersectingFeatures, + { selected: selectedStateToApply }, + exclusive + ); + } + this.data$.next( + Object.assign({}, this.data$.getValue(), { geometry: undefined }) + ); + } + + clear() { + const featureStore = this.workspace.entityStore as FeatureStore; + featureStore.state.updateAll({ selected: false }); + this.form$.value.control.reset(); + this.setAction(SelectionAction.Add); + this.data$.next( + Object.assign({}, this.data$.getValue(), { + geometry: undefined, + action: SelectionAction.Add + }) + ); + } +} diff --git a/packages/geo/src/lib/workspace/widgets/ogc-filter/ogc-filter.component.scss b/packages/geo/src/lib/workspace/widgets/ogc-filter/ogc-filter.component.scss index e69de29bb2..08464da66b 100644 --- a/packages/geo/src/lib/workspace/widgets/ogc-filter/ogc-filter.component.scss +++ b/packages/geo/src/lib/workspace/widgets/ogc-filter/ogc-filter.component.scss @@ -0,0 +1,6 @@ +:host { + igo-ogc-filterable-item { + width: 95%; + display: grid; + } +} diff --git a/packages/geo/src/lib/workspace/widgets/widgets.ts b/packages/geo/src/lib/workspace/widgets/widgets.ts index cc18ca71b2..0680bb2d48 100644 --- a/packages/geo/src/lib/workspace/widgets/widgets.ts +++ b/packages/geo/src/lib/workspace/widgets/widgets.ts @@ -2,9 +2,13 @@ import { InjectionToken } from '@angular/core'; import { Widget, WidgetService } from '@igo2/common/widget'; +import { InteractiveSelectionFormComponent } from './interactive-selection/interactive-selection.component'; import { OgcFilterComponent } from './ogc-filter/ogc-filter.component'; export const OgcFilterWidget = new InjectionToken('OgcFilterWidget'); +export const InteractiveSelectionFormWidget = new InjectionToken( + 'InteractiveSelectionFormWidget' +); export function ogcFilterWidgetFactory(widgetService: WidgetService): Widget { return widgetService.create(OgcFilterComponent); @@ -17,3 +21,17 @@ export function provideOgcFilterWidget() { deps: [WidgetService] }; } + +export function interactiveSelectionFormWidgetFactory( + widgetService: WidgetService +): Widget { + return widgetService.create(InteractiveSelectionFormComponent); +} + +export function provideInteractiveSelectionFormWidget() { + return { + provide: InteractiveSelectionFormWidget, + useFactory: interactiveSelectionFormWidgetFactory, + deps: [WidgetService] + }; +} diff --git a/packages/geo/src/lib/workspace/workspace.module.ts b/packages/geo/src/lib/workspace/workspace.module.ts index 6cfc785e8a..6c86531cf7 100644 --- a/packages/geo/src/lib/workspace/workspace.module.ts +++ b/packages/geo/src/lib/workspace/workspace.module.ts @@ -4,7 +4,10 @@ import { MatDialogModule } from '@angular/material/dialog'; import { IgoWidgetModule } from '@igo2/common/widget'; import { IgoOgcFilterModule } from './widgets/ogc-filter/ogc-filter.module'; -import { provideOgcFilterWidget } from './widgets/widgets'; +import { + provideInteractiveSelectionFormWidget, + provideOgcFilterWidget +} from './widgets/widgets'; import { IgoWorkspaceSelectorModule } from './workspace-selector/workspace-selector.module'; @NgModule({ @@ -15,6 +18,6 @@ import { IgoWorkspaceSelectorModule } from './workspace-selector/workspace-selec MatDialogModule ], exports: [IgoOgcFilterModule], - providers: [provideOgcFilterWidget()] + providers: [provideOgcFilterWidget(), provideInteractiveSelectionFormWidget()] }) export class IgoGeoWorkspaceModule {} diff --git a/packages/geo/src/locale/en.geo.json b/packages/geo/src/locale/en.geo.json index 83decf79a5..00f20f8121 100644 --- a/packages/geo/src/locale/en.geo.json +++ b/packages/geo/src/locale/en.geo.json @@ -822,8 +822,20 @@ "addError": "An error has occured. Entity could not be added.", "modifyError": "An error has occured. Entity could not be modified.", "cancel": "Cancel", - "inMapExtent.active.tooltip": "Ne montrer que les entités contenues dans la carte", - "inMapExtent.inactive.tooltip": "Montrer tous les entités" + "inMapExtent.active.tooltip": "Show only features contained in the map", + "inMapExtent.inactive.tooltip": "Show all features", + "widget": { + "interactiveSelection": { + "selection.add": "Add to the current selection", + "selection.new": "Select", + "selection.remove": "Remove from the current selection", + "reset": { + "button": "Reset" + }, + "action.title": "How to handle results", + "buffer.title": "Buffer size (meters)" + } + } }, "formValidation": { "mandatory": "{{column}} is mandatory", diff --git a/packages/geo/src/locale/fr.geo.json b/packages/geo/src/locale/fr.geo.json index 2d5b26a54b..50ea40a8ba 100644 --- a/packages/geo/src/locale/fr.geo.json +++ b/packages/geo/src/locale/fr.geo.json @@ -823,7 +823,19 @@ "modifyError": "Un problème est survenu. L'entité n'a pas pu être modifiée.", "cancel": "Annuler", "inMapExtent.active.tooltip": "Ne montrer que les enregistrements contenus dans la carte", - "inMapExtent.inactive.tooltip": "Montrer tous les enregistrements" + "inMapExtent.inactive.tooltip": "Montrer tous les enregistrements", + "widget": { + "interactiveSelection": { + "selection.add": "Ajouter à la présente sélection", + "selection.new": "Effectuer la sélection", + "selection.remove": "Supprimer les résultats de la sélection", + "reset": { + "button": "Réinitialiser" + }, + "action.title": "Action à faire avec le résultat de la présente sélection", + "buffer.title": "Taille de la zone tampon en mètre" + } + } }, "formValidation": { "mandatory": "{{column}}est un champ obligatoire", diff --git a/packages/integration/src/lib/workspace/shared/feature-actions.service.ts b/packages/integration/src/lib/workspace/shared/feature-actions.service.ts index f280be4e0a..aeb6441e71 100644 --- a/packages/integration/src/lib/workspace/shared/feature-actions.service.ts +++ b/packages/integration/src/lib/workspace/shared/feature-actions.service.ts @@ -1,6 +1,7 @@ -import { Injectable, OnDestroy } from '@angular/core'; +import { Inject, Injectable, OnDestroy } from '@angular/core'; import { Action } from '@igo2/common/action'; +import { Widget } from '@igo2/common/widget'; import { LanguageService } from '@igo2/core/language'; import { MediaService } from '@igo2/core/media'; import { @@ -8,7 +9,7 @@ import { StorageServiceEvent, StorageServiceEventEnum } from '@igo2/core/storage'; -import { FeatureWorkspace } from '@igo2/geo'; +import { FeatureWorkspace, InteractiveSelectionFormWidget } from '@igo2/geo'; import { BehaviorSubject, Subscription } from 'rxjs'; import { skipWhile } from 'rxjs/operators'; @@ -35,6 +36,8 @@ export class FeatureActionsService implements OnDestroy { } constructor( + @Inject(InteractiveSelectionFormWidget) + private interactiveSelectionFormWidget: Widget, private storageState: StorageState, public languageService: LanguageService, private toolState: ToolState, @@ -92,7 +95,8 @@ export class FeatureActionsService implements OnDestroy { this.storageService, this.languageService, this.mediaService, - this.toolState + this.toolState, + this.interactiveSelectionFormWidget ); } } diff --git a/packages/integration/src/lib/workspace/shared/wfs-actions.service.ts b/packages/integration/src/lib/workspace/shared/wfs-actions.service.ts index b37a0fd80c..290cb8a932 100644 --- a/packages/integration/src/lib/workspace/shared/wfs-actions.service.ts +++ b/packages/integration/src/lib/workspace/shared/wfs-actions.service.ts @@ -9,7 +9,11 @@ import { StorageServiceEvent, StorageServiceEventEnum } from '@igo2/core/storage'; -import { OgcFilterWidget, WfsWorkspace } from '@igo2/geo'; +import { + InteractiveSelectionFormWidget, + OgcFilterWidget, + WfsWorkspace +} from '@igo2/geo'; import { BehaviorSubject, Subscription } from 'rxjs'; import { skipWhile } from 'rxjs/operators'; @@ -38,6 +42,9 @@ export class WfsActionsService implements OnDestroy { } constructor( + @Optional() + @Inject(InteractiveSelectionFormWidget) + private interactiveSelectionFormWidget: Widget, @Optional() @Inject(OgcFilterWidget) private ogcFilterWidget: Widget, @@ -98,7 +105,8 @@ export class WfsActionsService implements OnDestroy { this.storageService, this.languageService, this.mediaService, - this.toolState + this.toolState, + this.interactiveSelectionFormWidget ); } } diff --git a/packages/integration/src/lib/workspace/shared/workspace.utils.ts b/packages/integration/src/lib/workspace/shared/workspace.utils.ts index 28099f417e..f153e2b8d4 100644 --- a/packages/integration/src/lib/workspace/shared/workspace.utils.ts +++ b/packages/integration/src/lib/workspace/shared/workspace.utils.ts @@ -49,7 +49,8 @@ export function getWorkspaceActions( storageService: StorageService, languageService: LanguageService, mediaService: MediaService, - toolState: ToolState + toolState: ToolState, + interactiveSelectionFormWidget?: Widget ): Action[] { const actions = [ { @@ -142,6 +143,19 @@ export function getWorkspaceActions( }, args: [ogcFilterWidget, workspace] }, + { + id: 'interactiveSelect', + icon: 'select-marker', + title: 'igo.integration.workspace.interactiveSelection.title', + tooltip: 'igo.integration.workspace.interactiveSelection.tooltip', + handler: (widget: Widget, ws: FeatureWorkspace | WfsWorkspace) => { + ws.activateWidget(widget, { + map: ws.map, + workspace: ws + }); + }, + args: [interactiveSelectionFormWidget, workspace] + }, { id: 'maximize', title: languageService.translate.instant( diff --git a/packages/integration/src/locale/en.integration.json b/packages/integration/src/locale/en.integration.json index 7b9b3ae70f..dd8cf41115 100644 --- a/packages/integration/src/locale/en.integration.json +++ b/packages/integration/src/locale/en.integration.json @@ -101,7 +101,9 @@ "maximize": "Extend", "maximizeTooltip": "Extend panel", "standardExtent": "Reduce", - "standardExtentTooltip": "Reduce panel" + "standardExtentTooltip": "Reduce panel", + "interactiveSelection.title": "Select features by geometry", + "interactiveSelection.tooltip": "Select features by geometry" }, "directions": { "warning": { diff --git a/packages/integration/src/locale/fr.integration.json b/packages/integration/src/locale/fr.integration.json index 9c88f82417..3329f43a5f 100644 --- a/packages/integration/src/locale/fr.integration.json +++ b/packages/integration/src/locale/fr.integration.json @@ -102,7 +102,9 @@ "maximize": "Agrandir", "maximizeTooltip": "Agrandir la fenêtre", "standardExtent": "Réduire", - "standardExtentTooltip": "Réduire la fenêtre" + "standardExtentTooltip": "Réduire la fenêtre", + "interactiveSelection.title": "Sélectioner les entités par géométrie", + "interactiveSelection.tooltip": "Sélectioner les entités par géométrie" }, "directions": { "warning": {