@open-pioneer/editing 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,236 @@
1
+ import { EventEmitter, createManualPromise } from '@open-pioneer/core';
2
+ import { TOPMOST_LAYER_Z } from '@open-pioneer/map';
3
+ import { Modify } from 'ol/interaction';
4
+ import VectorLayer from 'ol/layer/Vector';
5
+ import VectorSource from 'ol/source/Vector';
6
+ import GeoJSON from 'ol/format/GeoJSON';
7
+ import { unByKey } from 'ol/Observable';
8
+ import { Collection } from 'ol';
9
+ import { createStyles } from './style-utils.js';
10
+ import { saveUpdatedFeature } from './SaveFeaturesHandler.js';
11
+ import { createTooltip } from './Tooltip.js';
12
+
13
+ class EditingUpdateWorkflowImpl extends EventEmitter {
14
+ #waiter;
15
+ _httpService;
16
+ _intl;
17
+ _map;
18
+ _polygonStyle;
19
+ _vertexStyle;
20
+ _state;
21
+ _editLayerURL;
22
+ _featureId;
23
+ _initialFeature;
24
+ _editFeature;
25
+ _editingSource;
26
+ _editingLayer;
27
+ _modifyInteraction;
28
+ _olMap;
29
+ _mapContainer;
30
+ _tooltip;
31
+ _enterHandler;
32
+ _escapeHandler;
33
+ _error;
34
+ _interactionListener;
35
+ _mapListener;
36
+ constructor(options) {
37
+ super();
38
+ this._httpService = options.httpService;
39
+ this._intl = options.intl;
40
+ this._polygonStyle = options.polygonStyle;
41
+ this._vertexStyle = options.vertexStyle;
42
+ this._map = options.map;
43
+ this._olMap = options.map.olMap;
44
+ this._state = "active:initialized";
45
+ this._editLayerURL = options.ogcApiFeatureLayerUrl;
46
+ this._initialFeature = options.feature.clone();
47
+ this._initialFeature.setId(options.feature.getId());
48
+ this._editFeature = options.feature.clone();
49
+ this._editFeature.setId(options.feature.getId());
50
+ this._editFeature.setStyle(
51
+ createStyles({
52
+ polygon: this._polygonStyle,
53
+ vertex: this._vertexStyle
54
+ })
55
+ );
56
+ this._editingSource = new VectorSource({
57
+ features: new Collection([this._editFeature])
58
+ });
59
+ this._editingLayer = new VectorLayer({
60
+ source: this._editingSource,
61
+ zIndex: TOPMOST_LAYER_Z,
62
+ properties: {
63
+ name: "editing-layer"
64
+ }
65
+ });
66
+ this._modifyInteraction = new Modify({
67
+ source: this._editingSource
68
+ });
69
+ this._tooltip = createTooltip(
70
+ this._olMap,
71
+ this._intl.formatMessage({ id: "create.tooltip.deselect" })
72
+ );
73
+ this._enterHandler = (e) => {
74
+ if ((e.code === "Enter" || e.code === "NumpadEnter") && e.target === this._olMap.getTargetElement()) {
75
+ const updatedFeature = this._editingSource.getFeatures()[0];
76
+ if (!updatedFeature) {
77
+ throw Error("no updated feature found");
78
+ }
79
+ this._save(updatedFeature);
80
+ }
81
+ };
82
+ this._escapeHandler = (e) => {
83
+ if (e.code === "Escape" && e.target === this._olMap.getTargetElement()) {
84
+ this.reset();
85
+ }
86
+ };
87
+ this._interactionListener = [];
88
+ this._mapListener = [];
89
+ this._start();
90
+ }
91
+ getModifyInteraction() {
92
+ return this._modifyInteraction;
93
+ }
94
+ getState() {
95
+ return this._state;
96
+ }
97
+ _setState(state) {
98
+ this._state = state;
99
+ this.emit(state);
100
+ }
101
+ _save(feature) {
102
+ this._setState("active:saving");
103
+ const layerUrl = this._editLayerURL;
104
+ this._featureId = feature.getId()?.toString();
105
+ if (!this._featureId) {
106
+ this._destroy();
107
+ this._error = new Error("no feature id available");
108
+ this.#waiter?.reject(this._error);
109
+ return;
110
+ }
111
+ const geometry = feature?.getGeometry();
112
+ if (!geometry) {
113
+ this._destroy();
114
+ this._error = new Error("no geometry available");
115
+ this.#waiter?.reject(this._error);
116
+ return;
117
+ }
118
+ const projection = this._olMap.getView().getProjection();
119
+ const geoJson = new GeoJSON({
120
+ dataProjection: projection
121
+ });
122
+ const geoJSONGeometry = geoJson.writeGeometryObject(geometry, {
123
+ rightHanded: true,
124
+ decimals: 10
125
+ });
126
+ saveUpdatedFeature(
127
+ this._httpService,
128
+ layerUrl,
129
+ this._featureId,
130
+ geoJSONGeometry,
131
+ projection
132
+ ).then((featureId) => {
133
+ this._destroy();
134
+ this.#waiter?.resolve({ featureId });
135
+ }).catch((err) => {
136
+ this._destroy();
137
+ this._error = new Error("Failed to save feature", { cause: err });
138
+ this.#waiter?.reject(this._error);
139
+ });
140
+ }
141
+ _start() {
142
+ this._olMap.addLayer(this._editingLayer);
143
+ this._olMap.addInteraction(this._modifyInteraction);
144
+ const feature = this._editingSource.getFeatures()[0];
145
+ if (feature && !feature.getId()?.toString()) {
146
+ this._destroy();
147
+ this._error = new Error("no feature id available");
148
+ this.#waiter?.reject(this._error);
149
+ return;
150
+ }
151
+ this._mapContainer = this._olMap.getTargetElement() ?? void 0;
152
+ if (this._mapContainer) {
153
+ this._mapContainer.addEventListener("keydown", this._enterHandler, false);
154
+ this._mapContainer.addEventListener("keydown", this._escapeHandler, false);
155
+ }
156
+ this._tooltip.setVisible(true);
157
+ const click = this._map.olMap.on("click", (e) => {
158
+ const coordinate = e.coordinate;
159
+ const altKeyPressed = e.originalEvent.altKey;
160
+ const features = this._editingSource.getFeaturesAtCoordinate(coordinate);
161
+ if (altKeyPressed) {
162
+ return;
163
+ }
164
+ if (features.length === 0) {
165
+ this.triggerSave();
166
+ }
167
+ });
168
+ const modify = this._modifyInteraction.on("modifystart", () => {
169
+ this._setState("active:drawing");
170
+ });
171
+ const changedContainer = this._map.on("changed:container", () => {
172
+ this._mapContainer?.removeEventListener("keydown", this._enterHandler);
173
+ this._mapContainer?.removeEventListener("keydown", this._escapeHandler);
174
+ this._mapContainer = this._olMap.getTargetElement() ?? void 0;
175
+ if (this._mapContainer) {
176
+ this._mapContainer.addEventListener("keydown", this._enterHandler, false);
177
+ this._mapContainer.addEventListener("keydown", this._escapeHandler, false);
178
+ }
179
+ });
180
+ this._interactionListener.push(click, modify);
181
+ this._mapListener.push(changedContainer);
182
+ }
183
+ reset() {
184
+ const geometry = this._initialFeature.getGeometry()?.clone();
185
+ const resetFeature = this._editingSource.getFeatures()[0];
186
+ if (!resetFeature) {
187
+ throw Error("no updated feature found");
188
+ }
189
+ resetFeature.setGeometry(geometry);
190
+ this._setState("active:initialized");
191
+ }
192
+ stop() {
193
+ this._destroy();
194
+ this.#waiter?.resolve(void 0);
195
+ }
196
+ _destroy() {
197
+ this._editingSource.clear();
198
+ this._olMap.removeLayer(this._editingLayer);
199
+ this._olMap.removeInteraction(this._modifyInteraction);
200
+ this._tooltip.destroy();
201
+ this._interactionListener.map((listener) => {
202
+ unByKey(listener);
203
+ });
204
+ this._mapListener.map((listener) => {
205
+ listener.destroy();
206
+ });
207
+ this._mapContainer?.removeEventListener("keydown", this._enterHandler);
208
+ this._mapContainer?.removeEventListener("keydown", this._escapeHandler);
209
+ this._setState("destroyed");
210
+ }
211
+ triggerSave() {
212
+ const feature = this._editingSource.getFeatures()[0];
213
+ if (!feature) {
214
+ throw Error("no updated feature found");
215
+ }
216
+ this._save(feature);
217
+ }
218
+ whenComplete() {
219
+ if (this._state === "destroyed") {
220
+ if (this._error) {
221
+ return Promise.reject(this._error);
222
+ } else {
223
+ if (this._featureId) {
224
+ return Promise.resolve({ featureId: this._featureId });
225
+ } else {
226
+ return Promise.resolve(void 0);
227
+ }
228
+ }
229
+ }
230
+ const manualPromise = this.#waiter ??= createManualPromise();
231
+ return manualPromise.promise;
232
+ }
233
+ }
234
+
235
+ export { EditingUpdateWorkflowImpl };
236
+ //# sourceMappingURL=EditingUpdateWorkflowImpl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EditingUpdateWorkflowImpl.js","sources":["EditingUpdateWorkflowImpl.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport { EventEmitter, ManualPromise, createManualPromise } from \"@open-pioneer/core\";\nimport { MapModel, TOPMOST_LAYER_Z } from \"@open-pioneer/map\";\nimport { Modify } from \"ol/interaction\";\nimport VectorLayer from \"ol/layer/Vector\";\nimport VectorSource from \"ol/source/Vector\";\nimport { HttpService } from \"@open-pioneer/http\";\nimport { FlatStyle } from \"ol/style/flat\";\nimport Feature from \"ol/Feature\";\nimport GeoJSON from \"ol/format/GeoJSON\";\nimport GeoJSONGeometry from \"ol/format/GeoJSON\";\nimport GeoJSONGeometryCollection from \"ol/format/GeoJSON\";\nimport OlMap from \"ol/Map\";\nimport { Resource } from \"@open-pioneer/core\";\nimport { unByKey } from \"ol/Observable\";\nimport { EventsKey } from \"ol/events\";\nimport {\n EditingWorkflowEvents,\n EditingWorkflowState,\n EditingWorkflow,\n EditingWorkflowProps\n} from \"./api\";\nimport { Collection } from \"ol\";\nimport { createStyles } from \"./style-utils\";\nimport { PackageIntl } from \"@open-pioneer/runtime\";\nimport { saveUpdatedFeature } from \"./SaveFeaturesHandler\";\nimport { Tooltip, createTooltip } from \"./Tooltip\";\n\nexport class EditingUpdateWorkflowImpl\n extends EventEmitter<EditingWorkflowEvents>\n implements EditingWorkflow\n{\n #waiter: ManualPromise<Record<string, string> | undefined> | undefined;\n\n private _httpService: HttpService;\n private _intl: PackageIntl;\n\n private _map: MapModel;\n private _polygonStyle: FlatStyle;\n private _vertexStyle: FlatStyle;\n private _state: EditingWorkflowState;\n private _editLayerURL: URL;\n private _featureId: string | undefined;\n\n private _initialFeature: Feature;\n private _editFeature: Feature;\n private _editingSource: VectorSource;\n private _editingLayer: VectorLayer<VectorSource>;\n private _modifyInteraction: Modify;\n private _olMap: OlMap;\n private _mapContainer: HTMLElement | undefined;\n private _tooltip: Tooltip;\n private _enterHandler: (e: KeyboardEvent) => void;\n private _escapeHandler: (e: KeyboardEvent) => void;\n\n private _error: Error | undefined;\n\n private _interactionListener: Array<EventsKey>;\n private _mapListener: Array<Resource>;\n\n constructor(options: { feature: Feature } & EditingWorkflowProps) {\n super();\n this._httpService = options.httpService;\n this._intl = options.intl;\n\n this._polygonStyle = options.polygonStyle;\n this._vertexStyle = options.vertexStyle;\n\n this._map = options.map;\n this._olMap = options.map.olMap;\n this._state = \"active:initialized\";\n this._editLayerURL = options.ogcApiFeatureLayerUrl;\n\n // Save copy of initial state for reset feature\n this._initialFeature = options.feature.clone();\n this._initialFeature.setId(options.feature.getId());\n\n // Work on copied feature to avoid the style to be applied on the original feature\n this._editFeature = options.feature.clone();\n this._editFeature.setId(options.feature.getId());\n\n this._editFeature.setStyle(\n createStyles({\n polygon: this._polygonStyle,\n vertex: this._vertexStyle\n })\n );\n\n this._editingSource = new VectorSource({\n features: new Collection([this._editFeature])\n });\n this._editingLayer = new VectorLayer({\n source: this._editingSource,\n zIndex: TOPMOST_LAYER_Z,\n properties: {\n name: \"editing-layer\"\n }\n });\n\n this._modifyInteraction = new Modify({\n source: this._editingSource\n });\n\n this._tooltip = createTooltip(\n this._olMap,\n this._intl.formatMessage({ id: \"create.tooltip.deselect\" })\n );\n\n this._enterHandler = (e: KeyboardEvent) => {\n if (\n (e.code === \"Enter\" || e.code === \"NumpadEnter\") &&\n e.target === this._olMap.getTargetElement()\n ) {\n const updatedFeature = this._editingSource.getFeatures()[0];\n if (!updatedFeature) {\n throw Error(\"no updated feature found\");\n }\n this._save(updatedFeature);\n }\n };\n\n this._escapeHandler = (e: KeyboardEvent) => {\n if (e.code === \"Escape\" && e.target === this._olMap.getTargetElement()) {\n this.reset();\n }\n };\n\n this._interactionListener = [];\n this._mapListener = [];\n\n this._start();\n }\n\n getModifyInteraction() {\n return this._modifyInteraction;\n }\n\n getState() {\n return this._state;\n }\n\n private _setState(state: EditingWorkflowState) {\n this._state = state;\n this.emit(state);\n }\n\n private _save(feature: Feature) {\n this._setState(\"active:saving\");\n\n const layerUrl = this._editLayerURL;\n\n this._featureId = feature.getId()?.toString();\n if (!this._featureId) {\n this._destroy();\n this._error = new Error(\"no feature id available\");\n this.#waiter?.reject(this._error);\n return;\n }\n\n const geometry = feature?.getGeometry();\n if (!geometry) {\n this._destroy();\n this._error = new Error(\"no geometry available\");\n this.#waiter?.reject(this._error);\n return;\n }\n const projection = this._olMap.getView().getProjection();\n const geoJson = new GeoJSON({\n dataProjection: projection\n });\n const geoJSONGeometry: GeoJSONGeometry | GeoJSONGeometryCollection =\n geoJson.writeGeometryObject(geometry, {\n rightHanded: true,\n decimals: 10\n });\n\n saveUpdatedFeature(\n this._httpService,\n layerUrl,\n this._featureId,\n geoJSONGeometry,\n projection\n )\n .then((featureId) => {\n this._destroy();\n this.#waiter?.resolve({ featureId });\n })\n .catch((err: Error) => {\n this._destroy();\n this._error = new Error(\"Failed to save feature\", { cause: err });\n this.#waiter?.reject(this._error);\n });\n }\n\n private _start() {\n this._olMap.addLayer(this._editingLayer);\n this._olMap.addInteraction(this._modifyInteraction);\n\n const feature = this._editingSource.getFeatures()[0];\n if (feature && !feature.getId()?.toString()) {\n this._destroy();\n this._error = new Error(\"no feature id available\");\n this.#waiter?.reject(this._error);\n return;\n }\n\n // Add EventListener on focused map to abort actual interaction via `Escape`\n this._mapContainer = this._olMap.getTargetElement() ?? undefined;\n if (this._mapContainer) {\n this._mapContainer.addEventListener(\"keydown\", this._enterHandler, false);\n this._mapContainer.addEventListener(\"keydown\", this._escapeHandler, false);\n }\n\n this._tooltip.setVisible(true);\n\n const click = this._map.olMap.on(\"click\", (e) => {\n const coordinate = e.coordinate;\n const altKeyPressed = e.originalEvent.altKey;\n const features = this._editingSource.getFeaturesAtCoordinate(coordinate);\n\n if (altKeyPressed) {\n return;\n }\n\n if (features.length === 0) {\n this.triggerSave();\n }\n });\n\n const modify = this._modifyInteraction.on(\"modifystart\", () => {\n this._setState(\"active:drawing\");\n });\n\n // update event handler when container changes\n const changedContainer = this._map.on(\"changed:container\", () => {\n this._mapContainer?.removeEventListener(\"keydown\", this._enterHandler);\n this._mapContainer?.removeEventListener(\"keydown\", this._escapeHandler);\n\n this._mapContainer = this._olMap.getTargetElement() ?? undefined;\n if (this._mapContainer) {\n this._mapContainer.addEventListener(\"keydown\", this._enterHandler, false);\n this._mapContainer.addEventListener(\"keydown\", this._escapeHandler, false);\n }\n });\n\n this._interactionListener.push(click, modify);\n this._mapListener.push(changedContainer);\n }\n\n reset() {\n // Clone geometry to pass geometry, not reference\n const geometry = this._initialFeature.getGeometry()?.clone();\n\n const resetFeature = this._editingSource.getFeatures()[0];\n if (!resetFeature) {\n throw Error(\"no updated feature found\");\n }\n resetFeature.setGeometry(geometry);\n\n this._setState(\"active:initialized\");\n }\n\n stop() {\n this._destroy();\n this.#waiter?.resolve(undefined);\n }\n\n private _destroy() {\n this._editingSource.clear();\n this._olMap.removeLayer(this._editingLayer);\n this._olMap.removeInteraction(this._modifyInteraction);\n this._tooltip.destroy();\n\n // Remove event listener on interaction and on map\n this._interactionListener.map((listener) => {\n unByKey(listener);\n });\n this._mapListener.map((listener) => {\n listener.destroy();\n });\n\n // Remove event escape listener\n this._mapContainer?.removeEventListener(\"keydown\", this._enterHandler);\n this._mapContainer?.removeEventListener(\"keydown\", this._escapeHandler);\n\n this._setState(\"destroyed\");\n }\n\n triggerSave() {\n const feature = this._editingSource.getFeatures()[0];\n if (!feature) {\n throw Error(\"no updated feature found\");\n }\n this._save(feature);\n }\n\n whenComplete(): Promise<Record<string, string> | undefined> {\n if (this._state === \"destroyed\") {\n if (this._error) {\n return Promise.reject(this._error);\n } else {\n if (this._featureId) {\n return Promise.resolve({ featureId: this._featureId });\n } else {\n return Promise.resolve(undefined);\n }\n }\n }\n\n const manualPromise = (this.#waiter ??= createManualPromise());\n return manualPromise.promise;\n }\n}\n"],"names":[],"mappings":"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"}
package/README.md CHANGED
@@ -2,11 +2,22 @@
2
2
 
