Skip to content

Commit 1600cd8

Browse files
committed
WIP update edit component to enable copying other layers' geometry
1 parent 394dbef commit 1600cd8

File tree

11 files changed

+284
-74
lines changed

11 files changed

+284
-74
lines changed

projects/core/assets/locale/messages.core.nl.xlf

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -472,11 +472,11 @@
472472
</trans-unit>
473473
<trans-unit id="core.edit.select-feature-to-edit" datatype="html">
474474
<source>Select which feature to edit</source>
475-
<target>Selecteer een object om te bewerken</target>
475+
<target>Object om te bewerken</target>
476476
</trans-unit>
477477
<trans-unit id="core.edit.select-layer" datatype="html">
478478
<source>Select layer to edit</source>
479-
<target>Selecteer een laag om te bewerken</target>
479+
<target>Laag om te bewerken</target>
480480
</trans-unit>
481481
<trans-unit id="core.edit.true" datatype="html">
482482
<source>True</source>

projects/core/src/lib/components/edit/edit-dialog/edit-dialog.component.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,10 @@ export class EditDialogComponent {
9696
this.resetChanges();
9797
});
9898

99-
this.editMapToolService.editedGeometry$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(geometry => {
99+
this.editMapToolService.allEditGeometry$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(geometry => {
100100
this.geometryChanged(geometry, false);
101101
});
102-
this.editMapToolService.createdGeometry$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(geometry => {
103-
this.geometryChanged(geometry, true);
104-
});
102+
105103
ComponentConfigHelper.useInitialConfigForComponent<EditConfigModel>(
106104
this.store$,
107105
BaseComponentTypeEnum.EDIT,

projects/core/src/lib/components/edit/edit/edit.component.css

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,18 @@
1818
border-radius: 3px;
1919
}
2020

21-
.map-control-button--add-feature {
21+
.edit-wrapper {
22+
flex: 1;
23+
}
24+
25+
.edit-controls button {
2226
left: 3px;
2327
}
2428

25-
.edit-wrapper {
26-
flex: 1;
29+
.edit-controls {
30+
display: flex;
31+
flex-direction: column;
32+
height: 100%;
2733
}
2834

2935
.edit-container mat-form-field {

projects/core/src/lib/components/edit/edit/edit.component.html

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -28,38 +28,54 @@
2828
}
2929
</ng-container>
3030
</div>
31-
}
32-
@if (!isLine() && !isPoint()) {
33-
<button [matMenuTriggerFor]="geometryTypeMenu"
34-
[class.disabled]="layer.value === null"
35-
tmTooltip="Add new feature"
36-
i18n-tmTooltip="@@core.edit.add-new-feature"
37-
class="map-control-button map-control-button--add-feature"
38-
mat-flat-button>
39-
<mat-icon svgIcon="new_object"></mat-icon>
40-
</button>
41-
<mat-menu #geometryTypeMenu="matMenu" xPosition="after" yPosition="below">
42-
@if (isPolygon()) {
43-
<button mat-menu-item (click)="createFeature('area')" i18n="@@core.common.polygon">Polygon</button>
44-
<button mat-menu-item (click)="createFeature('rectangle')" i18n="@@core.common.rectangle">Rectangle</button>
45-
<button mat-menu-item (click)="createFeature('square')" i18n="@@core.common.square">Square</button>
31+
<div class="edit-controls">
32+
@if (!isLine() && !isPoint()) {
33+
<button [matMenuTriggerFor]="geometryTypeMenu"
34+
[class.disabled]="layer.value === null"
35+
tmTooltip="Add new feature"
36+
i18n-tmTooltip="@@core.edit.add-new-feature"
37+
class="map-control-button map-control-button--add-feature"
38+
mat-flat-button>
39+
<mat-icon svgIcon="new_object"></mat-icon>
40+
</button>
41+
<mat-menu #geometryTypeMenu="matMenu" xPosition="after" yPosition="below">
42+
@if (isPolygon()) {
43+
<button mat-menu-item (click)="createFeature('area')" i18n="@@core.common.polygon">Polygon</button>
44+
<button mat-menu-item (click)="createFeature('rectangle')" i18n="@@core.common.rectangle">Rectangle</button>
45+
<button mat-menu-item (click)="createFeature('square')" i18n="@@core.common.square">Square</button>
46+
} @else {
47+
<button mat-menu-item (click)="createFeature('point')" i18n="@@core.common.point">Point</button>
48+
<button mat-menu-item (click)="createFeature('line')" i18n="@@core.common.line">Line</button>
49+
<button mat-menu-item (click)="createFeature('area')" i18n="@@core.common.polygon">Polygon</button>
50+
<button mat-menu-item (click)="createFeature('rectangle')" i18n="@@core.common.rectangle">Rectangle</button>
51+
<button mat-menu-item (click)="createFeature('square')" i18n="@@core.common.square">Square</button>
52+
}
53+
</mat-menu>
4654
} @else {
47-
<button mat-menu-item (click)="createFeature('point')" i18n="@@core.common.point">Point</button>
48-
<button mat-menu-item (click)="createFeature('line')" i18n="@@core.common.line">Line</button>
49-
<button mat-menu-item (click)="createFeature('area')" i18n="@@core.common.polygon">Polygon</button>
50-
<button mat-menu-item (click)="createFeature('rectangle')" i18n="@@core.common.rectangle">Rectangle</button>
51-
<button mat-menu-item (click)="createFeature('square')" i18n="@@core.common.square">Square</button>
55+
<button [class.disabled]="layer.value === null"
56+
tmTooltip="Add new feature"
57+
i18n-tmTooltip="@@core.edit.add-new-feature"
58+
class="map-control-button map-control-button--add-feature"
59+
(click)="createFeatureIfSingleGeometryType()"
60+
mat-flat-button>
61+
<mat-icon svgIcon="new_object"></mat-icon>
62+
</button>
63+
}
64+
@if (layersToCreateNewFeaturesFrom().length > 0) {
65+
<button [matMenuTriggerFor]="layerToCreateNewFeaturesFromMenu"
66+
tmTooltip="Copy from other layer"
67+
class="map-control-button map-control-button--copy-feature"
68+
[class.active]="layer.value !== null"
69+
mat-flat-button>
70+
<mat-icon svgIcon="split"></mat-icon>
71+
</button>
72+
<mat-menu #layerToCreateNewFeaturesFromMenu="matMenu" xPosition="after" yPosition="below">
73+
@for (layer of layersToCreateNewFeaturesFrom(); track layer) {
74+
<button mat-menu-item (click)="createFeatureFromLayer(layer.id)">{{layer.title || layer.layerName}}</button>
75+
}
76+
</mat-menu>
5277
}
53-
</mat-menu>
54-
} @else {
55-
<button [class.disabled]="layer.value === null"
56-
tmTooltip="Add new feature"
57-
i18n-tmTooltip="@@core.edit.add-new-feature"
58-
class="map-control-button map-control-button--add-feature"
59-
(click)="createFeatureIfSingleGeometryType()"
60-
mat-flat-button>
61-
<mat-icon svgIcon="new_object"></mat-icon>
62-
</button>
78+
</div>
6379
}
6480
</div>
6581
}

projects/core/src/lib/components/edit/edit/edit.component.ts

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1-
import { ChangeDetectionStrategy, Component, DestroyRef, OnInit, inject } from '@angular/core';
1+
import { ChangeDetectionStrategy, Component, DestroyRef, OnInit, inject, signal } from '@angular/core';
22
import { selectEditActive, selectSelectedEditLayer } from '../state/edit.selectors';
33
import { Store } from '@ngrx/store';
44
import { combineLatest, of, take } from 'rxjs';
5-
import { setEditActive, setEditCreateNewFeatureActive, setSelectedEditLayer } from '../state/edit.actions';
5+
import {
6+
setEditActive, setEditCopyOtherLayerFeaturesActive, setEditCreateNewFeatureActive, setSelectedEditLayer,
7+
} from '../state/edit.actions';
68
import { FormControl } from '@angular/forms';
7-
import { selectEditableLayers } from '../../../map/state/map.selectors';
9+
import { selectEditableLayers, selectOrderedVisibleLayersWithServices } from '../../../map/state/map.selectors';
810
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
911
import { switchMap, withLatestFrom } from 'rxjs/operators';
1012
import { hideFeatureInfoDialog } from '../../feature-info/state/feature-info.actions';
1113
import { ApplicationLayerService } from '../../../map/services/application-layer.service';
12-
import { AttributeType, AuthenticatedUserService, GeometryType } from '@tailormap-viewer/api';
14+
import { AppLayerModel, AttributeType, AuthenticatedUserService, GeometryType } from '@tailormap-viewer/api';
1315
import { activateTool } from '../../toolbar/state/toolbar.actions';
1416
import { ToolbarComponentEnum } from '../../toolbar/models/toolbar-component.enum';
15-
import { DrawingType } from '@tailormap-viewer/map';
17+
import { DrawingType, MapService, ScaleHelper } from '@tailormap-viewer/map';
1618

1719
@Component({
1820
selector: 'tm-edit',
@@ -26,13 +28,15 @@ export class EditComponent implements OnInit {
2628
private destroyRef = inject(DestroyRef);
2729
private applicationLayerService = inject(ApplicationLayerService);
2830
private authenticatedUserService = inject(AuthenticatedUserService);
29-
31+
private mapService = inject(MapService);
3032

3133
public active$ = this.store$.select(selectEditActive);
3234
public editableLayers$ = this.store$.select(selectEditableLayers);
3335
public layer = new FormControl();
3436
public editGeometryType: GeometryType | null = null;
3537

38+
public layersToCreateNewFeaturesFrom = signal<AppLayerModel[]>([]);
39+
3640
private defaultTooltip = $localize `:@@core.edit.edit-feature-tooltip:Edit feature`;
3741
private notLoggedInTooltip = $localize `:@@core.edit.require-login-tooltip:You must be logged in to edit.`;
3842
private noLayersTooltip = $localize `:@@core.edit.no-editable-layers-tooltip:There are no editable layers. Enable a layer to start editing.`;
@@ -72,6 +76,17 @@ export class EditComponent implements OnInit {
7276
this.toggle(true);
7377
}
7478
});
79+
80+
combineLatest([ this.store$.select(selectSelectedEditLayer),
81+
this.store$.select(selectOrderedVisibleLayersWithServices),
82+
this.mapService.getMapViewDetails$() ]).pipe(
83+
takeUntilDestroyed(this.destroyRef),
84+
).subscribe(([ selectedEditLayerId, visibleLayers, mapViewDetails ]) => {
85+
const layers = selectedEditLayerId == null ? [] : visibleLayers.filter(layer =>
86+
layer.id !== selectedEditLayerId
87+
&& ScaleHelper.isInScale(mapViewDetails.scale, layer.minScale, layer.maxScale));
88+
this.layersToCreateNewFeaturesFrom.set(layers);
89+
});
7590
}
7691

7792
public isLine() {
@@ -152,4 +167,25 @@ export class EditComponent implements OnInit {
152167
}
153168
}
154169

170+
public createFeatureFromLayer(id: string) {
171+
// get layer attribute details for edit form
172+
this.applicationLayerService.getLayerDetails$(this.layer.value)
173+
.pipe(take(1))
174+
.subscribe(layerDetails => {
175+
// show edit dialog
176+
this.store$.dispatch(setEditCopyOtherLayerFeaturesActive({
177+
active: true,
178+
layerId: id,
179+
columnMetadata: layerDetails.details.attributes.map(attribute => {
180+
return {
181+
layerId: layerDetails.details.id,
182+
name: attribute.name,
183+
type: attribute.type as unknown as AttributeType,
184+
alias: attribute.editAlias,
185+
};
186+
},
187+
),
188+
}));
189+
});
190+
}
155191
}

projects/core/src/lib/components/edit/services/edit-map-tool.service.ts

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { DestroyRef, Injectable, OnDestroy, inject } from '@angular/core';
22
import {
33
DrawingToolConfigModel,
44
DrawingToolEvent,
5-
DrawingToolModel,
5+
DrawingToolModel, FeatureHelper,
66
MapClickToolConfigModel,
77
MapClickToolModel,
88
MapService,
@@ -12,12 +12,14 @@ import {
1212
ToolTypeEnum,
1313
} from '@tailormap-viewer/map';
1414
import { Store } from '@ngrx/store';
15-
import { selectEditStatus, selectEditError$, selectNewFeatureGeometryType, selectSelectedEditFeature } from '../state/edit.selectors';
15+
import {
16+
selectEditStatus, selectEditError$, selectNewFeatureGeometryType, selectSelectedEditFeature, selectCopiedFeatures,
17+
} from '../state/edit.selectors';
1618
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
17-
import { BehaviorSubject, combineLatest, concatMap, forkJoin, map, Observable, of, switchMap, take, tap } from 'rxjs';
19+
import { BehaviorSubject, combineLatest, concatMap, filter, forkJoin, map, merge, Observable, of, switchMap, take, tap } from 'rxjs';
1820
import { deregisterTool, registerTool } from '../../toolbar/state/toolbar.actions';
1921
import { ToolbarComponentEnum } from '../../toolbar/models/toolbar-component.enum';
20-
import { loadEditFeatures } from '../state/edit.actions';
22+
import { loadCopyFeatures, loadEditFeatures } from '../state/edit.actions';
2123
import { SnackBarMessageComponent, SnackBarMessageOptionsModel } from '@tailormap-viewer/shared';
2224
import { MatSnackBar } from '@angular/material/snack-bar';
2325
import { withLatestFrom } from 'rxjs/operators';
@@ -34,7 +36,6 @@ export class EditMapToolService implements OnDestroy {
3436
private applicationLayerService = inject(ApplicationLayerService);
3537
private destroyRef = inject(DestroyRef);
3638

37-
3839
private static DEFAULT_ERROR_MESSAGE = $localize `:@@core.edit.error-getting-features:Something went wrong while getting editable features, please try again`;
3940
private static DEFAULT_NO_FEATURES_FOUND_MESSAGE = $localize `:@@core.edit.no-features-found:No editable features found`;
4041

@@ -43,10 +44,10 @@ export class EditMapToolService implements OnDestroy {
4344
private createGeometryToolId = '';
4445

4546
private createdGeometrySubject = new BehaviorSubject<string | null>(null);
46-
public createdGeometry$ = this.createdGeometrySubject.asObservable();
47-
4847
private editedGeometrySubject = new BehaviorSubject<string | null>(null);
49-
public editedGeometry$ = this.editedGeometrySubject.asObservable();
48+
private readonly copiedGeometry$;
49+
50+
public allEditGeometry$;
5051

5152
constructor() {
5253

@@ -88,6 +89,27 @@ export class EditMapToolService implements OnDestroy {
8889
this.handleEditGeometryModified(modifiedGeometry);
8990
});
9091

92+
this.copiedGeometry$ = this.store$.select(selectCopiedFeatures).pipe(
93+
map(copiedFeatures => {
94+
if (copiedFeatures.length === 0) {
95+
return null;
96+
}
97+
return copiedFeatures
98+
.map(feature => feature.geometry!)
99+
.reduce((previousValue, currentValue) =>
100+
FeatureHelper.getWKT(FeatureHelper.appendMultiGeometryWKT(previousValue, currentValue)));
101+
}));
102+
103+
this.allEditGeometry$ = merge(
104+
this.editedGeometrySubject.asObservable(),
105+
this.createdGeometrySubject.asObservable(),
106+
this.copiedGeometry$,
107+
);
108+
109+
this.mapService.renderFeatures$("copied-features-geometry", this.copiedGeometry$, style)
110+
.pipe(takeUntilDestroyed(this.destroyRef))
111+
.subscribe();
112+
91113
this.mapService.renderFeatures$("create-feature-geometry", this.createdGeometrySubject.asObservable(), style)
92114
.pipe(takeUntilDestroyed(this.destroyRef))
93115
.subscribe();
@@ -125,11 +147,11 @@ export class EditMapToolService implements OnDestroy {
125147
toolManager.disableTool(this.editMapClickToolId, true);
126148
toolManager.disableTool(this.editGeometryToolId, true);
127149
toolManager.disableTool(this.createGeometryToolId, false);
128-
} else if(editStatus === 'active') {
150+
} else if(editStatus === 'active' || editStatus === 'copy_features') {
129151
toolManager.disableTool(this.editGeometryToolId, true);
130152
toolManager.disableTool(this.createGeometryToolId, true);
131153
toolManager.enableTool(this.editMapClickToolId, true);
132-
} else if(editStatus === 'edit_feature') {
154+
} else if(editStatus === 'edit_feature') {
133155
toolManager.disableTool(this.createGeometryToolId, true);
134156
toolManager.enableTool(this.editMapClickToolId, true);
135157
toolManager.enableTool(this.editGeometryToolId, false, { geometry: editGeometry });
@@ -167,7 +189,14 @@ export class EditMapToolService implements OnDestroy {
167189
}
168190

169191
private handleMapClick(evt: { mapCoordinates: [number, number]; mouseCoordinates: [number, number]; pointerType?: string }) {
170-
this.store$.dispatch(loadEditFeatures({ coordinates: evt.mapCoordinates, pointerType: evt.pointerType }));
192+
this.store$.select(selectEditStatus).pipe(take(1))
193+
.subscribe(status => {
194+
if (status === 'active') {
195+
this.store$.dispatch(loadEditFeatures({ coordinates: evt.mapCoordinates, mouseCoordinates: evt.mouseCoordinates, pointerType: evt.pointerType }));
196+
} else if (status === 'copy_features') {
197+
this.store$.dispatch(loadCopyFeatures({ coordinates: evt.mapCoordinates, mouseCoordinates: evt.mouseCoordinates, pointerType: evt.pointerType }));
198+
}
199+
});
171200
this.store$.pipe(selectEditError$)
172201
.subscribe(error => {
173202
if (!error || error.error === 'none') {

projects/core/src/lib/components/edit/state/edit.actions.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,24 @@ export const setEditCreateNewFeatureActive = createAction(
1717
props<{ active: boolean; geometryType: DrawingType; columnMetadata: FeatureInfoColumnMetadataModel[] }>(),
1818
);
1919

20+
export const setEditCopyOtherLayerFeaturesActive = createAction(
21+
`${editActionsPrefix} Set Copy Other Layer Features Active`,
22+
props<{ active: boolean; layerId: string; columnMetadata: FeatureInfoColumnMetadataModel[] }>(),
23+
);
24+
2025
export const setSelectedEditLayer = createAction(
2126
`${editActionsPrefix} Set Selected Layer`,
2227
props<{ layer: string | null }>(),
2328
);
2429

2530
export const loadEditFeatures = createAction(
2631
`${editActionsPrefix} Load Edit Features`,
27-
props<{ coordinates: [number, number]; pointerType?: string }>(),
32+
props<{ coordinates: [number, number]; mouseCoordinates: [number, number]; pointerType?: string }>(),
33+
);
34+
35+
export const loadCopyFeatures = createAction(
36+
`${editActionsPrefix} Load Copy Features`,
37+
props<{ coordinates: [number, number]; mouseCoordinates: [number, number]; pointerType?: string }>(),
2838
);
2939

3040
export const loadEditFeaturesSuccess = createAction(
@@ -37,6 +47,16 @@ export const loadEditFeaturesFailed = createAction(
3747
props<{ errorMessage?: string }>(),
3848
);
3949

50+
export const loadCopyFeaturesSuccess = createAction(
51+
`${editActionsPrefix} Load Copy Features Success`,
52+
props<{ featureInfo: FeatureInfoResponseModel[] }>(),
53+
);
54+
55+
export const loadCopyFeaturesFailed = createAction(
56+
`${editActionsPrefix} Load Copy Features Failed`,
57+
props<{ errorMessage?: string }>(),
58+
);
59+
4060
export const setSelectedEditFeature = createAction(
4161
`${editActionsPrefix} Set Selected Edit Feature`,
4262
props<{ fid: string | null }>(),

0 commit comments

Comments
 (0)