3
3
  This package provides an editing service that allows to start and handle geometry editing workflows.
4
4
 
5
- Note: The editing only works with OGC API Feature Services. The editing was only tested using the implementation of the OGC API Features in the XtraServer by interactive instruments. The collection where the geometry will be saved, needs to support the map's coordinate system.
5
+ > **_NOTE:_** The editing only works with OGC API Feature Services. The editing was only tested using
6
+ > the implementation of the OGC API Features in the XtraServer by interactive instruments.
7
+ > The collection in that the geometry will be saved, needs to support the map's coordinate system.
8
+ > The saving process may not be suitable for all kinds of OGC API Feature Service set up using the XtraServer.
9
+ > Please note the following additional information to ensure that process is appropriate for your services.
10
+ >
11
+ > Additional information:
12
+ >
13
+ > - Create Workflow: The feature is saved in the collection as a new feature with an empty properties
14
+ > object and without an id (using POST).
15
+ > - Update Workflow: The updated geometry is saved for the feature in the collection using a PATCH request.
16
+ > In addition to the new geometry, the PATCH request sends an empty properties object within the body.
6
17
 
7
18
  ## Usage
8
19
 
9
- To use the editing in an app, inject the editing service. Use the `start` method to create a new editing workflow.
20
+ To use the editing in an app, inject the editing service. Use the `createFeature` method to create a new `create` editing workflow or `updateFeature` method to create a new `update` editing workflow.
10
21
 
11
22
  Example:
12
23
 
@@ -24,10 +35,13 @@ export default defineBuildConfig({
24
35
  ```tsx
25
36
  const editingService = useService<EditingService>("editing.EditingService");
26
37
  const editingCollectionUrl = new URL("...");
27
- const workflow = editingService.start(map, editingCollectionUrl);
38
+ const feature = new Feature({});
39
+
40
+ const createWorkflow = editingService.createFeature(map, editingCollectionUrl);
41
+ const updateWorkflow = editingService.updateFeature(map, editingCollectionUrl, feature);
28
42
  ```
29
43
 
30
- An editing workflow can be stopped completely or the current drawing can be deleted without leaving the edit mode.
44
+ An editing workflow can be stopped completely or the current drawing can be reset to the initial state without leaving the edit mode.
31
45
 
32
46
  Example:
33
47
 
@@ -63,7 +77,7 @@ Example:
63
77
  ```js
64
78
  workflow
65
79
  .whenComplete()
66
- .then((featureId: string | undefined) => {
80
+ .then((featureId: Record<string, string> | undefined) => {
67
81
  // ...
68
82
  })
69
83
  .catch((error: Error) => {
@@ -85,7 +99,9 @@ vectorLayer.getSource()?.refresh();
85
99
 
86
100
  The default style of the geometries can be overridden with a custom style.
87
101
 
88
- Each geometry type has its own styling property (currently only `polygonDrawStyle`). See OpenLayers [`FlatStyleLike`](https://openlayers.org/en/latest/apidoc/module-ol_style_flat.html) for valid styling options.
102
+ Each geometry type has its own styling property (currently `polygonStyle` and `vertexStyle`). See OpenLayers [`FlatStyle`](https://openlayers.org/en/latest/apidoc/module-ol_style_flat.html#~FlatStyle) for valid styling options.
103
+
104
+ Example:
89
105
 
90
106
  ```js
91
107
  const element = createCustomElement({
@@ -93,14 +109,20 @@ const element = createCustomElement({
93
109
  config: {
94
110
  properties: {
95
111
  "@open-pioneer/editing": {
96
- "polygonDrawStyle": {
112
+ "polygonStyle": {
113
+ "fill-color": "rgba(255,255,255,0.4)",
97
114
  "stroke-color": "red",
98
115
  "stroke-width": 2,
99
- "fill-color": "rgba(0, 0, 0, 0.1)",
100
116
  "circle-radius": 5,
101
- "circle-fill-color": "rgba(255, 0, 0, 0.2)",
102
- "circle-stroke-color": "rgba(255, 0, 0, 0.7)",
103
- "circle-stroke-width": 2
117
+ "circle-fill-color": "red",
118
+ "circle-stroke-width": 1.25,
119
+ "circle-stroke-color": "red"
120
+ },
121
+ "vertexStyle": {
122
+ "circle-radius": 5,
123
+ "circle-fill-color": "red",
124
+ "circle-stroke-width": 1.25,
125
+ "circle-stroke-color": "red"
104
126
  }
105
127
  }
106
128
  }
@@ -111,6 +133,49 @@ const element = createCustomElement({
111
133
  customElements.define("ol-map-app", element);
112
134
  ```
113
135
 
136
+ Set `vertexStyle` to `null`, if no style is needed.
137
+
138
+ Example:
139
+
140
+ ```js
141
+ const element = createCustomElement({
142
+ ...,
143
+ config: {
144
+ properties: {
145
+ "@open-pioneer/editing": {
146
+ "polygonStyle": {
147
+ "fill-color": "rgba(255,255,255,0.4)",
148
+ "stroke-color": "red",
149
+ "stroke-width": 2,
150
+ "circle-radius": 5,
151
+ "circle-fill-color": "red",
152
+ "circle-stroke-width": 1.25,
153
+ "circle-stroke-color": "red"
154
+ },
155
+ "vertexStyle": null
156
+ }
157
+ }
158
+ },
159
+ ...
160
+ });
161
+
162
+ customElements.define("ol-map-app", element);
163
+ ```
164
+
165
+ ### Keyboard shortcuts and operating instructions
166
+
167
+ The user can use the following keyboard shortcuts / interactions during create feature workflow:
168
+
169
+ - `Esc`: Reset the drawing
170
+ - `Mouse double-click`: Set last vertex and finish drawing
171
+ - `Enter`: Finish drawing (vertex that is currently drawn is discarded)
172
+
173
+ The user can use the following keyboard shortcuts / interactions during update feature workflow:
174
+
175
+ - `Esc`: Reset the drawing
176
+ - `Alt + MouseClick`: Remove vertices from feature
177
+ - `Mouse click (outside feature)` or `Enter`: finish editing
178
+
114
179
  ## License
115
180
 
116
181
  Apache-2.0 (see `LICENSE` file)
@@ -2,4 +2,13 @@ import { HttpService } from "@open-pioneer/http";
2
2
  import GeoJSONGeometry from "ol/format/GeoJSON";
3
3
  import GeoJSONGeometryCollection from "ol/format/GeoJSON";
4
4
  import { Projection } from "ol/proj";
5
+ /**
6
+ * Function to save a created feature to an OGC API Features service.
7
+ * Resolves with feature id, or rejects if an error occurs.
8
+ */
5
9
  export declare function saveCreatedFeature(httpService: HttpService, url: URL, geometry: GeoJSONGeometry | GeoJSONGeometryCollection, projection: Projection): Promise<string>;
10
+ /**
11
+ * Function to save an updated geometry to a feature to an OGC API Features service.
12
+ * Resolves with feature id, or rejects if an error occurs.
13
+ */
14
+ export declare function saveUpdatedFeature(httpService: HttpService, url: URL, featureId: string, geometry: GeoJSONGeometry | GeoJSONGeometryCollection, projection: Projection): Promise<string>;
@@ -10,15 +10,32 @@ async function saveCreatedFeature(httpService, url, geometry, projection) {
10
10
  }
11
11
  });
12
12
  if (!response || !response.ok || response.status !== 201) {
13
- return Promise.reject(new Error("Request failed: " + response.status));
13
+ throw new Error("Request failed: " + response.status);
14
14
  }
15
15
  const location = response.headers.get("location");
16
16
  if (!location) {
17
- return Promise.reject(new Error("Request failed: no Location response header"));
17
+ throw new Error("Request failed: no Location response header");
18
18
  }
19
19
  const featureId = location.substring(location.lastIndexOf("/") + 1);
20
20
  return Promise.resolve(featureId);
21
21
  }
22
+ async function saveUpdatedFeature(httpService, url, featureId, geometry, projection) {
23
+ const epsgCode = projection.getCode();
24
+ const crs = epsgCode.replace("EPSG:", "http://www.opengis.net/def/crs/EPSG/0/");
25
+ const featureUrl = new URL(`${url.toString()}/${featureId}`);
26
+ const response = await httpService.fetch(featureUrl, {
27
+ method: "PATCH",
28
+ body: JSON.stringify({ type: "Feature", properties: {}, geometry }),
29
+ headers: {
30
+ "Content-Type": "application/geo+json; charset=utf-8",
31
+ "Content-Crs": `<${crs}>`
32
+ }
33
+ });
34
+ if (!response || !response.ok || response.status !== 204) {
35
+ throw new Error("Request failed: " + response.status);
36
+ }
37
+ return Promise.resolve(featureId);
38
+ }
22
39
 
23
- export { saveCreatedFeature };
40
+ export { saveCreatedFeature, saveUpdatedFeature };
24
41
  //# sourceMappingURL=SaveFeaturesHandler.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SaveFeaturesHandler.js","sources":["SaveFeaturesHandler.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\n\nimport { HttpService } from \"@open-pioneer/http\";\nimport GeoJSONGeometry from \"ol/format/GeoJSON\";\nimport GeoJSONGeometryCollection from \"ol/format/GeoJSON\";\nimport { Projection } from \"ol/proj\";\n\nexport async function saveCreatedFeature(\n httpService: HttpService,\n url: URL,\n geometry: GeoJSONGeometry | GeoJSONGeometryCollection,\n projection: Projection\n) {\n const epsgCode = projection.getCode();\n const crs = epsgCode.replace(\"EPSG:\", \"http://www.opengis.net/def/crs/EPSG/0/\");\n const response = await httpService.fetch(url, {\n method: \"POST\",\n body: JSON.stringify({ type: \"Feature\", properties: {}, geometry: geometry }),\n headers: {\n \"Content-Type\": \"application/geo+json; charset=utf-8\",\n \"Content-Crs\": `<${crs}>`\n }\n });\n\n if (!response || !response.ok || response.status !== 201) {\n return Promise.reject(new Error(\"Request failed: \" + response.status));\n }\n\n const location = response.headers.get(\"location\");\n if (!location) {\n return Promise.reject(new Error(\"Request failed: no Location response header\"));\n }\n\n const featureId = location.substring(location.lastIndexOf(\"/\") + 1);\n\n return Promise.resolve(featureId);\n}\n"],"names":[],"mappings":"AAQA,eAAsB,kBAClB,CAAA,WAAA,EACA,GACA,EAAA,QAAA,EACA,UACF,EAAA;AACE,EAAM,MAAA,QAAA,GAAW,WAAW,OAAQ,EAAA,CAAA;AACpC,EAAA,MAAM,GAAM,GAAA,QAAA,CAAS,OAAQ,CAAA,OAAA,EAAS,wCAAwC,CAAA,CAAA;AAC9E,EAAA,MAAM,QAAW,GAAA,MAAM,WAAY,CAAA,KAAA,CAAM,GAAK,EAAA;AAAA,IAC1C,MAAQ,EAAA,MAAA;AAAA,IACR,IAAA,EAAM,IAAK,CAAA,SAAA,CAAU,EAAE,IAAA,EAAM,WAAW,UAAY,EAAA,EAAI,EAAA,QAAA,EAAoB,CAAA;AAAA,IAC5E,OAAS,EAAA;AAAA,MACL,cAAgB,EAAA,qCAAA;AAAA,MAChB,aAAA,EAAe,IAAI,GAAG,CAAA,CAAA,CAAA;AAAA,KAC1B;AAAA,GACH,CAAA,CAAA;AAED,EAAA,IAAI,CAAC,QAAY,IAAA,CAAC,SAAS,EAAM,IAAA,QAAA,CAAS,WAAW,GAAK,EAAA;AACtD,IAAA,OAAO,QAAQ,MAAO,CAAA,IAAI,MAAM,kBAAqB,GAAA,QAAA,CAAS,MAAM,CAAC,CAAA,CAAA;AAAA,GACzE;AAEA,EAAA,MAAM,QAAW,GAAA,QAAA,CAAS,OAAQ,CAAA,GAAA,CAAI,UAAU,CAAA,CAAA;AAChD,EAAA,IAAI,CAAC,QAAU,EAAA;AACX,IAAA,OAAO,OAAQ,CAAA,MAAA,CAAO,IAAI,KAAA,CAAM,6CAA6C,CAAC,CAAA,CAAA;AAAA,GAClF;AAEA,EAAA,MAAM,YAAY,QAAS,CAAA,SAAA,CAAU,SAAS,WAAY,CAAA,GAAG,IAAI,CAAC,CAAA,CAAA;AAElE,EAAO,OAAA,OAAA,CAAQ,QAAQ,SAAS,CAAA,CAAA;AACpC;;;;"}
1
+ {"version":3,"file":"SaveFeaturesHandler.js","sources":["SaveFeaturesHandler.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport { HttpService } from \"@open-pioneer/http\";\nimport GeoJSONGeometry from \"ol/format/GeoJSON\";\nimport GeoJSONGeometryCollection from \"ol/format/GeoJSON\";\nimport { Projection } from \"ol/proj\";\n\n/**\n * Function to save a created feature to an OGC API Features service.\n * Resolves with feature id, or rejects if an error occurs.\n */\nexport async function saveCreatedFeature(\n httpService: HttpService,\n url: URL,\n geometry: GeoJSONGeometry | GeoJSONGeometryCollection,\n projection: Projection\n) {\n const epsgCode = projection.getCode();\n const crs = epsgCode.replace(\"EPSG:\", \"http://www.opengis.net/def/crs/EPSG/0/\");\n const response = await httpService.fetch(url, {\n method: \"POST\",\n body: JSON.stringify({ type: \"Feature\", properties: {}, geometry: geometry }),\n headers: {\n \"Content-Type\": \"application/geo+json; charset=utf-8\",\n \"Content-Crs\": `<${crs}>`\n }\n });\n\n if (!response || !response.ok || response.status !== 201) {\n throw new Error(\"Request failed: \" + response.status);\n }\n\n const location = response.headers.get(\"location\");\n if (!location) {\n throw new Error(\"Request failed: no Location response header\");\n }\n\n const featureId = location.substring(location.lastIndexOf(\"/\") + 1);\n return Promise.resolve(featureId);\n}\n\n/**\n * Function to save an updated geometry to a feature to an OGC API Features service.\n * Resolves with feature id, or rejects if an error occurs.\n */\nexport async function saveUpdatedFeature(\n httpService: HttpService,\n url: URL,\n featureId: string,\n geometry: GeoJSONGeometry | GeoJSONGeometryCollection,\n projection: Projection\n) {\n const epsgCode = projection.getCode();\n const crs = epsgCode.replace(\"EPSG:\", \"http://www.opengis.net/def/crs/EPSG/0/\");\n const featureUrl = new URL(`${url.toString()}/${featureId}`);\n const response = await httpService.fetch(featureUrl, {\n method: \"PATCH\",\n body: JSON.stringify({ type: \"Feature\", properties: {}, geometry: geometry }),\n headers: {\n \"Content-Type\": \"application/geo+json; charset=utf-8\",\n \"Content-Crs\": `<${crs}>`\n }\n });\n\n if (!response || !response.ok || response.status !== 204) {\n throw new Error(\"Request failed: \" + response.status);\n }\n\n return Promise.resolve(featureId);\n}\n"],"names":[],"mappings":"AAWA,eAAsB,kBAClB,CAAA,WAAA,EACA,GACA,EAAA,QAAA,EACA,UACF,EAAA;AACE,EAAM,MAAA,QAAA,GAAW,WAAW,OAAQ,EAAA,CAAA;AACpC,EAAA,MAAM,GAAM,GAAA,QAAA,CAAS,OAAQ,CAAA,OAAA,EAAS,wCAAwC,CAAA,CAAA;AAC9E,EAAA,MAAM,QAAW,GAAA,MAAM,WAAY,CAAA,KAAA,CAAM,GAAK,EAAA;AAAA,IAC1C,MAAQ,EAAA,MAAA;AAAA,IACR,IAAA,EAAM,IAAK,CAAA,SAAA,CAAU,EAAE,IAAA,EAAM,WAAW,UAAY,EAAA,EAAI,EAAA,QAAA,EAAoB,CAAA;AAAA,IAC5E,OAAS,EAAA;AAAA,MACL,cAAgB,EAAA,qCAAA;AAAA,MAChB,aAAA,EAAe,IAAI,GAAG,CAAA,CAAA,CAAA;AAAA,KAC1B;AAAA,GACH,CAAA,CAAA;AAED,EAAA,IAAI,CAAC,QAAY,IAAA,CAAC,SAAS,EAAM,IAAA,QAAA,CAAS,WAAW,GAAK,EAAA;AACtD,IAAA,MAAM,IAAI,KAAA,CAAM,kBAAqB,GAAA,QAAA,CAAS,MAAM,CAAA,CAAA;AAAA,GACxD;AAEA,EAAA,MAAM,QAAW,GAAA,QAAA,CAAS,OAAQ,CAAA,GAAA,CAAI,UAAU,CAAA,CAAA;AAChD,EAAA,IAAI,CAAC,QAAU,EAAA;AACX,IAAM,MAAA,IAAI,MAAM,6CAA6C,CAAA,CAAA;AAAA,GACjE;AAEA,EAAA,MAAM,YAAY,QAAS,CAAA,SAAA,CAAU,SAAS,WAAY,CAAA,GAAG,IAAI,CAAC,CAAA,CAAA;AAClE,EAAO,OAAA,OAAA,CAAQ,QAAQ,SAAS,CAAA,CAAA;AACpC,CAAA;AAMA,eAAsB,kBAClB,CAAA,WAAA,EACA,GACA,EAAA,SAAA,EACA,UACA,UACF,EAAA;AACE,EAAM,MAAA,QAAA,GAAW,WAAW,OAAQ,EAAA,CAAA;AACpC,EAAA,MAAM,GAAM,GAAA,QAAA,CAAS,OAAQ,CAAA,OAAA,EAAS,wCAAwC,CAAA,CAAA;AAC9E,EAAM,MAAA,UAAA,GAAa,IAAI,GAAI,CAAA,CAAA,EAAG,IAAI,QAAS,EAAC,CAAI,CAAA,EAAA,SAAS,CAAE,CAAA,CAAA,CAAA;AAC3D,EAAA,MAAM,QAAW,GAAA,MAAM,WAAY,CAAA,KAAA,CAAM,UAAY,EAAA;AAAA,IACjD,MAAQ,EAAA,OAAA;AAAA,IACR,IAAA,EAAM,IAAK,CAAA,SAAA,CAAU,EAAE,IAAA,EAAM,WAAW,UAAY,EAAA,EAAI,EAAA,QAAA,EAAoB,CAAA;AAAA,IAC5E,OAAS,EAAA;AAAA,MACL,cAAgB,EAAA,qCAAA;AAAA,MAChB,aAAA,EAAe,IAAI,GAAG,CAAA,CAAA,CAAA;AAAA,KAC1B;AAAA,GACH,CAAA,CAAA;AAED,EAAA,IAAI,CAAC,QAAY,IAAA,CAAC,SAAS,EAAM,IAAA,QAAA,CAAS,WAAW,GAAK,EAAA;AACtD,IAAA,MAAM,IAAI,KAAA,CAAM,kBAAqB,GAAA,QAAA,CAAS,MAAM,CAAA,CAAA;AAAA,GACxD;AAEA,EAAO,OAAA,OAAA,CAAQ,QAAQ,SAAS,CAAA,CAAA;AACpC;;;;"}
package/Tooltip.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ import { Resource } from "@open-pioneer/core";
2
+ import type OlMap from "ol/Map";
3
+ /**
4
+ * Represents a tooltip rendered on the OpenLayers map
5
+ */
6
+ export interface Tooltip extends Resource {
7
+ setVisible(visible: boolean): void;
8
+ setText(text: string): void;
9
+ }
10
+ /**
11
+ * Creates a new tooltip on the given map, with the given text content.
12
+ *
13
+ * The tooltip will follow the mouse while it moves over the map.
14
+ *
15
+ * Note: the tooltip starts invisible, and must be toggled on via `setVisible(true)`.
16
+ */
17
+ export declare function createTooltip(olMap: OlMap, text: string): Tooltip;
package/Tooltip.js ADDED
@@ -0,0 +1,35 @@
1
+ import { Overlay } from 'ol';
2
+ import { unByKey } from 'ol/Observable';
3
+
4
+ function createTooltip(olMap, text) {
5
+ const element = document.createElement("div");
6
+ element.className = "editing-tooltip editing-tooltip-hidden";
7
+ element.textContent = text;
8
+ const overlay = new Overlay({
9
+ element,
10
+ offset: [15, 0],
11
+ positioning: "center-left"
12
+ });
13
+ const pointerMove = olMap.on("pointermove", (evt) => {
14
+ if (evt.dragging) {
15
+ return;
16
+ }
17
+ overlay.setPosition(evt.coordinate);
18
+ });
19
+ olMap.addOverlay(overlay);
20
+ return {
21
+ destroy() {
22
+ unByKey(pointerMove);
23
+ olMap.removeOverlay(overlay);
24
+ },
25
+ setVisible(visible) {
26
+ element.classList.toggle("editing-tooltip-hidden", !visible);
27
+ },
28
+ setText(text2) {
29
+ element.textContent = text2;
30
+ }
31
+ };
32
+ }
33
+
34
+ export { createTooltip };
35
+ //# sourceMappingURL=Tooltip.js.map
package/Tooltip.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Tooltip.js","sources":["Tooltip.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport { Resource } from \"@open-pioneer/core\";\nimport { Overlay } from \"ol\";\nimport type OlMap from \"ol/Map\";\nimport { unByKey } from \"ol/Observable\";\n\n/**\n * Represents a tooltip rendered on the OpenLayers map\n */\nexport interface Tooltip extends Resource {\n setVisible(visible: boolean): void;\n setText(text: string): void;\n}\n\n/**\n * Creates a new tooltip on the given map, with the given text content.\n *\n * The tooltip will follow the mouse while it moves over the map.\n *\n * Note: the tooltip starts invisible, and must be toggled on via `setVisible(true)`.\n */\nexport function createTooltip(olMap: OlMap, text: string): Tooltip {\n const element = document.createElement(\"div\");\n element.className = \"editing-tooltip editing-tooltip-hidden\";\n element.textContent = text;\n\n const overlay = new Overlay({\n element: element,\n offset: [15, 0],\n positioning: \"center-left\"\n });\n\n const pointerMove = olMap.on(\"pointermove\", (evt) => {\n if (evt.dragging) {\n return;\n }\n\n overlay.setPosition(evt.coordinate);\n });\n\n olMap.addOverlay(overlay);\n return {\n destroy() {\n unByKey(pointerMove);\n olMap.removeOverlay(overlay);\n },\n setVisible(visible) {\n element.classList.toggle(\"editing-tooltip-hidden\", !visible);\n },\n setText(text) {\n element.textContent = text;\n }\n };\n}\n"],"names":["text"],"mappings":";;;AAsBgB,SAAA,aAAA,CAAc,OAAc,IAAuB,EAAA;AAC/D,EAAM,MAAA,OAAA,GAAU,QAAS,CAAA,aAAA,CAAc,KAAK,CAAA,CAAA;AAC5C,EAAA,OAAA,CAAQ,SAAY,GAAA,wCAAA,CAAA;AACpB,EAAA,OAAA,CAAQ,WAAc,GAAA,IAAA,CAAA;AAEtB,EAAM,MAAA,OAAA,GAAU,IAAI,OAAQ,CAAA;AAAA,IACxB,OAAA;AAAA,IACA,MAAA,EAAQ,CAAC,EAAA,EAAI,CAAC,CAAA;AAAA,IACd,WAAa,EAAA,aAAA;AAAA,GAChB,CAAA,CAAA;AAED,EAAA,MAAM,WAAc,GAAA,KAAA,CAAM,EAAG,CAAA,aAAA,EAAe,CAAC,GAAQ,KAAA;AACjD,IAAA,IAAI,IAAI,QAAU,EAAA;AACd,MAAA,OAAA;AAAA,KACJ;AAEA,IAAQ,OAAA,CAAA,WAAA,CAAY,IAAI,UAAU,CAAA,CAAA;AAAA,GACrC,CAAA,CAAA;AAED,EAAA,KAAA,CAAM,WAAW,OAAO,CAAA,CAAA;AACxB,EAAO,OAAA;AAAA,IACH,OAAU,GAAA;AACN,MAAA,OAAA,CAAQ,WAAW,CAAA,CAAA;AACnB,MAAA,KAAA,CAAM,cAAc,OAAO,CAAA,CAAA;AAAA,KAC/B;AAAA,IACA,WAAW,OAAS,EAAA;AAChB,MAAA,OAAA,CAAQ,SAAU,CAAA,MAAA,CAAO,wBAA0B,EAAA,CAAC,OAAO,CAAA,CAAA;AAAA,KAC/D;AAAA,IACA,QAAQA,KAAM,EAAA;AACV,MAAA,OAAA,CAAQ,WAAcA,GAAAA,KAAAA,CAAAA;AAAA,KAC1B;AAAA,GACJ,CAAA;AACJ;;;;"}
package/api.d.ts CHANGED
@@ -1,18 +1,24 @@
1
1
  import { EventEmitter } from "@open-pioneer/core";
2
+ import { HttpService } from "@open-pioneer/http";
2
3
  import { MapModel } from "@open-pioneer/map";
3
- import type { DeclaredService } from "@open-pioneer/runtime";
4
+ import type { DeclaredService, PackageIntl } from "@open-pioneer/runtime";
5
+ import { Feature } from "ol";
6
+ import { FlatStyle } from "ol/style/flat";
4
7
  /**
5
8
  * State of an editing workflow
6
9
  */
7
- export type EditingWorkflowState = "active:initialized" | "active:drawing" | "active:saving" | "inactive";
8
- /** Events emitted by the {@link EditingWorkflow}. */
10
+ export type EditingWorkflowState = "active:initialized" | "active:drawing" | "active:saving" | "destroyed";
11
+ /**
12
+ * Events emitted by the {@link EditingWorkflow}.
13
+ */
9
14
  export interface EditingWorkflowEvents {
10
15
  /**
11
16
  * Initial state after editing workflow was started but user has not yet started drawing.
12
17
  */
13
18
  "active:initialized": void;
14
19
  /**
15
- * State while user is drawing a feature. State is entered when user adds the first vertex of the geometry.
20
+ * State while user is drawing a feature. State is entered when user adds the first vertex of the geometry (`create-mode`).
21
+ * State while user is updating an existing feature. State is entered when user moved the first vertex of the geometry (`update-mode`).
16
22
  */
17
23
  "active:drawing": void;
18
24
  /**
@@ -22,23 +28,46 @@ export interface EditingWorkflowEvents {
22
28
  /**
23
29
  * State after editing is stopped.
24
30
  */
25
- "inactive": void;
31
+ "destroyed": void;
32
+ }
33
+ /**
34
+ * Props of an editing workflow
35
+ */
36
+ export interface EditingWorkflowProps {
37
+ map: MapModel;
38
+ ogcApiFeatureLayerUrl: URL;
39
+ polygonStyle: FlatStyle;
40
+ vertexStyle: FlatStyle;
41
+ httpService: HttpService;
42
+ intl: PackageIntl;
26
43
  }
27
44
  /**
28
45
  * EditingWorkflows are created by the {@link EditingService}
29
46
  * and represent a currently ongoing editing workflow.
30
47
  */
31
48
  export interface EditingWorkflow extends EventEmitter<EditingWorkflowEvents> {
49
+ /**
50
+ * Stops this editing operation.
51
+ */
52
+ stop(): void;
53
+ /**
54
+ * Resets this workflow to its initial state.
55
+ */
56
+ reset(): void;
32
57
  /**
33
58
  * Returns the current state of the editing workflow.
34
59
  */
35
60
  getState(): EditingWorkflowState;
61
+ /**
62
+ * Trigger saving the currently drawn/updated feature.
63
+ */
64
+ triggerSave(): void;
36
65
  /**
37
66
  * Wait for the editing to be finished. The returned promise resolves with the
38
67
  * feature ID when saving was successful and rejects if saving the feature
39
68
  * failed. It resolves with undefined when the editing was stopped.
40
69
  */
41
- whenComplete(): Promise<string | undefined>;
70
+ whenComplete(): Promise<Record<string, string> | undefined>;
42
71
  }
43
72
  /**
44
73
  * The editing service allows to start and handle editing workflows.
@@ -47,15 +76,19 @@ export interface EditingWorkflow extends EventEmitter<EditingWorkflowEvents> {
47
76
  */
48
77
  export interface EditingService extends DeclaredService<"editing.EditingService"> {
49
78
  /**
50
- * Creates and initializes a new {@link EditingWorkflow}.
79
+ * Creates and initializes a new {@link EditingWorkflow} to create a geometry.
80
+ */
81
+ createFeature(map: MapModel, ogcApiFeatureLayerUrl: URL): EditingWorkflow;
82
+ /**
83
+ * Creates and initializes a new {@link EditingWorkflow} to update an existing feature's geometry.
51
84
  */
52
- start(map: MapModel, ogcApiFeatureLayerUrl: URL): EditingWorkflow;
85
+ updateFeature(map: MapModel, ogcApiFeatureLayerUrl: URL, feature: Feature): EditingWorkflow;
53
86
  /**
54
87
  * Stops the edit mode and removes an existing {@link EditingWorkflow}.
55
88
  */
56
89
  stop(mapId: string): void;
57
90
  /**
58
- * Removes the unfinished geometry from an existing {@link EditingWorkflow} without leaving the edit mode.
91
+ * Resets the unfinished geometry from an existing {@link EditingWorkflow} without leaving the edit mode.
59
92
  */
60
93
  reset(mapId: string): void;
61
94
  }
package/i18n/de.yaml CHANGED
@@ -1,5 +1,7 @@
1
1
  messages:
2
2
  title: Editierung von Objekten
3
- tooltip:
4
- begin: Klicken, um mit Erstellung der Geometrie zu beginnen
5
- continue: Doppelt klicken, um Geometrie abzuschließen und Feature zu speichern
3
+ create:
4
+ tooltip:
5
+ begin: Klicken, um mit Erstellung der Geometrie zu beginnen
6
+ continue: Doppelt klicken, um Geometrie abzuschließen und Feature zu speichern
7
+ deselect: Außerhalb der Geometrie in die Karte klicken, um Änderungen zu speichern