ol-wfst 4.3.0 → 5.0.0
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.
- package/README.md +240 -173
- package/dist/@types.d.ts +25 -9
- package/dist/@types.d.ts.map +1 -1
- package/dist/Geoserver.d.ts +6 -4
- package/dist/Geoserver.d.ts.map +1 -1
- package/dist/WfsLayer.d.ts +10 -6
- package/dist/WfsLayer.d.ts.map +1 -1
- package/dist/WmsLayer.d.ts +34 -4
- package/dist/WmsLayer.d.ts.map +1 -1
- package/dist/css/ol-wfst.bootstrap5.css +102 -90
- package/dist/css/ol-wfst.bootstrap5.min.css +1 -1
- package/dist/css/ol-wfst.css +432 -54
- package/dist/css/ol-wfst.min.css +1 -1
- package/dist/defaults.d.ts.map +1 -1
- package/dist/jsx-runtime.d.ts +4 -0
- package/dist/jsx-runtime.d.ts.map +1 -0
- package/dist/modules/EditControlChanges.d.ts +2 -1
- package/dist/modules/EditControlChanges.d.ts.map +1 -1
- package/dist/modules/EditFieldsModal.d.ts +10 -6
- package/dist/modules/EditFieldsModal.d.ts.map +1 -1
- package/dist/modules/EditOverlay.d.ts +1 -1
- package/dist/modules/EditOverlay.d.ts.map +1 -1
- package/dist/modules/ExtendLineOverlay.d.ts +12 -0
- package/dist/modules/ExtendLineOverlay.d.ts.map +1 -0
- package/dist/modules/LayersControl.d.ts +5 -0
- package/dist/modules/LayersControl.d.ts.map +1 -1
- package/dist/modules/QueryOverlay.d.ts +21 -0
- package/dist/modules/QueryOverlay.d.ts.map +1 -0
- package/dist/modules/base/BaseLayer.d.ts +7 -3
- package/dist/modules/base/BaseLayer.d.ts.map +1 -1
- package/dist/modules/base/WfsSource.d.ts.map +1 -1
- package/dist/modules/editLayer.d.ts.map +1 -1
- package/dist/modules/errors.d.ts +1 -1
- package/dist/modules/errors.d.ts.map +1 -1
- package/dist/modules/helpers.d.ts.map +1 -1
- package/dist/modules/i18n/en.d.ts.map +1 -1
- package/dist/modules/i18n/es.d.ts.map +1 -1
- package/dist/modules/i18n/index.d.ts.map +1 -1
- package/dist/modules/i18n/zh.d.ts.map +1 -1
- package/dist/modules/loading.d.ts.map +1 -1
- package/dist/modules/state.d.ts +9 -1
- package/dist/modules/state.d.ts.map +1 -1
- package/dist/modules/styleFunction.d.ts +36 -3
- package/dist/modules/styleFunction.d.ts.map +1 -1
- package/dist/ol-wfst.d.ts +243 -6
- package/dist/ol-wfst.d.ts.map +1 -1
- package/dist/ol-wfst.js +1855 -386
- package/dist/ol-wfst.js.map +1 -1
- package/dist/ol-wfst.min.js +3 -3
- package/dist/ol-wfst.min.js.map +1 -1
- package/lib/@types.d.ts +25 -9
- package/lib/@types.d.ts.map +1 -1
- package/lib/Geoserver.d.ts +6 -4
- package/lib/Geoserver.d.ts.map +1 -1
- package/lib/WfsLayer.d.ts +10 -6
- package/lib/WfsLayer.d.ts.map +1 -1
- package/lib/WmsLayer.d.ts +34 -4
- package/lib/WmsLayer.d.ts.map +1 -1
- package/lib/defaults.d.ts.map +1 -1
- package/lib/jsx-runtime.d.ts +4 -0
- package/lib/jsx-runtime.d.ts.map +1 -0
- package/lib/modules/EditControlChanges.d.ts +2 -1
- package/lib/modules/EditControlChanges.d.ts.map +1 -1
- package/lib/modules/EditFieldsModal.d.ts +10 -6
- package/lib/modules/EditFieldsModal.d.ts.map +1 -1
- package/lib/modules/EditOverlay.d.ts +1 -1
- package/lib/modules/EditOverlay.d.ts.map +1 -1
- package/lib/modules/ExtendLineOverlay.d.ts +12 -0
- package/lib/modules/ExtendLineOverlay.d.ts.map +1 -0
- package/lib/modules/LayersControl.d.ts +5 -0
- package/lib/modules/LayersControl.d.ts.map +1 -1
- package/lib/modules/QueryOverlay.d.ts +21 -0
- package/lib/modules/QueryOverlay.d.ts.map +1 -0
- package/lib/modules/base/BaseLayer.d.ts +7 -3
- package/lib/modules/base/BaseLayer.d.ts.map +1 -1
- package/lib/modules/base/WfsSource.d.ts.map +1 -1
- package/lib/modules/editLayer.d.ts.map +1 -1
- package/lib/modules/errors.d.ts +1 -1
- package/lib/modules/errors.d.ts.map +1 -1
- package/lib/modules/helpers.d.ts.map +1 -1
- package/lib/modules/i18n/en.d.ts.map +1 -1
- package/lib/modules/i18n/es.d.ts.map +1 -1
- package/lib/modules/i18n/index.d.ts.map +1 -1
- package/lib/modules/i18n/zh.d.ts.map +1 -1
- package/lib/modules/loading.d.ts.map +1 -1
- package/lib/modules/state.d.ts +9 -1
- package/lib/modules/state.d.ts.map +1 -1
- package/lib/modules/styleFunction.d.ts +36 -3
- package/lib/modules/styleFunction.d.ts.map +1 -1
- package/lib/ol-wfst.d.ts +243 -6
- package/lib/ol-wfst.d.ts.map +1 -1
- package/lib/ol-wfst.js +1857 -384
- package/lib/ol-wfst.js.map +1 -1
- package/lib/style/css/ol-gisify.css +432 -54
- package/lib/style/css/ol-wfst.bootstrap5.css +102 -90
- package/lib/style/scss/_variables.scss +2 -1
- package/lib/style/scss/ol-wfst.scss +441 -42
- package/package.json +3 -3
- package/dist/myPragma.d.ts +0 -2
- package/dist/myPragma.d.ts.map +0 -1
- package/lib/myPragma.d.ts +0 -2
- package/lib/myPragma.d.ts.map +0 -1
package/lib/ol-wfst.js
CHANGED
|
@@ -1,27 +1,33 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* ol-wfst - v4.
|
|
2
|
+
* ol-wfst - v4.4.0
|
|
3
3
|
* https://github.com/GastonZalba/ol-wfst#readme
|
|
4
|
-
* Built: Thu
|
|
4
|
+
* Built: Thu Sep 10 2026 23:06:22 GMT-0300 (Argentina Standard Time)
|
|
5
5
|
*/
|
|
6
|
-
import CircleStyle from 'ol/style/Circle.js';
|
|
7
6
|
import Fill$1 from 'ol/style/Fill.js';
|
|
8
7
|
import Stroke$1 from 'ol/style/Stroke.js';
|
|
9
8
|
import Style$1 from 'ol/style/Style.js';
|
|
10
9
|
import Control from 'ol/control/Control.js';
|
|
10
|
+
import FullScreen from 'ol/control/FullScreen.js';
|
|
11
11
|
import Draw from 'ol/interaction/Draw.js';
|
|
12
|
+
import DragBox from 'ol/interaction/DragBox.js';
|
|
12
13
|
import Modify from 'ol/interaction/Modify.js';
|
|
13
14
|
import Select from 'ol/interaction/Select.js';
|
|
14
15
|
import Snap from 'ol/interaction/Snap.js';
|
|
16
|
+
import LineString from 'ol/geom/LineString.js';
|
|
17
|
+
import MultiLineString$1 from 'ol/geom/MultiLineString.js';
|
|
18
|
+
import GeometryCollection$1 from 'ol/geom/GeometryCollection.js';
|
|
15
19
|
import Collection from 'ol/Collection.js';
|
|
20
|
+
import VectorLayer from 'ol/layer/Vector.js';
|
|
21
|
+
import VectorSource, { VectorSourceEvent } from 'ol/source/Vector.js';
|
|
16
22
|
import BaseEvent from 'ol/events/Event.js';
|
|
17
|
-
import { primaryAction,
|
|
23
|
+
import { primaryAction, shiftKeyOnly } from 'ol/events/condition.js';
|
|
18
24
|
import Observable, { unByKey } from 'ol/Observable.js';
|
|
25
|
+
import { squaredDistance, squaredDistanceToSegment } from 'ol/coordinate.js';
|
|
26
|
+
import { getCenter, createEmpty, extend } from 'ol/extent.js';
|
|
19
27
|
import Modal from 'modal-vanilla';
|
|
20
|
-
import VectorLayer from 'ol/layer/Vector.js';
|
|
21
28
|
import { Mixin } from 'ts-mixer';
|
|
22
29
|
import Layer from 'ol/layer/Base.js';
|
|
23
30
|
import GeoJSON from 'ol/format/GeoJSON.js';
|
|
24
|
-
import VectorSource, { VectorSourceEvent } from 'ol/source/Vector.js';
|
|
25
31
|
import { transformExtent } from 'ol/proj.js';
|
|
26
32
|
import { bbox } from 'ol/loadingstrategy.js';
|
|
27
33
|
import TileLayer from 'ol/layer/Tile.js';
|
|
@@ -33,11 +39,9 @@ import WFS from 'ol/format/WFS.js';
|
|
|
33
39
|
import { Style, Stroke, Fill, Circle } from 'ol/style.js';
|
|
34
40
|
import BaseObject from 'ol/Object.js';
|
|
35
41
|
import Circle$1 from 'ol/geom/Circle.js';
|
|
36
|
-
import GeometryCollection$1 from 'ol/geom/GeometryCollection.js';
|
|
37
42
|
import Feature from 'ol/Feature.js';
|
|
38
43
|
import { fromCircle } from 'ol/geom/Polygon.js';
|
|
39
44
|
import WFSCapabilities from 'ol-wfs-capabilities';
|
|
40
|
-
import { getCenter } from 'ol/extent.js';
|
|
41
45
|
import Overlay from 'ol/Overlay.js';
|
|
42
46
|
|
|
43
47
|
// External
|
|
@@ -83,6 +87,10 @@ const showError = (msg, originalError = null, layerName = '') => {
|
|
|
83
87
|
const es = {
|
|
84
88
|
labels: {
|
|
85
89
|
select: 'Seleccionar',
|
|
90
|
+
selectBox: 'Seleccionar elementos dibujando una caja',
|
|
91
|
+
selectFreehand: 'Seleccionar elementos dibujando a mano alzada',
|
|
92
|
+
query: 'Consultar información del elemento',
|
|
93
|
+
featureInfo: 'Información del elemento',
|
|
86
94
|
addElement: 'Modo dibujo',
|
|
87
95
|
editElement: 'Editar elemento',
|
|
88
96
|
save: 'Guardar',
|
|
@@ -92,9 +100,11 @@ const es = {
|
|
|
92
100
|
upload: 'Subir',
|
|
93
101
|
editMode: 'Modo Edición',
|
|
94
102
|
confirmDelete: '¿Estás seguro de borrar el elemento?',
|
|
103
|
+
confirmDeleteElements: '¿Borrar los {} elementos?',
|
|
95
104
|
geomTypeNotSupported: 'Geometría no compatible con la capa',
|
|
96
105
|
editFields: 'Editar campos',
|
|
97
106
|
editGeom: 'Editar geometría',
|
|
107
|
+
continueLine: 'Continuar dibujando desde aquí',
|
|
98
108
|
selectDrawType: 'Tipo de geometría para dibujar',
|
|
99
109
|
uploadToLayer: 'Subir archivo a la capa seleccionada',
|
|
100
110
|
uploadFeatures: 'Subida de elementos a la capa',
|
|
@@ -102,7 +112,12 @@ const es = {
|
|
|
102
112
|
invalidFeatures: 'Invalidas',
|
|
103
113
|
loading: 'Cargando...',
|
|
104
114
|
toggleVisibility: 'Cambiar visibilidad de la capa',
|
|
105
|
-
|
|
115
|
+
fullscreen: 'Pantalla completa',
|
|
116
|
+
close: 'Cerrar',
|
|
117
|
+
multipleValues: 'Valores múltiples',
|
|
118
|
+
editElements: 'Editar elementos ({})',
|
|
119
|
+
multipleEditNotice: 'Edición múltiple activada. Los cambios se aplicarán a los {} elementos seleccionados.',
|
|
120
|
+
geoserverLayers: 'Capas de GeoServer'
|
|
106
121
|
},
|
|
107
122
|
errors: {
|
|
108
123
|
capabilities: 'No se pudieron obtener las Capabilidades del GeoServer',
|
|
@@ -123,6 +138,10 @@ const es = {
|
|
|
123
138
|
const en = {
|
|
124
139
|
labels: {
|
|
125
140
|
select: 'Select',
|
|
141
|
+
selectBox: 'Select features by drawing a box',
|
|
142
|
+
selectFreehand: 'Select features by freehand drawing',
|
|
143
|
+
query: 'Query feature info',
|
|
144
|
+
featureInfo: 'Feature info',
|
|
126
145
|
addElement: 'Toggle Draw mode',
|
|
127
146
|
editElement: 'Edit feature',
|
|
128
147
|
save: 'Save',
|
|
@@ -132,9 +151,11 @@ const en = {
|
|
|
132
151
|
upload: 'Upload',
|
|
133
152
|
editMode: 'Edit Mode',
|
|
134
153
|
confirmDelete: 'Are you sure to delete the feature?',
|
|
154
|
+
confirmDeleteElements: 'Delete the {} selected features?',
|
|
135
155
|
geomTypeNotSupported: 'Geometry not supported by layer',
|
|
136
156
|
editFields: 'Edit fields',
|
|
137
157
|
editGeom: 'Edit geometry',
|
|
158
|
+
continueLine: 'Continue drawing from here',
|
|
138
159
|
selectDrawType: 'Geometry type to draw',
|
|
139
160
|
uploadToLayer: 'Upload file to selected layer',
|
|
140
161
|
uploadFeatures: 'Uploaded features to layer',
|
|
@@ -142,7 +163,12 @@ const en = {
|
|
|
142
163
|
invalidFeatures: 'Invalid',
|
|
143
164
|
loading: 'Loading...',
|
|
144
165
|
toggleVisibility: 'Toggle layer visibility',
|
|
145
|
-
|
|
166
|
+
fullscreen: 'Fullscreen',
|
|
167
|
+
close: 'Close',
|
|
168
|
+
multipleValues: 'Multiple values',
|
|
169
|
+
editElements: 'Edit features ({})',
|
|
170
|
+
multipleEditNotice: 'Multi-edit active. Changes will be applied to the {} selected features.',
|
|
171
|
+
geoserverLayers: 'Geoserver Layers'
|
|
146
172
|
},
|
|
147
173
|
errors: {
|
|
148
174
|
capabilities: 'GeoServer Capabilities could not be downloaded.',
|
|
@@ -163,6 +189,10 @@ const en = {
|
|
|
163
189
|
const zh = {
|
|
164
190
|
labels: {
|
|
165
191
|
select: '选择',
|
|
192
|
+
selectBox: '框选元素',
|
|
193
|
+
selectFreehand: '手绘选择元素',
|
|
194
|
+
query: '查询元素信息',
|
|
195
|
+
featureInfo: '元素信息',
|
|
166
196
|
addElement: '切换绘图类型',
|
|
167
197
|
editElement: '编辑元素',
|
|
168
198
|
save: '保存',
|
|
@@ -172,9 +202,11 @@ const zh = {
|
|
|
172
202
|
upload: '上传',
|
|
173
203
|
editMode: '编辑模式',
|
|
174
204
|
confirmDelete: '确认删除元素?',
|
|
205
|
+
confirmDeleteElements: '确认删除选中的 {} 个元素?',
|
|
175
206
|
geomTypeNotSupported: '图层不支持该几何',
|
|
176
207
|
editFields: '编辑区域',
|
|
177
208
|
editGeom: '编辑几何',
|
|
209
|
+
continueLine: '从此处继续绘制',
|
|
178
210
|
selectDrawType: '几何类型',
|
|
179
211
|
uploadToLayer: '通过文件上传图层',
|
|
180
212
|
uploadFeatures: '上传元素到图层',
|
|
@@ -182,7 +214,12 @@ const zh = {
|
|
|
182
214
|
invalidFeatures: '不合法',
|
|
183
215
|
loading: '加载中...',
|
|
184
216
|
toggleVisibility: '切换图层透明度',
|
|
185
|
-
|
|
217
|
+
fullscreen: '全屏',
|
|
218
|
+
close: '关闭',
|
|
219
|
+
multipleValues: '多个值',
|
|
220
|
+
editElements: '编辑元素({})',
|
|
221
|
+
multipleEditNotice: '多选编辑已激活。更改将应用于所有 {} 个选定元素。',
|
|
222
|
+
geoserverLayers: 'GeoServer 图层'
|
|
186
223
|
},
|
|
187
224
|
errors: {
|
|
188
225
|
capabilities: '无法加载GeoServer服务所支持的能力.',
|
|
@@ -217,12 +254,37 @@ const setLang = (lang = 'en', customI18n = null) => {
|
|
|
217
254
|
I18N = Object.assign(Object.assign({}, I18N), customI18n);
|
|
218
255
|
}
|
|
219
256
|
};
|
|
257
|
+
/**
|
|
258
|
+
* /**
|
|
259
|
+
* For translations thas has a variable "{}"" to be replaced inside
|
|
260
|
+
* @param string
|
|
261
|
+
* @param args
|
|
262
|
+
* @returns
|
|
263
|
+
*/
|
|
264
|
+
const I18N_ = (string, ...args) => {
|
|
265
|
+
var _a;
|
|
266
|
+
let text = (_a = I18N.labels) === null || _a === void 0 ? void 0 : _a[string];
|
|
267
|
+
if (!text) {
|
|
268
|
+
text = I18N[string];
|
|
269
|
+
}
|
|
270
|
+
if (!text) {
|
|
271
|
+
console.error('Translation not found', string);
|
|
272
|
+
text = string;
|
|
273
|
+
}
|
|
274
|
+
if (args.length) {
|
|
275
|
+
args.forEach((arg) => {
|
|
276
|
+
text = text.replace(/{}/, arg);
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
return text;
|
|
280
|
+
};
|
|
220
281
|
|
|
221
282
|
let loadingDiv;
|
|
222
283
|
const initLoading = () => {
|
|
223
284
|
loadingDiv = document.createElement('div');
|
|
224
285
|
loadingDiv.className = 'ol-wfst--tools-control--loading';
|
|
225
|
-
loadingDiv.
|
|
286
|
+
loadingDiv.setAttribute('role', 'progressbar');
|
|
287
|
+
loadingDiv.setAttribute('aria-label', I18N.labels.loading);
|
|
226
288
|
return loadingDiv;
|
|
227
289
|
};
|
|
228
290
|
const showLoading = (bool = true) => {
|
|
@@ -260,13 +322,27 @@ var Modes;
|
|
|
260
322
|
(function (Modes) {
|
|
261
323
|
Modes["Edit"] = "EDIT";
|
|
262
324
|
Modes["Draw"] = "DRAW";
|
|
325
|
+
Modes["Query"] = "QUERY";
|
|
263
326
|
})(Modes || (Modes = {}));
|
|
327
|
+
var SelectionMode;
|
|
328
|
+
(function (SelectionMode) {
|
|
329
|
+
SelectionMode["Single"] = "SINGLE";
|
|
330
|
+
SelectionMode["Box"] = "BOX";
|
|
331
|
+
SelectionMode["Freehand"] = "FREEHAND";
|
|
332
|
+
})(SelectionMode || (SelectionMode = {}));
|
|
333
|
+
let selectionMode = SelectionMode.Single;
|
|
264
334
|
function activateMode(m = null) {
|
|
265
335
|
mode = m;
|
|
266
336
|
}
|
|
267
337
|
function getMode() {
|
|
268
338
|
return mode;
|
|
269
339
|
}
|
|
340
|
+
function setSelectionMode(m) {
|
|
341
|
+
selectionMode = m;
|
|
342
|
+
}
|
|
343
|
+
function getSelectionMode() {
|
|
344
|
+
return selectionMode;
|
|
345
|
+
}
|
|
270
346
|
const editedFeatures = new Set();
|
|
271
347
|
const mapLayers = {};
|
|
272
348
|
function setMap(m) {
|
|
@@ -305,16 +381,23 @@ function isFeatureEdited(feature) {
|
|
|
305
381
|
*/
|
|
306
382
|
class BaseLayer extends Layer {
|
|
307
383
|
/**
|
|
384
|
+
* Initialize the layer: request DescribeFeatureType and resolve when it
|
|
385
|
+
* finishes (whether successful or not).
|
|
386
|
+
*
|
|
308
387
|
* @private
|
|
388
|
+
* @returns Promise that resolves when the layer is initialized
|
|
309
389
|
*/
|
|
310
|
-
_init() {
|
|
390
|
+
async _init() {
|
|
311
391
|
const geoserver = this.getGeoserver();
|
|
312
392
|
if (geoserver.isLoaded()) {
|
|
313
|
-
this.getAndUpdateDescribeFeatureType();
|
|
393
|
+
await this.getAndUpdateDescribeFeatureType();
|
|
314
394
|
}
|
|
315
395
|
else {
|
|
316
|
-
|
|
317
|
-
|
|
396
|
+
await new Promise((resolve) => {
|
|
397
|
+
geoserver.once('change:capabilities', async () => {
|
|
398
|
+
await this.getAndUpdateDescribeFeatureType();
|
|
399
|
+
resolve();
|
|
400
|
+
});
|
|
318
401
|
});
|
|
319
402
|
}
|
|
320
403
|
}
|
|
@@ -390,13 +473,13 @@ class BaseLayer extends Layer {
|
|
|
390
473
|
}
|
|
391
474
|
/**
|
|
392
475
|
* @public
|
|
393
|
-
* @param
|
|
476
|
+
* @param featureIds
|
|
394
477
|
* @returns
|
|
395
478
|
*/
|
|
396
|
-
async maybeLockFeature(
|
|
479
|
+
async maybeLockFeature(featureIds) {
|
|
397
480
|
const geoserver = this.getGeoserver();
|
|
398
481
|
if (geoserver.getUseLockFeature() && geoserver.hasLockFeature()) {
|
|
399
|
-
return await geoserver.lockFeature(
|
|
482
|
+
return await geoserver.lockFeature(featureIds, this.get(BaseLayerProperty.NAME));
|
|
400
483
|
}
|
|
401
484
|
return null;
|
|
402
485
|
}
|
|
@@ -435,44 +518,48 @@ var BaseLayerProperty;
|
|
|
435
518
|
*/
|
|
436
519
|
class WfsSource extends VectorSource {
|
|
437
520
|
constructor(options) {
|
|
438
|
-
super(Object.assign(Object.assign({}, options), { format: new GeoJSON(), loader:
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
521
|
+
super(Object.assign(Object.assign({}, options), { format: new GeoJSON(), loader: (extent, resolution, projection, success, failure) => {
|
|
522
|
+
void (async () => {
|
|
523
|
+
try {
|
|
524
|
+
// If bbox, add extent to the request
|
|
525
|
+
if (options.strategy == bbox) {
|
|
526
|
+
const extentGeoServer = transformExtent(extent, projection.getCode(), options.geoServerAdvanced.projection);
|
|
527
|
+
// https://docs.geoserver.org/stable/en/user/services/wfs/reference.html
|
|
528
|
+
// request features using a bounding box with CRS maybe different from featureTypes native CRS
|
|
529
|
+
this.urlParams.set('bbox', extentGeoServer.toString() +
|
|
530
|
+
`,${options.geoServerAdvanced.projection}`);
|
|
531
|
+
}
|
|
532
|
+
const url_fetch = options.geoserverUrl +
|
|
533
|
+
'?' +
|
|
534
|
+
this.urlParams.toString();
|
|
535
|
+
const response = await fetch(url_fetch, {
|
|
536
|
+
headers: options.headers,
|
|
537
|
+
credentials: options.credentials
|
|
538
|
+
});
|
|
539
|
+
if (!response.ok) {
|
|
540
|
+
throw new Error('');
|
|
541
|
+
}
|
|
542
|
+
const data = await response.json();
|
|
543
|
+
if (data.exceptions) {
|
|
544
|
+
throw new Error(parseError(data));
|
|
545
|
+
}
|
|
546
|
+
const features = this.getFormat().readFeatures(data, {
|
|
547
|
+
featureProjection: projection.getCode(),
|
|
548
|
+
dataProjection: options.geoServerAdvanced.projection
|
|
549
|
+
});
|
|
550
|
+
features.forEach((feature) => {
|
|
551
|
+
feature.set('_layerName_', options.name,
|
|
552
|
+
/* silent = */ true);
|
|
553
|
+
});
|
|
554
|
+
this.addFeatures(features);
|
|
555
|
+
success(features);
|
|
455
556
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
557
|
+
catch (err) {
|
|
558
|
+
this.removeLoadedExtent(extent);
|
|
559
|
+
showError(I18N.errors.geoserver, err, options.name);
|
|
560
|
+
failure();
|
|
459
561
|
}
|
|
460
|
-
|
|
461
|
-
featureProjection: projection.getCode(),
|
|
462
|
-
dataProjection: options.geoServerAdvanced.projection
|
|
463
|
-
});
|
|
464
|
-
features.forEach((feature) => {
|
|
465
|
-
feature.set('_layerName_', options.name,
|
|
466
|
-
/* silent = */ true);
|
|
467
|
-
});
|
|
468
|
-
this.addFeatures(features);
|
|
469
|
-
success(features);
|
|
470
|
-
}
|
|
471
|
-
catch (err) {
|
|
472
|
-
this.removeLoadedExtent(extent);
|
|
473
|
-
showError(I18N.errors.geoserver, err, options.name);
|
|
474
|
-
failure();
|
|
475
|
-
}
|
|
562
|
+
})();
|
|
476
563
|
} }));
|
|
477
564
|
this.urlParams = new URLSearchParams({
|
|
478
565
|
SERVICE: 'wfs',
|
|
@@ -620,7 +707,7 @@ class WmsLayer extends Mixin(BaseLayer, (TileLayer)) {
|
|
|
620
707
|
this._loadingCount = 0;
|
|
621
708
|
this._loadedCount = 0;
|
|
622
709
|
/**
|
|
623
|
-
* Return the full accuracy
|
|
710
|
+
* Return the full accuracy geometries to replace the features from GetFeatureInfo
|
|
624
711
|
* @param featuresId
|
|
625
712
|
* @returns
|
|
626
713
|
*/
|
|
@@ -631,10 +718,10 @@ class WmsLayer extends Mixin(BaseLayer, (TileLayer)) {
|
|
|
631
718
|
INFO_FORMAT: 'application/json',
|
|
632
719
|
REQUEST: 'GetFeature',
|
|
633
720
|
TYPENAME: this.get('name'),
|
|
634
|
-
MAXFEATURES:
|
|
721
|
+
MAXFEATURES: String(featuresId.length),
|
|
635
722
|
OUTPUTFORMAT: 'application/json',
|
|
636
723
|
SRSNAME: getMap().getView().getProjection().getCode(),
|
|
637
|
-
FEATUREID:
|
|
724
|
+
FEATUREID: featuresId.join(',')
|
|
638
725
|
});
|
|
639
726
|
const url = this.getSource().getUrls()[0] + '?' + queryParams.toString();
|
|
640
727
|
try {
|
|
@@ -647,7 +734,11 @@ class WmsLayer extends Mixin(BaseLayer, (TileLayer)) {
|
|
|
647
734
|
throw new Error(`${I18N.errors.getFeatures} ${response.status}`);
|
|
648
735
|
}
|
|
649
736
|
const data = await response.json();
|
|
650
|
-
|
|
737
|
+
const fullById = {};
|
|
738
|
+
this._parseFeaturesFromResponse(data).forEach((feature) => {
|
|
739
|
+
fullById[String(feature.getId())] = feature;
|
|
740
|
+
});
|
|
741
|
+
return featuresId.map((id) => fullById[String(id)]).filter(Boolean);
|
|
651
742
|
}
|
|
652
743
|
catch (err) {
|
|
653
744
|
console.error(err);
|
|
@@ -657,6 +748,9 @@ class WmsLayer extends Mixin(BaseLayer, (TileLayer)) {
|
|
|
657
748
|
if (options.beforeTransactFeature) {
|
|
658
749
|
this.beforeTransactFeature = options.beforeTransactFeature;
|
|
659
750
|
}
|
|
751
|
+
if (options.beforeShowFieldsModal) {
|
|
752
|
+
this.beforeShowFieldsModal = options.beforeShowFieldsModal;
|
|
753
|
+
}
|
|
660
754
|
this._formatGeoJSON = new GeoJSON();
|
|
661
755
|
const geoserver = options.geoserver;
|
|
662
756
|
const source = new WmsSource({
|
|
@@ -712,6 +806,112 @@ class WmsLayer extends Mixin(BaseLayer, (TileLayer)) {
|
|
|
712
806
|
FEATURE_COUNT: 1,
|
|
713
807
|
EXCEPTIONS: 'application/json'
|
|
714
808
|
});
|
|
809
|
+
return this._requestFeatures(url);
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Request the WFS features that intersect the given geometry, selecting
|
|
813
|
+
* them with a WFS GetFeature + CQL INTERSECTS filter. Used to select
|
|
814
|
+
* features from a WMS layer with a box or a freehand lasso.
|
|
815
|
+
*
|
|
816
|
+
* @param geometry Geometry in the map view projection
|
|
817
|
+
* @returns
|
|
818
|
+
* @private
|
|
819
|
+
*/
|
|
820
|
+
async _getFeaturesInGeometry(geometry) {
|
|
821
|
+
var _a, _b, _c;
|
|
822
|
+
// Make sure the DescribeFeatureType is loaded to get the geometry
|
|
823
|
+
// field, waiting for it if the layer is still initializing
|
|
824
|
+
if (!this.getDescribeFeatureType()) {
|
|
825
|
+
await this.getAndUpdateDescribeFeatureType();
|
|
826
|
+
}
|
|
827
|
+
const geomField = (_b = (_a = this.getDescribeFeatureType()) === null || _a === void 0 ? void 0 : _a._parsed) === null || _b === void 0 ? void 0 : _b.geomField;
|
|
828
|
+
if (!geomField) {
|
|
829
|
+
showError(`${I18N.errors.layer} "${this.get('name')}"`);
|
|
830
|
+
return [];
|
|
831
|
+
}
|
|
832
|
+
const viewProj = getMap().getView().getProjection().getCode();
|
|
833
|
+
const nativeSrs = this._getNativeSrs();
|
|
834
|
+
// Work on a copy transformed to the layer native projection so the
|
|
835
|
+
// server evaluates the INTERSECTS filter against the proper SRID
|
|
836
|
+
const polygon = geometry.clone();
|
|
837
|
+
if (nativeSrs !== viewProj) {
|
|
838
|
+
try {
|
|
839
|
+
polygon.transform(viewProj, nativeSrs);
|
|
840
|
+
}
|
|
841
|
+
catch (err) {
|
|
842
|
+
console.error(err);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
const ring = polygon.getCoordinates()[0] || [];
|
|
846
|
+
const ringWkt = ring.map(([x, y]) => `${x} ${y}`).join(', ');
|
|
847
|
+
// Force the filter geometry CRS with the EWKT SRID prefix so the
|
|
848
|
+
// server evaluates the INTERSECTS against the layer projection
|
|
849
|
+
// instead of its native storage SRS
|
|
850
|
+
const sridMatch = (_c = nativeSrs.match(/(?:EPSG\s*::?\s*|EPSG\/0\/)(\d+)/i)) !== null && _c !== void 0 ? _c : nativeSrs.match(/(\d+)$/);
|
|
851
|
+
const cql = sridMatch
|
|
852
|
+
? `INTERSECTS(${geomField}, SRID=${sridMatch[1]};POLYGON((${ringWkt})))`
|
|
853
|
+
: `INTERSECTS(${geomField}, POLYGON((${ringWkt})))`;
|
|
854
|
+
const queryParams = new URLSearchParams({
|
|
855
|
+
SERVICE: 'wfs',
|
|
856
|
+
VERSION: '2.0.0',
|
|
857
|
+
REQUEST: 'GetFeature',
|
|
858
|
+
TYPENAME: this.get('name'),
|
|
859
|
+
OUTPUTFORMAT: 'application/json',
|
|
860
|
+
SRSNAME: viewProj,
|
|
861
|
+
CQL_FILTER: cql
|
|
862
|
+
});
|
|
863
|
+
const url = this.getSource().getUrls()[0] + '?' + queryParams.toString();
|
|
864
|
+
// The server returns the features in the map view projection (same as
|
|
865
|
+
// the full resolution request), so no further transformation is needed
|
|
866
|
+
return this._requestFeatures(url);
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* Resolve the layer native SRS so spatial filters are evaluated in the
|
|
870
|
+
* correct projection. It reads the DefaultCRS from the WFS capabilities,
|
|
871
|
+
* falling back to the geoserver advanced projection option and finally to
|
|
872
|
+
* the map view projection.
|
|
873
|
+
*
|
|
874
|
+
* @returns
|
|
875
|
+
* @private
|
|
876
|
+
*/
|
|
877
|
+
_getNativeSrs() {
|
|
878
|
+
var _a;
|
|
879
|
+
const geoserver = this.getGeoserver();
|
|
880
|
+
const viewProj = getMap().getView().getProjection().getCode();
|
|
881
|
+
const layerName = String(this.get('name') || '');
|
|
882
|
+
const layerLocalName = layerName.split(':').pop();
|
|
883
|
+
const featureTypeList = (_a = geoserver.getParsedCapabilities()) === null || _a === void 0 ? void 0 : _a.FeatureTypeList;
|
|
884
|
+
const featureType = Array.isArray(featureTypeList)
|
|
885
|
+
? featureTypeList.find((ft) => {
|
|
886
|
+
const featureTypeName = String((ft === null || ft === void 0 ? void 0 : ft.Name) || '');
|
|
887
|
+
return (featureTypeName === layerName ||
|
|
888
|
+
featureTypeName === layerLocalName ||
|
|
889
|
+
featureTypeName.split(':').pop() === layerLocalName);
|
|
890
|
+
})
|
|
891
|
+
: undefined;
|
|
892
|
+
const defaultCrs = (featureType === null || featureType === void 0 ? void 0 : featureType.DefaultCRS)
|
|
893
|
+
? String(featureType.DefaultCRS)
|
|
894
|
+
: '';
|
|
895
|
+
const epsgMatch = defaultCrs.match(/(?:EPSG\s*::?\s*|EPSG\/0\/)(\d+)/i);
|
|
896
|
+
if (epsgMatch) {
|
|
897
|
+
return `EPSG:${epsgMatch[1]}`;
|
|
898
|
+
}
|
|
899
|
+
const advanced = geoserver.getAdvanced().projection;
|
|
900
|
+
if (advanced) {
|
|
901
|
+
return String(advanced);
|
|
902
|
+
}
|
|
903
|
+
return viewProj;
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* Fetch and parse the features from a request url, replacing the
|
|
907
|
+
* low resolution geometries (GetFeatureInfo) with the full resolution
|
|
908
|
+
* ones requested by FEATUREID when possible.
|
|
909
|
+
*
|
|
910
|
+
* @param url
|
|
911
|
+
* @returns
|
|
912
|
+
* @private
|
|
913
|
+
*/
|
|
914
|
+
async _requestFeatures(url) {
|
|
715
915
|
const geoserver = this.getGeoserver();
|
|
716
916
|
try {
|
|
717
917
|
const response = await fetch(url, {
|
|
@@ -724,6 +924,9 @@ class WmsLayer extends Mixin(BaseLayer, (TileLayer)) {
|
|
|
724
924
|
const data = await response.json();
|
|
725
925
|
let features = this._parseFeaturesFromResponse(data);
|
|
726
926
|
const featuresId = features.map((f) => f.getId());
|
|
927
|
+
if (!featuresId.length) {
|
|
928
|
+
return [];
|
|
929
|
+
}
|
|
727
930
|
const fullResList = await this._getFullResGeometryById(featuresId);
|
|
728
931
|
if (fullResList) {
|
|
729
932
|
features = fullResList;
|
|
@@ -773,50 +976,95 @@ class WmsLayer extends Mixin(BaseLayer, (TileLayer)) {
|
|
|
773
976
|
}
|
|
774
977
|
}
|
|
775
978
|
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
return tagName(attrs, children);
|
|
787
|
-
const elem = tagName === null
|
|
788
|
-
? new DocumentFragment()
|
|
979
|
+
function createElement(tagName, props) {
|
|
980
|
+
props = props || {};
|
|
981
|
+
let children = props.children || [];
|
|
982
|
+
children = Array.isArray(children) ? children : [children];
|
|
983
|
+
delete props.children;
|
|
984
|
+
if (typeof tagName === 'function') {
|
|
985
|
+
return tagName(props, children);
|
|
986
|
+
}
|
|
987
|
+
const elem = tagName === 'fragment'
|
|
988
|
+
? document.createDocumentFragment()
|
|
789
989
|
: document.createElement(tagName);
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
value
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
990
|
+
if (elem instanceof HTMLElement) {
|
|
991
|
+
Object.entries(props).forEach(([name, value]) => {
|
|
992
|
+
if (value != null) {
|
|
993
|
+
// listener
|
|
994
|
+
if (name.startsWith('on') && typeof value === 'function') {
|
|
995
|
+
elem.addEventListener(name
|
|
996
|
+
.slice(2)
|
|
997
|
+
.toLowerCase(), value);
|
|
998
|
+
}
|
|
999
|
+
else if (name === 'className') {
|
|
798
1000
|
elem.setAttribute('class', value.toString());
|
|
799
|
-
|
|
1001
|
+
}
|
|
1002
|
+
else if (name === 'htmlFor') {
|
|
800
1003
|
elem.setAttribute('for', value.toString());
|
|
801
|
-
|
|
1004
|
+
}
|
|
1005
|
+
else {
|
|
802
1006
|
elem.setAttribute(name, value.toString());
|
|
1007
|
+
}
|
|
803
1008
|
}
|
|
804
|
-
}
|
|
805
|
-
}
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
806
1011
|
for (const child of children) {
|
|
807
|
-
if (!child)
|
|
1012
|
+
if (!child) {
|
|
808
1013
|
continue;
|
|
809
|
-
|
|
1014
|
+
}
|
|
1015
|
+
if (Array.isArray(child)) {
|
|
810
1016
|
elem.append(...child);
|
|
1017
|
+
}
|
|
1018
|
+
else if (child instanceof Node) {
|
|
1019
|
+
elem.appendChild(child);
|
|
1020
|
+
}
|
|
811
1021
|
else {
|
|
812
|
-
|
|
813
|
-
elem.innerHTML += child;
|
|
814
|
-
else
|
|
815
|
-
elem.appendChild(child);
|
|
1022
|
+
elem.innerHTML += child;
|
|
816
1023
|
}
|
|
817
1024
|
}
|
|
818
1025
|
return elem;
|
|
819
1026
|
}
|
|
1027
|
+
function jsx(type, props) {
|
|
1028
|
+
return createElement(type, props);
|
|
1029
|
+
}
|
|
1030
|
+
function jsxs(type, props) {
|
|
1031
|
+
return createElement(type, props);
|
|
1032
|
+
}
|
|
1033
|
+
function Fragment(type, children) {
|
|
1034
|
+
return createElement('fragment', { children });
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
function uploadSvg() {
|
|
1038
|
+
return (new DOMParser().parseFromString("\r\n<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" width=\"512\" height=\"512\" viewBox=\"0 0 512 512\">\r\n<path d=\"M240 352h-240v128h480v-128h-240zM448 416h-64v-32h64v32zM112 160l128-128 128 128h-80v160h-96v-160z\"></path>\r\n</svg>\r\n", 'image/svg+xml')).firstChild;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
function drawSvg() {
|
|
1042
|
+
return (new DOMParser().parseFromString("<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" width=\"768\" height=\"768\" viewBox=\"0 0 768 768\">\r\n <path d=\"M663 225l-58.5 58.5-120-120 58.5-58.5q9-9 22.5-9t22.5 9l75 75q9 9 9 22.5t-9 22.5zM96 552l354-354 120 120-354 354h-120v-120z\"></path>\r\n</svg>", 'image/svg+xml')).firstChild;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
function selectSvg() {
|
|
1046
|
+
return (new DOMParser().parseFromString("<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" width=\"289\" height=\"448\" viewBox=\"0 0 289 448\">\r\n <path d=\"M283.25 260.75c4.75 4.5 6 11.5 3.5 17.25-2.5 6-8.25 10-14.75 10h-95.5l50.25 119c3.5 8.25-0.5 17.5-8.5 21l-44.25 18.75c-8.25 3.5-17.5-0.5-21-8.5l-47.75-113-78 78c-3 3-7 4.75-11.25 4.75-2 0-4.25-0.5-6-1.25-6-2.5-10-8.25-10-14.75v-376c0-6.5 4-12.25 10-14.75 1.75-0.75 4-1.25 6-1.25 4.25 0 8.25 1.5 11.25 4.75z\"></path>\r\n</svg>\r\n", 'image/svg+xml')).firstChild;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
function selectBoxSvg() {
|
|
1050
|
+
return (new DOMParser().parseFromString("<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\">\r\n <rect x=\"3.5\" y=\"3.5\" width=\"17\" height=\"17\" rx=\"1.5\" fill=\"none\" stroke=\"#000\" stroke-width=\"2\" stroke-dasharray=\"4 2\"></rect>\r\n</svg>", 'image/svg+xml')).firstChild;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
function selectFreehandSvg() {
|
|
1054
|
+
return (new DOMParser().parseFromString("<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\">\r\n <path d=\"M4.59 6.89c.7-.71 1.4-1.35 1.71-1.22.5.2 0 1.03-.3 1.52-.25.42-2.86 3.89-2.86 6.31 0 1.28.48 2.34 1.34 2.98.75.56 1.74.73 2.64.46 1.07-.31 1.95-1.4 3.06-2.77 1.21-1.49 2.83-3.44 4.08-3.44 1.63 0 1.65 1.01 1.76 1.79-3.78.64-5.38 3.67-5.38 5.37 0 1.7 1.44 3.09 3.21 3.09 1.63 0 4.29-1.33 4.69-6.1H21v-2.5h-2.47c-.15-1.65-1.09-4.2-4.03-4.2-2.25 0-4.18 1.91-4.94 2.84-.58.73-2.06 2.48-2.29 2.72-.25.3-.68.84-1.11.84-.45 0-.72-.83-.36-1.92.35-1.09 1.4-2.86 1.85-3.52.78-1.14 1.3-1.92 1.3-3.28C8.95 3.69 7.31 3 6.44 3 5.12 3 3.97 4 3.72 4.25c-.36.36-.66.66-.88.93l1.75 1.71zm9.29 11.66c-.31 0-.74-.26-.74-.72 0-.6.73-2.2 2.87-2.76-.3 2.69-1.43 3.48-2.13 3.48z\"></path>\r\n</svg>", 'image/svg+xml')).firstChild;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
function infoSvg() {
|
|
1058
|
+
return (new DOMParser().parseFromString("<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\">\r\n <path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\"></path>\r\n</svg>", 'image/svg+xml')).firstChild;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
function visibilityOnSvg() {
|
|
1062
|
+
return (new DOMParser().parseFromString("<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" width=\"768\" height=\"768\" viewBox=\"0 0 768 768\">\n<path d=\"M384 288q39 0 67.5 28.5t28.5 67.5-28.5 67.5-67.5 28.5-67.5-28.5-28.5-67.5 28.5-67.5 67.5-28.5zM384 544.5q66 0 113.25-47.25t47.25-113.25-47.25-113.25-113.25-47.25-113.25 47.25-47.25 113.25 47.25 113.25 113.25 47.25zM384 144q118.5 0 214.5 66t138 174q-42 108-138 174t-214.5 66-214.5-66-138-174q42-108 138-174t214.5-66z\"></path>\n</svg>\n", 'image/svg+xml')).firstChild;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
function visibilityOffSvg() {
|
|
1066
|
+
return (new DOMParser().parseFromString("<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" width=\"768\" height=\"768\" viewBox=\"0 0 768 768\">\n<path d=\"M379.5 288h4.5q39 0 67.5 28.5t28.5 67.5v6zM241.5 313.5q-18 36-18 70.5 0 66 47.25 113.25t113.25 47.25q34.5 0 70.5-18l-49.5-49.5q-12 3-21 3-39 0-67.5-28.5t-28.5-67.5q0-9 3-21zM64.5 136.5l40.5-40.5 567 567-40.5 40.5q-7.5-7.5-47.25-46.5t-60.75-60q-64.5 27-139.5 27-118.5 0-214.5-66t-138-174q16.5-39 51.75-86.25t68.25-72.75q-18-18-50.25-51t-36.75-37.5zM384 223.5q-30 0-58.5 12l-69-69q58.5-22.5 127.5-22.5 118.5 0 213.75 66t137.25 174q-36 88.5-109.5 151.5l-93-93q12-28.5 12-58.5 0-66-47.25-113.25t-113.25-47.25z\"></path>\n</svg>\n", 'image/svg+xml')).firstChild;
|
|
1067
|
+
}
|
|
820
1068
|
|
|
821
1069
|
/**
|
|
822
1070
|
* Removes in the DOM the class of the tools
|
|
@@ -833,6 +1081,19 @@ const activateModeButtons = () => {
|
|
|
833
1081
|
if (btn) {
|
|
834
1082
|
btn.classList.add('wfst--active');
|
|
835
1083
|
}
|
|
1084
|
+
deactivateQueryButton();
|
|
1085
|
+
};
|
|
1086
|
+
const activateQueryButton = () => {
|
|
1087
|
+
const btn = document.querySelector('.ol-wfst--tools-control-btn-query');
|
|
1088
|
+
if (btn) {
|
|
1089
|
+
btn.classList.add('wfst--active');
|
|
1090
|
+
}
|
|
1091
|
+
};
|
|
1092
|
+
const deactivateQueryButton = () => {
|
|
1093
|
+
const btn = document.querySelector('.ol-wfst--tools-control-btn-query');
|
|
1094
|
+
if (btn) {
|
|
1095
|
+
btn.classList.remove('wfst--active');
|
|
1096
|
+
}
|
|
836
1097
|
};
|
|
837
1098
|
const activateDrawButton = () => {
|
|
838
1099
|
const btn = document.querySelector('.ol-wfst--tools-control-btn-draw');
|
|
@@ -840,6 +1101,29 @@ const activateDrawButton = () => {
|
|
|
840
1101
|
btn.classList.add('wfst--active');
|
|
841
1102
|
}
|
|
842
1103
|
};
|
|
1104
|
+
const activateSelectModeButton = (mode) => {
|
|
1105
|
+
const activeBtn = document.querySelector('.ol-wfst--tools-control-btn-select.wfst--active, .ol-wfst--tools-control-btn-select-box.wfst--active, .ol-wfst--tools-control-btn-select-freehand.wfst--active');
|
|
1106
|
+
if (activeBtn) {
|
|
1107
|
+
activeBtn.classList.remove('wfst--active');
|
|
1108
|
+
}
|
|
1109
|
+
deactivateQueryButton();
|
|
1110
|
+
const btnMap = {
|
|
1111
|
+
[SelectionMode.Single]: '.ol-wfst--tools-control-btn-select',
|
|
1112
|
+
[SelectionMode.Box]: '.ol-wfst--tools-control-btn-select-box',
|
|
1113
|
+
[SelectionMode.Freehand]: '.ol-wfst--tools-control-btn-select-freehand'
|
|
1114
|
+
};
|
|
1115
|
+
const btn = document.querySelector(btnMap[mode]);
|
|
1116
|
+
if (btn) {
|
|
1117
|
+
btn.classList.add('wfst--active');
|
|
1118
|
+
}
|
|
1119
|
+
};
|
|
1120
|
+
const deactivateSelectModeButton = () => {
|
|
1121
|
+
const activeBtn = document.querySelector('.ol-wfst--tools-control-btn-select.wfst--active, .ol-wfst--tools-control-btn-select-box.wfst--active, .ol-wfst--tools-control-btn-select-freehand.wfst--active');
|
|
1122
|
+
if (activeBtn) {
|
|
1123
|
+
activeBtn.classList.remove('wfst--active');
|
|
1124
|
+
}
|
|
1125
|
+
deactivateQueryButton();
|
|
1126
|
+
};
|
|
843
1127
|
class LayersControl extends Observable {
|
|
844
1128
|
constructor(uploads, uploadFormats) {
|
|
845
1129
|
super();
|
|
@@ -852,23 +1136,15 @@ class LayersControl extends Observable {
|
|
|
852
1136
|
* @public
|
|
853
1137
|
*/
|
|
854
1138
|
addLayerEl(layer) {
|
|
855
|
-
const container = document.querySelector('.wfst--tools-control--select-layers');
|
|
1139
|
+
const container = document.querySelector('.wfst--tools-control--select-layers-list');
|
|
856
1140
|
const layerName = layer.get(BaseLayerProperty.NAME);
|
|
857
1141
|
const checked = layer === getActiveLayerToInsertEls() ? { checked: true } : {};
|
|
858
|
-
const input = (
|
|
859
|
-
const layerDom = (
|
|
1142
|
+
const input = (jsx("input", Object.assign({ value: layerName, id: `wfst--${layerName}`, type: "radio", className: "ol-wfst--tools-control-input", name: "wfst--select-layer" }, checked, { onChange: (evt) => this._layerChangeHandler(evt, layer) })));
|
|
1143
|
+
const layerDom = (jsx("div", { className: `wfst--layer-control
|
|
860
1144
|
${layer.getVisible() ? 'ol-wfst--visible-on' : ''}
|
|
861
1145
|
${layer === getActiveLayerToInsertEls()
|
|
862
1146
|
? 'ol-wfst--selected-on'
|
|
863
|
-
: ''}`, "data-layer": layerName },
|
|
864
|
-
createElement("div", { className: "ol-wfst--tools-control-visible" },
|
|
865
|
-
createElement("span", { className: "ol-wfst--tools-control-visible-btn ol-wfst--visible-btn-on", title: I18N.labels.toggleVisibility, onClick: (evt) => this._visibilityClickHandler(evt) },
|
|
866
|
-
createElement("img", { src: img$3 })),
|
|
867
|
-
createElement("span", { className: "ol-wfst--tools-control-visible-btn ol-wfst--visible-btn-off", title: I18N.labels.toggleVisibility, onClick: (evt) => this._visibilityClickHandler(evt) },
|
|
868
|
-
createElement("img", { src: img$2 }))),
|
|
869
|
-
createElement("label", { htmlFor: `wfst--${layerName}` },
|
|
870
|
-
input,
|
|
871
|
-
createElement("span", { title: layer.getDescribeFeatureType()._parsed.geomType }, layer.get(BaseLayerProperty.LABEL)))));
|
|
1147
|
+
: ''}`, "data-layer": layerName, children: jsxs("label", { htmlFor: `wfst--${layerName}`, children: [jsxs("div", { className: "ol-wfst--tools-control-visible", children: [jsx("span", { className: "ol-wfst--tools-control-visible-btn ol-wfst--visible-btn-on", title: I18N.labels.toggleVisibility, onClick: (evt) => this._visibilityClickHandler(evt), children: visibilityOnSvg() }), jsx("span", { className: "ol-wfst--tools-control-visible-btn ol-wfst--visible-btn-off", title: I18N.labels.toggleVisibility, onClick: (evt) => this._visibilityClickHandler(evt), children: visibilityOffSvg() })] }), input, jsx("span", { title: `${layer instanceof WfsLayer ? 'WFS' : 'WMS'} - ${layer.getDescribeFeatureType()._parsed.geomType}`, children: layer.get(BaseLayerProperty.LABEL) })] }) }));
|
|
872
1148
|
container.appendChild(layerDom);
|
|
873
1149
|
if (layer === getActiveLayerToInsertEls()) {
|
|
874
1150
|
input.dispatchEvent(new Event('change'));
|
|
@@ -964,34 +1240,32 @@ class LayersControl extends Observable {
|
|
|
964
1240
|
this.dispatchEvent('changeLayer');
|
|
965
1241
|
}
|
|
966
1242
|
render() {
|
|
967
|
-
return (
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
})))),
|
|
994
|
-
createElement("div", { className: "wfst--tools-control--select-layers" })));
|
|
1243
|
+
return (jsxs(Fragment, { children: [jsxs("div", { className: "wfst--tools-control--head", children: [jsxs("div", { className: "ol-wfst--tools-control-select-cnt", children: [jsx("button", { className: "ol-wfst--tools-control-btn ol-wfst--tools-control-btn-select wfst--active", type: "button", title: I18N.labels.select, onClick: () => {
|
|
1244
|
+
this.dispatchEvent('selectModeSingle');
|
|
1245
|
+
}, children: selectSvg() }), jsx("button", { className: "ol-wfst--tools-control-btn ol-wfst--tools-control-btn-select-box", type: "button", title: I18N.labels.selectBox, onClick: () => {
|
|
1246
|
+
this.dispatchEvent('selectModeBox');
|
|
1247
|
+
}, children: selectBoxSvg() }), jsx("button", { className: "ol-wfst--tools-control-btn ol-wfst--tools-control-btn-select-freehand", type: "button", title: I18N.labels.selectFreehand, onClick: () => {
|
|
1248
|
+
this.dispatchEvent('selectModeFreehand');
|
|
1249
|
+
}, children: selectFreehandSvg() })] }), jsx("div", { className: "ol-wfst--tools-control-query-cnt", children: jsx("button", { className: "ol-wfst--tools-control-btn ol-wfst--tools-control-btn-query", type: "button", title: I18N.labels.query, onClick: () => {
|
|
1250
|
+
this.dispatchEvent('queryMode');
|
|
1251
|
+
}, children: infoSvg() }) }), jsxs("div", { className: "ol-wfst--tools-control-draw-cnt", children: [jsx("button", { className: "ol-wfst--tools-control-btn ol-wfst--tools-control-btn-draw", type: "button", title: I18N.labels.addElement, onClick: () => {
|
|
1252
|
+
this.dispatchEvent('drawMode');
|
|
1253
|
+
}, children: drawSvg() }), jsx("select", { title: I18N.labels.selectDrawType, className: "wfst--tools-control--select-draw", onChange: (evt) => {
|
|
1254
|
+
const selectedValue = evt.target.value;
|
|
1255
|
+
this._changeStateSelect(getActiveLayerToInsertEls(), selectedValue);
|
|
1256
|
+
this.dispatchEvent('changeGeom');
|
|
1257
|
+
}, children: [
|
|
1258
|
+
GeometryType.Point,
|
|
1259
|
+
GeometryType.MultiPoint,
|
|
1260
|
+
GeometryType.LineString,
|
|
1261
|
+
GeometryType.MultiLineString,
|
|
1262
|
+
GeometryType.Polygon,
|
|
1263
|
+
GeometryType.MultiPolygon,
|
|
1264
|
+
GeometryType.Circle
|
|
1265
|
+
].map((type) => {
|
|
1266
|
+
// Show all options, but enable only the accepted ones
|
|
1267
|
+
return jsx("option", { value: type, children: type });
|
|
1268
|
+
}) })] }), this._uploads && (jsxs("div", { children: [jsx("input", { id: "ol-wfst--upload", type: "file", accept: this._uploadFormats, onChange: (evt) => this._uploads.process(evt) }), jsx("label", { className: "ol-wfst--tools-control-btn ol-wfst--tools-control-btn-upload", htmlFor: "ol-wfst--upload", title: I18N.labels.uploadToLayer, children: uploadSvg() })] }))] }), jsxs("div", { className: "wfst--tools-control--select-layers", children: [jsx("div", { className: "wfst--tools-control--select-layers-title", children: I18N.labels.geoserverLayers }), jsx("div", { className: "wfst--tools-control--select-layers-list" })] })] }));
|
|
995
1269
|
}
|
|
996
1270
|
}
|
|
997
1271
|
|
|
@@ -1230,6 +1504,7 @@ const getDefaultOptions = () => {
|
|
|
1230
1504
|
evtType: 'singleclick',
|
|
1231
1505
|
active: true,
|
|
1232
1506
|
showControl: true,
|
|
1507
|
+
fullscreen: true,
|
|
1233
1508
|
language: DEFAULT_LANGUAGE,
|
|
1234
1509
|
uploadFormats: '.geojson,.json,.kml',
|
|
1235
1510
|
processUpload: null,
|
|
@@ -1247,91 +1522,145 @@ const getDefaultOptions = () => {
|
|
|
1247
1522
|
};
|
|
1248
1523
|
|
|
1249
1524
|
class EditControlChangesEl extends Control {
|
|
1250
|
-
constructor(
|
|
1525
|
+
constructor(features) {
|
|
1251
1526
|
super({
|
|
1252
|
-
element: (
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
} }, I18N.labels.cancel),
|
|
1262
|
-
createElement("button", { type: "button", className: "btn btn-sm btn-primary", onClick: () => {
|
|
1263
|
-
this.dispatchEvent(new VectorSourceEvent('apply', feature));
|
|
1264
|
-
} }, I18N.labels.apply),
|
|
1265
|
-
createElement("button", { type: "button", className: "btn btn-sm btn-danger-outline", onClick: () => {
|
|
1266
|
-
this.dispatchEvent(new VectorSourceEvent('delete', feature));
|
|
1267
|
-
} }, I18N.labels.delete))))
|
|
1527
|
+
element: (jsx("div", { className: "ol-wfst--changes-control", children: jsxs("div", { className: "ol-wfst--changes-control-el", children: [jsxs("div", { className: "ol-wfst--changes-control-id", children: [jsx("b", { children: I18N.labels.editMode }), " -", ' ', jsx("i", { children: features.length > 1
|
|
1528
|
+
? I18N_('editElements', features.length)
|
|
1529
|
+
: String(features[0].getId()) })] }), jsx("button", { type: "button", className: "btn btn-sm btn-secondary", onClick: () => {
|
|
1530
|
+
this._dispatch('cancel', features);
|
|
1531
|
+
}, children: I18N.labels.cancel }), jsx("button", { type: "button", className: "btn btn-sm btn-primary", onClick: () => {
|
|
1532
|
+
this._dispatch('apply', features);
|
|
1533
|
+
}, children: I18N.labels.apply }), jsx("button", { type: "button", className: "btn btn-sm btn-danger-outline", onClick: () => {
|
|
1534
|
+
this._dispatch('delete', features);
|
|
1535
|
+
}, children: I18N.labels.delete })] }) }))
|
|
1268
1536
|
});
|
|
1269
1537
|
}
|
|
1538
|
+
_dispatch(type, features) {
|
|
1539
|
+
const evt = new VectorSourceEvent(type, features[0]);
|
|
1540
|
+
evt.features = features;
|
|
1541
|
+
this.dispatchEvent(evt);
|
|
1542
|
+
}
|
|
1270
1543
|
}
|
|
1271
1544
|
|
|
1272
1545
|
// Ol
|
|
1546
|
+
/**
|
|
1547
|
+
* Modern vertex handle mimicking the round "extend line" buttons shown at
|
|
1548
|
+
* line endpoints: a soft halo for depth and a white disc with a subtle
|
|
1549
|
+
* border. When `highlighted` is `true` (e.g. hovering a vertex with the
|
|
1550
|
+
* Modify interaction) the handle gets bigger with a stronger halo and a
|
|
1551
|
+
* darker border.
|
|
1552
|
+
*
|
|
1553
|
+
* @param highlighted Whether to render the bigger/highlighted state
|
|
1554
|
+
* @param geometry Style geometry function to position the handles
|
|
1555
|
+
* @private
|
|
1556
|
+
*/
|
|
1557
|
+
const editVertexStyles = (highlighted = false, geometry) => {
|
|
1558
|
+
const halo = highlighted ? 10 : 8;
|
|
1559
|
+
const haloColor = highlighted
|
|
1560
|
+
? 'rgba(0, 0, 0, 0.40)'
|
|
1561
|
+
: 'rgba(0, 0, 0, 0.28)';
|
|
1562
|
+
const radius = highlighted ? 7 : 6;
|
|
1563
|
+
const border = highlighted ? '#4b5563' : '#b3b3b3';
|
|
1564
|
+
const borderWidth = highlighted ? 2 : 1.5;
|
|
1565
|
+
return [
|
|
1566
|
+
new Style(Object.assign({ image: new Circle({
|
|
1567
|
+
radius: halo,
|
|
1568
|
+
fill: new Fill({
|
|
1569
|
+
color: haloColor
|
|
1570
|
+
})
|
|
1571
|
+
}) }, (geometry ? { geometry } : {}))),
|
|
1572
|
+
new Style(Object.assign({ image: new Circle({
|
|
1573
|
+
radius: radius,
|
|
1574
|
+
fill: new Fill({
|
|
1575
|
+
color: '#ffffff'
|
|
1576
|
+
}),
|
|
1577
|
+
stroke: new Stroke({
|
|
1578
|
+
width: borderWidth,
|
|
1579
|
+
color: border
|
|
1580
|
+
})
|
|
1581
|
+
}) }, (geometry ? { geometry } : {})))
|
|
1582
|
+
];
|
|
1583
|
+
};
|
|
1584
|
+
/**
|
|
1585
|
+
* Extract a `MultiPoint` with every vertex of the given feature geometry
|
|
1586
|
+
* (polygons and multi-line strings are flattened). Returns `undefined` when
|
|
1587
|
+
* the geometry has no coordinates.
|
|
1588
|
+
*
|
|
1589
|
+
* @param feature
|
|
1590
|
+
* @private
|
|
1591
|
+
*/
|
|
1592
|
+
const getFeatureVertices = (feature) => {
|
|
1593
|
+
let geometry = feature.getGeometry();
|
|
1594
|
+
if (geometry instanceof GeometryCollection) {
|
|
1595
|
+
geometry = geometry.getGeometries()[0];
|
|
1596
|
+
}
|
|
1597
|
+
const coordinates = geometry.getCoordinates();
|
|
1598
|
+
let flatCoordinates = null;
|
|
1599
|
+
if (geometry instanceof Polygon || geometry instanceof MultiLineString) {
|
|
1600
|
+
flatCoordinates = coordinates.flat(1);
|
|
1601
|
+
}
|
|
1602
|
+
else if (geometry instanceof MultiPolygon) {
|
|
1603
|
+
flatCoordinates = coordinates.flat(2);
|
|
1604
|
+
}
|
|
1605
|
+
else {
|
|
1606
|
+
flatCoordinates = coordinates;
|
|
1607
|
+
}
|
|
1608
|
+
if (!flatCoordinates || !flatCoordinates.length) {
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
return new MultiPoint(flatCoordinates);
|
|
1612
|
+
};
|
|
1273
1613
|
/**
|
|
1274
1614
|
* Master style that handles two modes on the Edit Layer:
|
|
1275
1615
|
* - one is the basic, showing only the vertices
|
|
1276
1616
|
* - and the other when modify is active, showing bigger vertices
|
|
1277
1617
|
*
|
|
1618
|
+
* When `hovered` is `true`, a white/red halo is prepended to highlight the
|
|
1619
|
+
* feature, indicating it can be selected on click.
|
|
1620
|
+
*
|
|
1278
1621
|
* @param feature
|
|
1622
|
+
* @param hovered
|
|
1279
1623
|
* @private
|
|
1280
1624
|
*/
|
|
1281
|
-
function styleFunction(feature) {
|
|
1282
|
-
const getVertexs = (feature) => {
|
|
1283
|
-
let geometry = feature.getGeometry();
|
|
1284
|
-
if (geometry instanceof GeometryCollection) {
|
|
1285
|
-
geometry = geometry.getGeometries()[0];
|
|
1286
|
-
}
|
|
1287
|
-
const coordinates = geometry.getCoordinates();
|
|
1288
|
-
let flatCoordinates = null;
|
|
1289
|
-
if (geometry instanceof Polygon ||
|
|
1290
|
-
geometry instanceof MultiLineString) {
|
|
1291
|
-
flatCoordinates = coordinates.flat(1);
|
|
1292
|
-
}
|
|
1293
|
-
else if (geometry instanceof MultiPolygon) {
|
|
1294
|
-
flatCoordinates = coordinates.flat(2);
|
|
1295
|
-
}
|
|
1296
|
-
else {
|
|
1297
|
-
flatCoordinates = coordinates;
|
|
1298
|
-
}
|
|
1299
|
-
if (!flatCoordinates || !flatCoordinates.length) {
|
|
1300
|
-
return;
|
|
1301
|
-
}
|
|
1302
|
-
return new MultiPoint(flatCoordinates);
|
|
1303
|
-
};
|
|
1625
|
+
function styleFunction(feature, hovered = false) {
|
|
1304
1626
|
let geometry = feature.getGeometry();
|
|
1305
1627
|
let type = geometry.getType();
|
|
1306
1628
|
if (geometry instanceof GeometryCollection) {
|
|
1307
1629
|
geometry = geometry.getGeometries()[0];
|
|
1308
1630
|
type = geometry.getType();
|
|
1309
1631
|
}
|
|
1632
|
+
const haloStyle = () => {
|
|
1633
|
+
return [
|
|
1634
|
+
new Style({
|
|
1635
|
+
image: new Circle({
|
|
1636
|
+
radius: 9,
|
|
1637
|
+
fill: new Fill({
|
|
1638
|
+
color: 'rgba(255, 255, 255, 0.9)'
|
|
1639
|
+
}),
|
|
1640
|
+
stroke: new Stroke({
|
|
1641
|
+
color: '#ff0000',
|
|
1642
|
+
width: 3
|
|
1643
|
+
})
|
|
1644
|
+
}),
|
|
1645
|
+
stroke: new Stroke({
|
|
1646
|
+
color: 'rgba(255, 255, 255, 0.9)',
|
|
1647
|
+
width: 8
|
|
1648
|
+
}),
|
|
1649
|
+
fill: new Fill({
|
|
1650
|
+
color: 'rgba(255, 255, 255, 0.9)'
|
|
1651
|
+
})
|
|
1652
|
+
})
|
|
1653
|
+
];
|
|
1654
|
+
};
|
|
1310
1655
|
switch (type) {
|
|
1311
1656
|
case GeometryType.Point:
|
|
1312
1657
|
case GeometryType.MultiPoint:
|
|
1313
1658
|
if (getMode() === Modes.Edit) {
|
|
1314
|
-
return
|
|
1315
|
-
new Style({
|
|
1316
|
-
image: new Circle({
|
|
1317
|
-
radius: 6,
|
|
1318
|
-
fill: new Fill({
|
|
1319
|
-
color: '#000000'
|
|
1320
|
-
})
|
|
1321
|
-
})
|
|
1322
|
-
}),
|
|
1323
|
-
new Style({
|
|
1324
|
-
image: new Circle({
|
|
1325
|
-
radius: 4,
|
|
1326
|
-
fill: new Fill({
|
|
1327
|
-
color: '#ff0000'
|
|
1328
|
-
})
|
|
1329
|
-
})
|
|
1330
|
-
})
|
|
1331
|
-
];
|
|
1659
|
+
return editVertexStyles();
|
|
1332
1660
|
}
|
|
1333
1661
|
else {
|
|
1334
1662
|
return [
|
|
1663
|
+
...(hovered ? haloStyle() : []),
|
|
1335
1664
|
new Style({
|
|
1336
1665
|
image: new Circle({
|
|
1337
1666
|
radius: 5,
|
|
@@ -1363,19 +1692,7 @@ function styleFunction(feature) {
|
|
|
1363
1692
|
color: 'rgba(255, 0, 0, 0.7)'
|
|
1364
1693
|
})
|
|
1365
1694
|
}),
|
|
1366
|
-
|
|
1367
|
-
image: new Circle({
|
|
1368
|
-
radius: 4,
|
|
1369
|
-
fill: new Fill({
|
|
1370
|
-
color: '#ff0000'
|
|
1371
|
-
}),
|
|
1372
|
-
stroke: new Stroke({
|
|
1373
|
-
width: 2,
|
|
1374
|
-
color: 'rgba(5, 5, 5, 0.9)'
|
|
1375
|
-
})
|
|
1376
|
-
}),
|
|
1377
|
-
geometry: (feature) => getVertexs(feature)
|
|
1378
|
-
}),
|
|
1695
|
+
...editVertexStyles(false, getFeatureVertices),
|
|
1379
1696
|
new Style({
|
|
1380
1697
|
stroke: new Stroke({
|
|
1381
1698
|
color: 'rgba(255, 255, 255, 0.7)',
|
|
@@ -1386,6 +1703,7 @@ function styleFunction(feature) {
|
|
|
1386
1703
|
}
|
|
1387
1704
|
else {
|
|
1388
1705
|
return [
|
|
1706
|
+
...(hovered ? haloStyle() : []),
|
|
1389
1707
|
new Style({
|
|
1390
1708
|
image: new Circle({
|
|
1391
1709
|
radius: 2,
|
|
@@ -1393,7 +1711,7 @@ function styleFunction(feature) {
|
|
|
1393
1711
|
color: '#000000'
|
|
1394
1712
|
})
|
|
1395
1713
|
}),
|
|
1396
|
-
geometry: (feature) =>
|
|
1714
|
+
geometry: (feature) => getFeatureVertices(feature)
|
|
1397
1715
|
}),
|
|
1398
1716
|
new Style({
|
|
1399
1717
|
stroke: new Stroke({
|
|
@@ -1408,91 +1726,178 @@ function styleFunction(feature) {
|
|
|
1408
1726
|
}
|
|
1409
1727
|
}
|
|
1410
1728
|
}
|
|
1411
|
-
|
|
1412
|
-
// Ol
|
|
1413
1729
|
/**
|
|
1414
|
-
*
|
|
1730
|
+
* Style used to highlight a feature after a query, drawn on a dedicated
|
|
1731
|
+
* highlight layer so the source layers keep their own rendering.
|
|
1415
1732
|
*
|
|
1416
1733
|
* @param feature
|
|
1417
1734
|
* @private
|
|
1418
1735
|
*/
|
|
1736
|
+
function queryStyleFunction(_feature) {
|
|
1737
|
+
return [
|
|
1738
|
+
new Style({
|
|
1739
|
+
image: new Circle({
|
|
1740
|
+
radius: 6,
|
|
1741
|
+
fill: new Fill({
|
|
1742
|
+
color: 'rgba(0, 170, 255, 0.4)'
|
|
1743
|
+
}),
|
|
1744
|
+
stroke: new Stroke({
|
|
1745
|
+
color: '#0066cc',
|
|
1746
|
+
width: 2
|
|
1747
|
+
})
|
|
1748
|
+
}),
|
|
1749
|
+
stroke: new Stroke({
|
|
1750
|
+
color: '#00aaff',
|
|
1751
|
+
width: 4
|
|
1752
|
+
}),
|
|
1753
|
+
fill: new Fill({
|
|
1754
|
+
color: 'rgba(0, 170, 255, 0.3)'
|
|
1755
|
+
})
|
|
1756
|
+
})
|
|
1757
|
+
];
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
/**
|
|
1761
|
+
* Shows a fields form in a modal window to allow changes in the properties of
|
|
1762
|
+
* one or several features. When editing multiple features, fields whose value
|
|
1763
|
+
* differs across the selection show the "multiple values" placeholder and only
|
|
1764
|
+
* the fields actually changed are applied to the whole selection.
|
|
1765
|
+
*
|
|
1766
|
+
* @param features
|
|
1767
|
+
* @private
|
|
1768
|
+
*/
|
|
1419
1769
|
class EditFieldsModal extends Observable {
|
|
1420
1770
|
constructor(options) {
|
|
1421
1771
|
super();
|
|
1422
1772
|
this._options = options;
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1773
|
+
this._modal = new Modal(Object.assign(Object.assign({}, this._options.modal), { header: true, headerClose: true, title: '', content: '<div></div>', footer: `
|
|
1774
|
+
<button
|
|
1775
|
+
type="button"
|
|
1776
|
+
class="btn btn-sm btn-third"
|
|
1777
|
+
data-action="delete"
|
|
1778
|
+
data-dismiss="modal"
|
|
1779
|
+
>
|
|
1780
|
+
${I18N.labels.delete}
|
|
1781
|
+
</button>
|
|
1782
|
+
<button
|
|
1783
|
+
type="button"
|
|
1784
|
+
class="btn btn-sm btn-secondary"
|
|
1785
|
+
data-dismiss="modal"
|
|
1786
|
+
>
|
|
1787
|
+
${I18N.labels.cancel}
|
|
1788
|
+
</button>
|
|
1789
|
+
<button
|
|
1790
|
+
type="button"
|
|
1791
|
+
class="btn btn-sm btn-primary"
|
|
1792
|
+
data-action="save"
|
|
1793
|
+
data-dismiss="modal"
|
|
1794
|
+
>
|
|
1795
|
+
${I18N.labels.save}
|
|
1796
|
+
</button>
|
|
1797
|
+
` }));
|
|
1435
1798
|
this._modal.on('dismiss', (modal, event) => {
|
|
1436
1799
|
// On saving changes
|
|
1437
1800
|
if (event.target.dataset.action === 'save') {
|
|
1438
|
-
const
|
|
1439
|
-
|
|
1801
|
+
const formElements = modal.el.querySelector('form')
|
|
1802
|
+
.elements;
|
|
1803
|
+
const multipleFields = new Set(Array.from(formElements)
|
|
1804
|
+
.filter((el) => el.dataset.multiple === 'true')
|
|
1805
|
+
.map((el) => el.name));
|
|
1806
|
+
Array.from(formElements).forEach((el) => {
|
|
1440
1807
|
const value = el.value;
|
|
1441
1808
|
const field = el.name;
|
|
1442
|
-
|
|
1809
|
+
// Do not overwrite untouched fields that had
|
|
1810
|
+
// multiple values in the selection
|
|
1811
|
+
if (multipleFields.has(field) && value === '') {
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1814
|
+
this._features.forEach((feature) => {
|
|
1815
|
+
if (feature.get(field) !== value) {
|
|
1816
|
+
feature.set(field, value, /* isSilent = */ true);
|
|
1817
|
+
}
|
|
1818
|
+
});
|
|
1443
1819
|
});
|
|
1444
|
-
this.
|
|
1445
|
-
|
|
1446
|
-
|
|
1820
|
+
this._features.forEach((feature) => {
|
|
1821
|
+
feature.changed();
|
|
1822
|
+
addFeatureToEditedList(feature);
|
|
1823
|
+
});
|
|
1824
|
+
this._dispatch('save', this._features);
|
|
1447
1825
|
}
|
|
1448
1826
|
else if (event.target.dataset.action === 'delete') {
|
|
1449
|
-
this.
|
|
1827
|
+
this._dispatch('delete', this._features);
|
|
1450
1828
|
}
|
|
1451
1829
|
});
|
|
1452
1830
|
}
|
|
1453
|
-
show(
|
|
1454
|
-
this.
|
|
1455
|
-
const
|
|
1456
|
-
const
|
|
1457
|
-
|
|
1831
|
+
show(features) {
|
|
1832
|
+
this._features = Array.isArray(features) ? features : [features];
|
|
1833
|
+
const multiple = this._features.length > 1;
|
|
1834
|
+
const modalTitle = multiple
|
|
1835
|
+
? I18N_('editElements', this._features.length)
|
|
1836
|
+
: `${I18N.labels.editElement} ${this._features[0].getId()} `;
|
|
1837
|
+
const layerName = this._features[0].get('_layerName_');
|
|
1458
1838
|
// Data schema from the geoserver
|
|
1459
1839
|
const layer = getStoredLayer(layerName);
|
|
1460
|
-
const
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1840
|
+
const describeFeatureType = layer.getDescribeFeatureType()._parsed;
|
|
1841
|
+
const fieldList = describeFeatureType.properties.filter((field) => field.name !== describeFeatureType.geomField);
|
|
1842
|
+
this._modal._html.body.innerHTML = '';
|
|
1843
|
+
this._modal._html.body.append(jsxs("div", { children: [multiple && (jsx("div", { className: "ol-wfst--edit-modal-notice", children: I18N_('multipleEditNotice', this._features.length) })), jsx("form", { autocomplete: "false", children: fieldList.flatMap((field) => {
|
|
1844
|
+
const key = field.name;
|
|
1845
|
+
const values = this._features.map((feature) => feature.get(key));
|
|
1846
|
+
const uniqueValues = Array.from(new Set(values.map((value) => String(value !== null && value !== void 0 ? value : ''))));
|
|
1847
|
+
const hasMultipleValues = multiple && uniqueValues.length > 1;
|
|
1848
|
+
const value = hasMultipleValues
|
|
1849
|
+
? null
|
|
1850
|
+
: uniqueValues[0] || null;
|
|
1851
|
+
const typeXsd = field.type;
|
|
1852
|
+
let type;
|
|
1853
|
+
switch (typeXsd) {
|
|
1854
|
+
case 'xsd:double':
|
|
1855
|
+
case 'xsd:number':
|
|
1856
|
+
case 'xsd:int':
|
|
1857
|
+
type = 'number';
|
|
1858
|
+
break;
|
|
1859
|
+
case 'xsd:date':
|
|
1860
|
+
type = 'date';
|
|
1861
|
+
break;
|
|
1862
|
+
case 'xsd:date-time':
|
|
1863
|
+
type = 'datetime';
|
|
1864
|
+
break;
|
|
1865
|
+
case 'xsd:string':
|
|
1866
|
+
default:
|
|
1867
|
+
type = 'text';
|
|
1868
|
+
}
|
|
1869
|
+
let input = (jsx("input", { placeholder: hasMultipleValues
|
|
1870
|
+
? I18N.labels.multipleValues
|
|
1871
|
+
: 'NULL', className: 'ol-wfst--input-field-input' +
|
|
1872
|
+
(hasMultipleValues
|
|
1873
|
+
? ' ol-wfst--input-multiple'
|
|
1874
|
+
: ''), type: type, name: key, value: value, "data-multiple": hasMultipleValues ? 'true' : null }));
|
|
1875
|
+
if (layer.beforeShowFieldsModal) {
|
|
1876
|
+
const hookInput = layer.beforeShowFieldsModal(field, value, input);
|
|
1877
|
+
if (!hookInput) {
|
|
1878
|
+
return [];
|
|
1879
|
+
}
|
|
1880
|
+
if (typeof hookInput === 'string') {
|
|
1881
|
+
input = new DOMParser().parseFromString(hookInput, 'text/html').body.childNodes[0];
|
|
1882
|
+
}
|
|
1883
|
+
else {
|
|
1884
|
+
input = hookInput;
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
if (hasMultipleValues) {
|
|
1888
|
+
input.dataset.multiple = 'true';
|
|
1889
|
+
input.classList.add('ol-wfst--input-multiple');
|
|
1890
|
+
}
|
|
1891
|
+
return (jsxs("div", { className: "ol-wfst--input-field-container", children: [jsx("label", { className: "ol-wfst--input-field-label", htmlFor: key, children: key }), input] }));
|
|
1892
|
+
}) })] }));
|
|
1893
|
+
this._modal._html.header.innerHTML = modalTitle;
|
|
1494
1894
|
this._modal.show();
|
|
1495
1895
|
}
|
|
1896
|
+
_dispatch(type, features) {
|
|
1897
|
+
const evt = new VectorSourceEvent(type, features[0]);
|
|
1898
|
+
evt.features = features;
|
|
1899
|
+
this.dispatchEvent(evt);
|
|
1900
|
+
}
|
|
1496
1901
|
}
|
|
1497
1902
|
|
|
1498
1903
|
// Ol
|
|
@@ -1518,8 +1923,8 @@ class Geoserver extends BaseObject {
|
|
|
1518
1923
|
wfsTransactionVersion: '1.1.0',
|
|
1519
1924
|
projection: DEFAULT_GEOSERVER_SRS,
|
|
1520
1925
|
lockFeatureParams: {
|
|
1521
|
-
expiry: 5,
|
|
1522
|
-
lockId: '
|
|
1926
|
+
expiry: 5, // minutes
|
|
1927
|
+
lockId: 'WFST-editor',
|
|
1523
1928
|
releaseAction: 'SOME'
|
|
1524
1929
|
}
|
|
1525
1930
|
},
|
|
@@ -1537,6 +1942,7 @@ class Geoserver extends BaseObject {
|
|
|
1537
1942
|
this._insertFeatures = [];
|
|
1538
1943
|
this._updateFeatures = [];
|
|
1539
1944
|
this._deleteFeatures = [];
|
|
1945
|
+
this._lockedId = this._options.advanced.lockFeatureParams.lockId;
|
|
1540
1946
|
// Formats
|
|
1541
1947
|
this._formatWFS = new WFS();
|
|
1542
1948
|
this._formatGeoJSON = new GeoJSON();
|
|
@@ -1875,7 +2281,7 @@ class Geoserver extends BaseObject {
|
|
|
1875
2281
|
if (this.hasLockFeature &&
|
|
1876
2282
|
this.getUseLockFeature() &&
|
|
1877
2283
|
transactionType !== TransactionType.Insert) {
|
|
1878
|
-
payload = payload.replace(`</Transaction>`, `<LockId>${this.
|
|
2284
|
+
payload = payload.replace(`</Transaction>`, `<LockId>${this._lockedId}</LockId></Transaction>`);
|
|
1879
2285
|
}
|
|
1880
2286
|
const headers = Object.assign({ 'Content-Type': 'text/xml' }, this.getHeaders());
|
|
1881
2287
|
const response = await fetch(this.getUrl(), {
|
|
@@ -1903,6 +2309,9 @@ class Geoserver extends BaseObject {
|
|
|
1903
2309
|
if (transactionType !== TransactionType.Delete) {
|
|
1904
2310
|
this._removeFeatures(features);
|
|
1905
2311
|
}
|
|
2312
|
+
features.forEach((feature) => {
|
|
2313
|
+
removeFeatureFromEditList(feature);
|
|
2314
|
+
});
|
|
1906
2315
|
wlayer.refresh();
|
|
1907
2316
|
showLoading(false);
|
|
1908
2317
|
this._insertFeatures = [];
|
|
@@ -1962,30 +2371,34 @@ class Geoserver extends BaseObject {
|
|
|
1962
2371
|
const featureProperties = feature.getProperties();
|
|
1963
2372
|
delete featureProperties.boundedBy;
|
|
1964
2373
|
delete featureProperties._layerName_;
|
|
2374
|
+
delete featureProperties._editOverlayCoord_;
|
|
1965
2375
|
const clone = new Feature(featureProperties);
|
|
1966
2376
|
clone.setId(feature.getId());
|
|
1967
2377
|
return clone;
|
|
1968
2378
|
}
|
|
1969
2379
|
/**
|
|
1970
|
-
* Lock
|
|
1971
|
-
* to avoid changes from multiples
|
|
2380
|
+
* Lock one or several features in the geoserver. Useful before editing,
|
|
2381
|
+
* to avoid changes from multiples users. Locking several features at once
|
|
2382
|
+
* with a single request keeps the lock covered by one LockId
|
|
1972
2383
|
*
|
|
1973
|
-
* @param
|
|
2384
|
+
* @param featureIds
|
|
1974
2385
|
* @param layerName
|
|
1975
2386
|
* @param retry
|
|
1976
2387
|
* @public
|
|
1977
2388
|
*/
|
|
1978
|
-
async lockFeature(
|
|
2389
|
+
async lockFeature(featureIds, layerName, retry = 0) {
|
|
1979
2390
|
const params = new URLSearchParams({
|
|
1980
2391
|
service: 'wfs',
|
|
1981
2392
|
version: this.getAdvanced().lockFeatureVersion,
|
|
1982
2393
|
request: 'LockFeature',
|
|
1983
2394
|
typeName: layerName,
|
|
1984
2395
|
expiry: String(this._options.advanced.lockFeatureParams.expiry),
|
|
1985
|
-
|
|
2396
|
+
handle: this._options.advanced.lockFeatureParams.lockId,
|
|
1986
2397
|
releaseAction: this._options.advanced.lockFeatureParams.releaseAction,
|
|
1987
2398
|
exceptions: 'application/json',
|
|
1988
|
-
featureid:
|
|
2399
|
+
featureid: Array.isArray(featureIds)
|
|
2400
|
+
? featureIds.join(',')
|
|
2401
|
+
: String(featureIds)
|
|
1989
2402
|
});
|
|
1990
2403
|
const url_fetch = this.getUrl() + '?' + params.toString();
|
|
1991
2404
|
try {
|
|
@@ -2006,7 +2419,7 @@ class Geoserver extends BaseObject {
|
|
|
2006
2419
|
if (exceptions[0].code === 'CannotLockAllFeatures') {
|
|
2007
2420
|
// Maybe the Feature is already blocked, ant thats trigger error, so, we try one locking more time again
|
|
2008
2421
|
if (!retry) {
|
|
2009
|
-
this.lockFeature(
|
|
2422
|
+
this.lockFeature(featureIds, layerName, 1);
|
|
2010
2423
|
}
|
|
2011
2424
|
else {
|
|
2012
2425
|
throw error;
|
|
@@ -2018,21 +2431,20 @@ class Geoserver extends BaseObject {
|
|
|
2018
2431
|
}
|
|
2019
2432
|
}
|
|
2020
2433
|
catch (err) {
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2434
|
+
// XML response (not JSON): parse the real lock id returned by
|
|
2435
|
+
// the server so the transaction references the lock that was
|
|
2436
|
+
// actually created instead of the configured one
|
|
2437
|
+
try {
|
|
2438
|
+
const dataDoc = new window.DOMParser().parseFromString(data, 'text/xml');
|
|
2439
|
+
const lockIdNode = dataDoc.getElementsByTagName('wfs:LockId')[0] ||
|
|
2440
|
+
dataDoc.getElementsByTagNameNS('http://www.opengis.net/wfs', 'LockId')[0];
|
|
2441
|
+
if (lockIdNode && lockIdNode.textContent) {
|
|
2442
|
+
this._lockedId = lockIdNode.textContent;
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
catch (parseErr) {
|
|
2446
|
+
// Keep the configured lockId
|
|
2033
2447
|
}
|
|
2034
|
-
|
|
2035
|
-
*/
|
|
2036
2448
|
}
|
|
2037
2449
|
return data;
|
|
2038
2450
|
}
|
|
@@ -2056,34 +2468,107 @@ var GeoserverProperty;
|
|
|
2056
2468
|
GeoserverProperty["ISLOADED"] = "isLoaded";
|
|
2057
2469
|
})(GeoserverProperty || (GeoserverProperty = {}));
|
|
2058
2470
|
|
|
2059
|
-
|
|
2471
|
+
function editFieldsSvg() {
|
|
2472
|
+
return (new DOMParser().parseFromString("<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" width=\"448\" height=\"448\" viewBox=\"0 0 448 448\">\r\n <path d=\"M222 296l29-29-38-38-29 29v14h24v24h14zM332 116c-2.25-2.25-6-2-8.25 0.25l-87.5 87.5c-2.25 2.25-2.5 6-0.25 8.25s6 2 8.25-0.25l87.5-87.5c2.25-2.25 2.5-6 0.25-8.25zM352 264.5v47.5c0 39.75-32.25 72-72 72h-208c-39.75 0-72-32.25-72-72v-208c0-39.75 32.25-72 72-72h208c10 0 20 2 29.25 6.25 2.25 1 4 3.25 4.5 5.75 0.5 2.75-0.25 5.25-2.25 7.25l-12.25 12.25c-2.25 2.25-5.25 3-8 2-3.75-1-7.5-1.5-11.25-1.5h-208c-22 0-40 18-40 40v208c0 22 18 40 40 40h208c22 0 40-18 40-40v-31.5c0-2 0.75-4 2.25-5.5l16-16c2.5-2.5 5.75-3 8.75-1.75s5 4 5 7.25zM328 80l72 72-168 168h-72v-72zM439 113l-23 23-72-72 23-23c9.25-9.25 24.75-9.25 34 0l38 38c9.25 9.25 9.25 24.75 0 34z\"></path>\r\n</svg>", 'image/svg+xml')).firstChild;
|
|
2473
|
+
}
|
|
2060
2474
|
|
|
2061
|
-
|
|
2475
|
+
function editGeomSvg() {
|
|
2476
|
+
return (new DOMParser().parseFromString(" <svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" width=\"541\" height=\"512\" viewBox=\"0 0 541 512\">\r\n <path fill=\"#000\" d=\"M103.306 228.483l129.493-125.249c-17.662-4.272-31.226-18.148-34.98-35.663l-0.055-0.307-129.852 125.248c17.812 4.15 31.53 18.061 35.339 35.662l0.056 0.308z\"></path>\r\n <path fill=\"#000\" d=\"M459.052 393.010c-13.486-8.329-22.346-23.018-22.373-39.779v-0.004c-0.053-0.817-0.082-1.772-0.082-2.733s0.030-1.916 0.089-2.863l-0.007 0.13-149.852 71.94c9.598 8.565 15.611 20.969 15.611 34.779 0 0.014 0 0.029 0 0.043v-0.002c-0.048 5.164-0.94 10.104-2.544 14.711l0.098-0.322z\"></path>\r\n <path fill=\"#000\" d=\"M290.207 57.553c-0.009 15.55-7.606 29.324-19.289 37.819l-0.135 0.093 118.054 46.69c-0.216-1.608-0.346-3.48-0.36-5.379v-0.017c0.033-16.948 9.077-31.778 22.596-39.953l0.209-0.118-122.298-48.056c0.659 2.633 1.098 5.693 1.221 8.834l0.002 0.087z\"></path>\r\n <path fill=\"#000\" d=\"M241.36 410.132l-138.629-160.067c-4.734 17.421-18.861 30.61-36.472 33.911l-0.29 0.045 143.881 166.255c1.668-18.735 14.197-34.162 31.183-40.044l0.327-0.099z\"></path>\r\n <path fill=\"#000\" d=\"M243.446 115.105c-31.785 0-57.553-25.767-57.553-57.553s25.767-57.553 57.553-57.553c31.785 0 57.552 25.767 57.552 57.553v0c0 31.786-25.767 57.553-57.553 57.553v0zM243.446 21.582c-19.866 0-35.97 16.105-35.97 35.97s16.105 35.97 35.97 35.97c19.866 0 35.97-16.105 35.97-35.97v0c0-19.866-16.104-35.97-35.97-35.97v0z\"></path>\r\n <path fill=\"#000\" d=\"M483.224 410.78c-31.786 0-57.553-25.767-57.553-57.553s25.767-57.553 57.553-57.553c31.786 0 57.552 25.767 57.552 57.553v0c0 31.786-25.767 57.553-57.553 57.553v0zM483.224 317.257c-19.866 0-35.97 16.104-35.97 35.97s16.105 35.97 35.97 35.97c19.866 0 35.97-16.105 35.97-35.97v0c0-19.866-16.105-35.97-35.97-35.97v0z\"></path>\r\n <path fill=\"#000\" d=\"M57.553 295.531c-31.785 0-57.553-25.767-57.553-57.553s25.767-57.553 57.553-57.553c31.785 0 57.553 25.767 57.553 57.553v0c0 31.786-25.767 57.553-57.553 57.553v0zM57.553 202.008c-19.866 0-35.97 16.105-35.97 35.97s16.105 35.97 35.97 35.97c19.866 0 35.97-16.105 35.97-35.97v0c-0.041-19.835-16.13-35.898-35.97-35.898 0 0 0 0 0 0v0z\"></path>\r\n <path fill=\"#000\" d=\"M256.036 512.072c-31.786 0-57.553-25.767-57.553-57.553s25.767-57.553 57.553-57.553c31.786 0 57.553 25.767 57.553 57.553v0c0 31.786-25.767 57.553-57.553 57.553v0zM256.036 418.55c-19.866 0-35.97 16.104-35.97 35.97s16.105 35.97 35.97 35.97c19.866 0 35.97-16.105 35.97-35.97v0c0-19.866-16.105-35.97-35.97-35.97v0z\"></path>\r\n <path fill=\"#000\" d=\"M435.24 194.239c-31.786 0-57.553-25.767-57.553-57.553s25.767-57.553 57.553-57.553c31.786 0 57.553 25.767 57.553 57.553v0c0 31.785-25.767 57.553-57.553 57.553v0zM435.24 100.716c-19.866 0-35.97 16.105-35.97 35.97s16.105 35.97 35.97 35.97c19.866 0 35.97-16.105 35.97-35.97v0c0-19.866-16.105-35.97-35.97-35.97v0z\"></path>\r\n</svg>", 'image/svg+xml')).firstChild;
|
|
2477
|
+
}
|
|
2062
2478
|
|
|
2063
2479
|
class EditOverlay extends Overlay {
|
|
2064
|
-
constructor(feature, coordinate = null) {
|
|
2480
|
+
constructor(feature = null, coordinate = null, id) {
|
|
2065
2481
|
super({
|
|
2066
|
-
id: feature.getId(),
|
|
2067
|
-
position: coordinate ||
|
|
2482
|
+
id: id !== null && id !== void 0 ? id : (feature ? feature.getId() : undefined),
|
|
2483
|
+
position: coordinate ||
|
|
2484
|
+
(feature
|
|
2485
|
+
? getCenter(feature.getGeometry().getExtent())
|
|
2486
|
+
: undefined),
|
|
2068
2487
|
positioning: 'center-center',
|
|
2069
2488
|
offset: [0, -40],
|
|
2070
2489
|
stopEvent: true,
|
|
2071
|
-
element: (
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2490
|
+
element: (jsxs("div", { children: [jsx("div", { className: "ol-wfst--edit-button-cnt", onClick: () => {
|
|
2491
|
+
this.dispatchEvent('editFields');
|
|
2492
|
+
}, children: jsx("button", { className: "ol-wfst--edit-button", type: "button", title: I18N.labels.editFields, children: editFieldsSvg() }) }), jsx("div", { className: "ol-wfst--edit-button-cnt", onClick: () => {
|
|
2493
|
+
this.dispatchEvent('editGeom');
|
|
2494
|
+
}, children: jsx("button", { class: "ol-wfst--edit-button", type: "button", title: I18N.labels.editGeom, children: editGeomSvg() }) })] }))
|
|
2495
|
+
});
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
function addLineSvg() {
|
|
2500
|
+
return (new DOMParser().parseFromString("<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\">\r\n <path d=\"M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z\"></path>\r\n</svg>", 'image/svg+xml')).firstChild;
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
class ExtendLineOverlay extends Overlay {
|
|
2504
|
+
constructor(feature, coordinate, side, component = 0, onClick) {
|
|
2505
|
+
super({
|
|
2506
|
+
position: coordinate,
|
|
2507
|
+
positioning: 'center-center',
|
|
2508
|
+
stopEvent: true,
|
|
2509
|
+
element: (jsx("div", { className: "ol-wfst--extend-button-cnt", children: jsx("button", { className: "ol-wfst--extend-button", type: "button", title: I18N.labels.continueLine, onClick: () => {
|
|
2510
|
+
onClick(side);
|
|
2511
|
+
}, children: addLineSvg() }) }))
|
|
2512
|
+
});
|
|
2513
|
+
this.feature = feature;
|
|
2514
|
+
this.side = side;
|
|
2515
|
+
this.component = component;
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
const QUERY_OVERLAY_ID = 'ol-wfst--query-overlay';
|
|
2520
|
+
/**
|
|
2521
|
+
* Popup overlay that shows the attributes of a queried feature and its
|
|
2522
|
+
* layer label. The geometry is highlighted separately on a dedicated layer.
|
|
2523
|
+
*
|
|
2524
|
+
* @extends {ol/Overlay~Overlay}
|
|
2525
|
+
* @param feature
|
|
2526
|
+
* @param coordinate
|
|
2527
|
+
* @param layer
|
|
2528
|
+
* @private
|
|
2529
|
+
*/
|
|
2530
|
+
class QueryOverlay extends Overlay {
|
|
2531
|
+
constructor(feature, coordinate, layer) {
|
|
2532
|
+
const describeFeatureType = layer.getDescribeFeatureType();
|
|
2533
|
+
const parsed = describeFeatureType === null || describeFeatureType === void 0 ? void 0 : describeFeatureType._parsed;
|
|
2534
|
+
const fields = (parsed === null || parsed === void 0 ? void 0 : parsed.properties) || [];
|
|
2535
|
+
const geomField = parsed === null || parsed === void 0 ? void 0 : parsed.geomField;
|
|
2536
|
+
const rows = fields
|
|
2537
|
+
.filter((field) => field.name !== geomField)
|
|
2538
|
+
.map((field) => {
|
|
2539
|
+
let value = feature.get(field.name);
|
|
2540
|
+
if (value === undefined || value === null) {
|
|
2541
|
+
value = '';
|
|
2542
|
+
}
|
|
2543
|
+
else if (typeof value === 'object') {
|
|
2544
|
+
value = JSON.stringify(value);
|
|
2545
|
+
}
|
|
2546
|
+
return (jsxs("div", { className: "ol-wfst--query-popup-row", children: [jsx("span", { className: "ol-wfst--query-popup-key", children: field.name }), jsx("span", { className: "ol-wfst--query-popup-value", children: String(value) })] }));
|
|
2547
|
+
});
|
|
2548
|
+
super({
|
|
2549
|
+
id: QUERY_OVERLAY_ID,
|
|
2550
|
+
position: coordinate || getCenter(feature.getGeometry().getExtent()),
|
|
2551
|
+
positioning: 'bottom-center',
|
|
2552
|
+
offset: [0, -14],
|
|
2553
|
+
stopEvent: true,
|
|
2554
|
+
autoPan: true,
|
|
2555
|
+
element: (jsxs("div", { className: "ol-wfst--query-popup", children: [jsxs("div", { className: "ol-wfst--query-popup-head", children: [jsx("div", { className: "ol-wfst--query-popup-title", children: I18N.labels.featureInfo }), jsx("button", { className: "ol-wfst--query-popup-close", type: "button", title: I18N.labels.close, onClick: () => {
|
|
2556
|
+
this.dispatchEvent('close');
|
|
2557
|
+
}, children: "\u00D7" })] }), jsxs("div", { className: "ol-wfst--query-popup-subtitle", children: [jsx("b", { children: layer.get(BaseLayerProperty.LABEL) }), jsx("i", { children: String(feature.getId()) })] }), jsx("div", { className: "ol-wfst--query-popup-body", children: rows })] }))
|
|
2082
2558
|
});
|
|
2083
2559
|
}
|
|
2084
2560
|
}
|
|
2085
2561
|
|
|
2562
|
+
function fullscreenSvg() {
|
|
2563
|
+
return (new DOMParser().parseFromString("<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\">\r\n <path d=\"M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z\"></path>\r\n</svg>", 'image/svg+xml')).firstChild;
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
function fullscreenExitSvg() {
|
|
2567
|
+
return (new DOMParser().parseFromString("<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\">\r\n <path d=\"M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z\"></path>\r\n</svg>", 'image/svg+xml')).firstChild;
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2086
2570
|
const controlElement = document.createElement('div');
|
|
2571
|
+
const INIT_LOADING_TIMEOUT = 20000;
|
|
2087
2572
|
/**
|
|
2088
2573
|
* Tiny WFS-T client to insert (drawing/uploading), modify and delete
|
|
2089
2574
|
* features on GeoServers using OpenLayers. Layers with these types
|
|
@@ -2112,6 +2597,14 @@ class Wfst extends Control {
|
|
|
2112
2597
|
}
|
|
2113
2598
|
});
|
|
2114
2599
|
this._initialized = false;
|
|
2600
|
+
this._queryOverlayId = 'ol-wfst--query-overlay';
|
|
2601
|
+
this._shiftPressed = false;
|
|
2602
|
+
this._hoveredFeature = null;
|
|
2603
|
+
this._keySelectBound = false;
|
|
2604
|
+
// Editing
|
|
2605
|
+
this._editFeaturesOriginal = {};
|
|
2606
|
+
this._multiOverlayId = 'ol-wfst--multi-edit-overlay';
|
|
2607
|
+
this._extendOverlays = [];
|
|
2115
2608
|
setLang(options.language, options.i18n);
|
|
2116
2609
|
const defaultOptions = getDefaultOptions();
|
|
2117
2610
|
this._options = deepObjectAssign(defaultOptions, options);
|
|
@@ -2120,7 +2613,7 @@ class Wfst extends Control {
|
|
|
2120
2613
|
this._controlWidgetToolsDiv = controlElement;
|
|
2121
2614
|
this._controlWidgetToolsDiv.className = 'ol-wfst--tools-control';
|
|
2122
2615
|
this._uploads = new Uploads(this._options);
|
|
2123
|
-
this._editFields = new EditFieldsModal(this._options
|
|
2616
|
+
this._editFields = new EditFieldsModal(this._options);
|
|
2124
2617
|
}
|
|
2125
2618
|
/**
|
|
2126
2619
|
* Get all the layers in the ol-wfst instance
|
|
@@ -2152,8 +2645,10 @@ class Wfst extends Control {
|
|
|
2152
2645
|
if (layers.length) {
|
|
2153
2646
|
let layerRendered = 0;
|
|
2154
2647
|
let layersNumber = 0; // Only count visibles
|
|
2648
|
+
showLoading();
|
|
2649
|
+
const layerInitPromises = [];
|
|
2155
2650
|
layers.forEach((layer) => {
|
|
2156
|
-
if (layer.
|
|
2651
|
+
if (layer.isVisible(this._view))
|
|
2157
2652
|
layersNumber++;
|
|
2158
2653
|
layer.on('layerRendered', () => {
|
|
2159
2654
|
layerRendered++;
|
|
@@ -2167,15 +2662,17 @@ class Wfst extends Control {
|
|
|
2167
2662
|
}
|
|
2168
2663
|
});
|
|
2169
2664
|
layer.on('change:describeFeatureType', () => {
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2665
|
+
if (this._options.showControl && layers.length > 1) {
|
|
2666
|
+
const domEl = this._layersControl.addLayerEl(layer);
|
|
2667
|
+
layer.on('change:isVisible', () => {
|
|
2668
|
+
const layerNotVisible = 'ol-wfst--layer-not-visible';
|
|
2669
|
+
const visible = layer.isVisibleByZoom();
|
|
2670
|
+
if (visible)
|
|
2671
|
+
domEl.classList.remove(layerNotVisible);
|
|
2672
|
+
else
|
|
2673
|
+
domEl.classList.add(layerNotVisible);
|
|
2674
|
+
});
|
|
2675
|
+
}
|
|
2179
2676
|
layer.set(BaseLayerProperty.ISVISIBLE, this._currentZoom > layer.getMinZoom());
|
|
2180
2677
|
this.dispatchEvent(new WfstEvent({
|
|
2181
2678
|
type: 'describeFeatureType',
|
|
@@ -2183,13 +2680,21 @@ class Wfst extends Control {
|
|
|
2183
2680
|
data: layer.getDescribeFeatureType()
|
|
2184
2681
|
}));
|
|
2185
2682
|
});
|
|
2186
|
-
layer._init();
|
|
2683
|
+
layerInitPromises.push(layer._init());
|
|
2187
2684
|
this._map.addLayer(layer);
|
|
2188
2685
|
setMapLayers({
|
|
2189
2686
|
[layer.get(BaseLayerProperty.NAME)]: layer
|
|
2190
2687
|
});
|
|
2191
2688
|
});
|
|
2192
2689
|
this._createMapElements(this._options.showControl, this._options.active);
|
|
2690
|
+
// Show the loading bar until every layer has resolved its
|
|
2691
|
+
// DescribeFeatureType (success or error), with a fail-safe
|
|
2692
|
+
// timeout so it never stays visible on a dead connection.
|
|
2693
|
+
await Promise.race([
|
|
2694
|
+
Promise.all(layerInitPromises),
|
|
2695
|
+
new Promise((resolve) => setTimeout(resolve, INIT_LOADING_TIMEOUT))
|
|
2696
|
+
]);
|
|
2697
|
+
showLoading(false);
|
|
2193
2698
|
}
|
|
2194
2699
|
}
|
|
2195
2700
|
catch (err) {
|
|
@@ -2222,17 +2727,26 @@ class Wfst extends Control {
|
|
|
2222
2727
|
});
|
|
2223
2728
|
});
|
|
2224
2729
|
// @ts-expect-error
|
|
2225
|
-
this._editFields.on('save', ({ feature }) => {
|
|
2226
|
-
|
|
2227
|
-
this.
|
|
2730
|
+
this._editFields.on('save', async ({ feature, features }) => {
|
|
2731
|
+
const list = features || [feature];
|
|
2732
|
+
const ok = await this._transactEditList(list);
|
|
2733
|
+
if (ok) {
|
|
2734
|
+
Array.from(list).forEach((f) => this._collectionModify.remove(f));
|
|
2735
|
+
}
|
|
2228
2736
|
});
|
|
2229
2737
|
// @ts-expect-error
|
|
2230
|
-
this._editFields.on('delete', ({
|
|
2231
|
-
this._deleteFeature(
|
|
2738
|
+
this._editFields.on('delete', ({ features }) => {
|
|
2739
|
+
this._deleteFeature(features, true);
|
|
2232
2740
|
});
|
|
2233
2741
|
this._addMapEvents();
|
|
2742
|
+
this._addMiddleButtonPan();
|
|
2234
2743
|
initModal(this._options['modal']);
|
|
2235
|
-
this.
|
|
2744
|
+
this._layersWidgetDiv = document.createElement('div');
|
|
2745
|
+
this._layersWidgetDiv.className = 'ol-wfst--layers-control';
|
|
2746
|
+
this._layersWidgetDiv.append(initLoading());
|
|
2747
|
+
this._map.addControl(new Control({
|
|
2748
|
+
element: this._layersWidgetDiv
|
|
2749
|
+
}));
|
|
2236
2750
|
this._initMapAndLayers();
|
|
2237
2751
|
}
|
|
2238
2752
|
/**
|
|
@@ -2248,8 +2762,18 @@ class Wfst extends Control {
|
|
|
2248
2762
|
this._prepareEditLayer();
|
|
2249
2763
|
this._addInteractions();
|
|
2250
2764
|
this._addInteractionHandlers();
|
|
2765
|
+
this._prepareHighlightLayer();
|
|
2766
|
+
this._addQueryInteraction();
|
|
2251
2767
|
if (showControl) {
|
|
2252
2768
|
this._addMapControl();
|
|
2769
|
+
if (this._options.fullscreen) {
|
|
2770
|
+
this._map.addControl(new FullScreen({
|
|
2771
|
+
className: 'ol-wfst--fullscreen',
|
|
2772
|
+
tipLabel: I18N.labels.fullscreen,
|
|
2773
|
+
label: fullscreenSvg(),
|
|
2774
|
+
labelActive: fullscreenExitSvg()
|
|
2775
|
+
}));
|
|
2776
|
+
}
|
|
2253
2777
|
}
|
|
2254
2778
|
// By default, init in edit mode
|
|
2255
2779
|
this.activateEditMode(active);
|
|
@@ -2268,9 +2792,10 @@ class Wfst extends Control {
|
|
|
2268
2792
|
this._interactionWfsSelect = new Select({
|
|
2269
2793
|
hitTolerance: 10,
|
|
2270
2794
|
style: (feature) => styleFunction(feature),
|
|
2271
|
-
toggleCondition:
|
|
2795
|
+
toggleCondition: shiftKeyOnly, // Allow adding features to the selection with shift
|
|
2272
2796
|
filter: (feature, layer) => {
|
|
2273
2797
|
return (getMode() !== Modes.Edit &&
|
|
2798
|
+
getMode() !== Modes.Query &&
|
|
2274
2799
|
layer &&
|
|
2275
2800
|
layer instanceof WfsLayer &&
|
|
2276
2801
|
layer === getActiveLayerToInsertEls());
|
|
@@ -2279,7 +2804,12 @@ class Wfst extends Control {
|
|
|
2279
2804
|
this._map.addInteraction(this._interactionWfsSelect);
|
|
2280
2805
|
this._interactionWfsSelect.on('select', ({ selected, deselected, mapBrowserEvent }) => {
|
|
2281
2806
|
const coordinate = mapBrowserEvent.coordinate;
|
|
2282
|
-
|
|
2807
|
+
// Clear hover to avoid keeping the style on the selected feature
|
|
2808
|
+
this._clearHoverState();
|
|
2809
|
+
if (getMode() === Modes.Query) {
|
|
2810
|
+
return;
|
|
2811
|
+
}
|
|
2812
|
+
if (selected.length) {
|
|
2283
2813
|
selected.forEach((feature) => {
|
|
2284
2814
|
if (!isFeatureEdited(feature)) {
|
|
2285
2815
|
// Remove the feature from the original layer
|
|
@@ -2288,6 +2818,7 @@ class Wfst extends Control {
|
|
|
2288
2818
|
this._addFeatureToEditMode(feature, coordinate, layer.get(BaseLayerProperty.NAME));
|
|
2289
2819
|
}
|
|
2290
2820
|
});
|
|
2821
|
+
this._lockSelectedFeatures();
|
|
2291
2822
|
}
|
|
2292
2823
|
if (deselected.length) {
|
|
2293
2824
|
if (getMode() !== Modes.Edit) {
|
|
@@ -2309,18 +2840,33 @@ class Wfst extends Control {
|
|
|
2309
2840
|
this._interactionSelectModify = new Select({
|
|
2310
2841
|
style: (feature) => styleFunction(feature),
|
|
2311
2842
|
layers: [getEditLayer()],
|
|
2312
|
-
toggleCondition:
|
|
2843
|
+
toggleCondition: shiftKeyOnly, // Allow adding features with shift
|
|
2313
2844
|
removeCondition: () => (getMode() === Modes.Edit ? true : false) // Prevent deselect on clicking outside the feature
|
|
2314
2845
|
});
|
|
2315
2846
|
this._map.addInteraction(this._interactionSelectModify);
|
|
2847
|
+
// When clicking without shift, replace the current selection: remove
|
|
2848
|
+
// any previously selected and not re-selected feature from the edit list
|
|
2849
|
+
this._interactionSelectModify.on('select', ({ selected, mapBrowserEvent }) => {
|
|
2850
|
+
if (!selected.length ||
|
|
2851
|
+
!mapBrowserEvent ||
|
|
2852
|
+
shiftKeyOnly(mapBrowserEvent)) {
|
|
2853
|
+
return;
|
|
2854
|
+
}
|
|
2855
|
+
const selectedSet = new Set(selected);
|
|
2856
|
+
Array.from(this._collectionModify.getArray()).forEach((feature) => {
|
|
2857
|
+
if (!selectedSet.has(feature)) {
|
|
2858
|
+
this._collectionModify.remove(feature);
|
|
2859
|
+
}
|
|
2860
|
+
});
|
|
2861
|
+
});
|
|
2316
2862
|
this._collectionModify =
|
|
2317
2863
|
this._interactionSelectModify.getFeatures();
|
|
2318
|
-
this._keyClickWms = this._map.on(this._options.evtType, async (evt) => {
|
|
2864
|
+
this._keyClickWms = this._map.on(this._options.evtType || 'singleclick', async (evt) => {
|
|
2319
2865
|
if (this._map.hasFeatureAtPixel(evt.pixel)) {
|
|
2320
2866
|
return;
|
|
2321
2867
|
}
|
|
2322
2868
|
// Only get other features if editmode is disabled
|
|
2323
|
-
if (getMode() !== Modes.Edit) {
|
|
2869
|
+
if (getMode() !== Modes.Edit && getMode() !== Modes.Query) {
|
|
2324
2870
|
const layer = getActiveLayerToInsertEls();
|
|
2325
2871
|
// If layer is hidden or is a wfs, skip
|
|
2326
2872
|
if (!layer.getVisible() ||
|
|
@@ -2332,8 +2878,14 @@ class Wfst extends Control {
|
|
|
2332
2878
|
if (!(features === null || features === void 0 ? void 0 : features.length)) {
|
|
2333
2879
|
return;
|
|
2334
2880
|
}
|
|
2335
|
-
//
|
|
2881
|
+
// Without shift, replace the current selection
|
|
2882
|
+
if (!shiftKeyOnly(evt)) {
|
|
2883
|
+
Array.from(this._collectionModify.getArray()).forEach((feature) => {
|
|
2884
|
+
this._collectionModify.remove(feature);
|
|
2885
|
+
});
|
|
2886
|
+
}
|
|
2336
2887
|
this._addFeatureToEditMode(features[0], evt.coordinate, layer.get(BaseLayerProperty.NAME));
|
|
2888
|
+
this._lockSelectedFeatures();
|
|
2337
2889
|
}
|
|
2338
2890
|
});
|
|
2339
2891
|
};
|
|
@@ -2344,20 +2896,9 @@ class Wfst extends Control {
|
|
|
2344
2896
|
prepareWmsInteraction();
|
|
2345
2897
|
}
|
|
2346
2898
|
this._interactionModify = new Modify({
|
|
2347
|
-
style: () => {
|
|
2899
|
+
style: (feature) => {
|
|
2348
2900
|
if (getMode() === Modes.Edit) {
|
|
2349
|
-
return
|
|
2350
|
-
image: new CircleStyle({
|
|
2351
|
-
radius: 6,
|
|
2352
|
-
fill: new Fill$1({
|
|
2353
|
-
color: '#ff0000'
|
|
2354
|
-
}),
|
|
2355
|
-
stroke: new Stroke$1({
|
|
2356
|
-
width: 2,
|
|
2357
|
-
color: 'rgba(5, 5, 5, 0.9)'
|
|
2358
|
-
})
|
|
2359
|
-
})
|
|
2360
|
-
});
|
|
2901
|
+
return editVertexStyles(feature.get('existing') === true);
|
|
2361
2902
|
}
|
|
2362
2903
|
else {
|
|
2363
2904
|
return;
|
|
@@ -2373,6 +2914,175 @@ class Wfst extends Control {
|
|
|
2373
2914
|
source: getEditLayer().getSource()
|
|
2374
2915
|
});
|
|
2375
2916
|
this._map.addInteraction(this._interactionSnap);
|
|
2917
|
+
this._addSelectionInteractions();
|
|
2918
|
+
}
|
|
2919
|
+
/**
|
|
2920
|
+
* Interactions to select features by drawing a box (DragBox) or a freehand
|
|
2921
|
+
* polygon (freehand Draw lasso). The selected features are added to the
|
|
2922
|
+
* edit list, keeping the same behaviour as the single click selection.
|
|
2923
|
+
*
|
|
2924
|
+
* @private
|
|
2925
|
+
*/
|
|
2926
|
+
_addSelectionInteractions() {
|
|
2927
|
+
const selectionStyle = new Style$1({
|
|
2928
|
+
fill: new Fill$1({
|
|
2929
|
+
color: 'rgba(255, 200, 0, 0.1)'
|
|
2930
|
+
}),
|
|
2931
|
+
stroke: new Stroke$1({
|
|
2932
|
+
color: '#ffc800',
|
|
2933
|
+
width: 2,
|
|
2934
|
+
lineDash: [6, 4]
|
|
2935
|
+
})
|
|
2936
|
+
});
|
|
2937
|
+
// Box selection (dragging a rectangle)
|
|
2938
|
+
this._interactionDragBox = new DragBox({
|
|
2939
|
+
condition: (evt) => primaryAction(evt) && this._isSelectMode(SelectionMode.Box),
|
|
2940
|
+
className: 'ol-wfst--dragbox'
|
|
2941
|
+
});
|
|
2942
|
+
this._interactionDragBox.on('boxend', (evt) => {
|
|
2943
|
+
if (!this._isSelectMode(SelectionMode.Box)) {
|
|
2944
|
+
return;
|
|
2945
|
+
}
|
|
2946
|
+
// Without shift, replace the current selection
|
|
2947
|
+
if (!evt.mapBrowserEvent.originalEvent.shiftKey) {
|
|
2948
|
+
this._collectionModify.clear();
|
|
2949
|
+
}
|
|
2950
|
+
this._selectByGeometry(this._interactionDragBox.getGeometry());
|
|
2951
|
+
});
|
|
2952
|
+
this._map.addInteraction(this._interactionDragBox);
|
|
2953
|
+
// Freehand selection (holding the mouse to draw a closed lasso)
|
|
2954
|
+
this._interactionFreehandSelect = new Draw({
|
|
2955
|
+
type: GeometryType.Polygon,
|
|
2956
|
+
freehand: true,
|
|
2957
|
+
stopClick: true,
|
|
2958
|
+
style: selectionStyle,
|
|
2959
|
+
condition: (evt) => primaryAction(evt) && this._isSelectMode(SelectionMode.Freehand)
|
|
2960
|
+
});
|
|
2961
|
+
this._interactionFreehandSelect.on('drawend', (evt) => {
|
|
2962
|
+
if (!this._isSelectMode(SelectionMode.Freehand)) {
|
|
2963
|
+
return;
|
|
2964
|
+
}
|
|
2965
|
+
const geometry = evt.feature.getGeometry();
|
|
2966
|
+
const ring = geometry ? geometry.getCoordinates()[0] : [];
|
|
2967
|
+
// Ignore degenerate lassos (a simple click without dragging)
|
|
2968
|
+
if (!ring.length || ring.length < 4 || geometry.getArea() <= 0) {
|
|
2969
|
+
return;
|
|
2970
|
+
}
|
|
2971
|
+
// Without shift, replace the current selection
|
|
2972
|
+
if (!this._shiftPressed) {
|
|
2973
|
+
this._collectionModify.clear();
|
|
2974
|
+
}
|
|
2975
|
+
this._selectByGeometry(geometry);
|
|
2976
|
+
});
|
|
2977
|
+
this._map.addInteraction(this._interactionFreehandSelect);
|
|
2978
|
+
this._refreshSelectionInteractions();
|
|
2979
|
+
}
|
|
2980
|
+
/**
|
|
2981
|
+
* Whether a selection tool mode is currently usable: only outside the
|
|
2982
|
+
* edit/draw modes.
|
|
2983
|
+
*
|
|
2984
|
+
* @param mode
|
|
2985
|
+
* @private
|
|
2986
|
+
*/
|
|
2987
|
+
_isSelectMode(mode) {
|
|
2988
|
+
return getMode() === null && getSelectionMode() === mode;
|
|
2989
|
+
}
|
|
2990
|
+
/**
|
|
2991
|
+
* Enable/disable the box and freehand selection interactions according to
|
|
2992
|
+
* the current selection tool and map mode.
|
|
2993
|
+
*
|
|
2994
|
+
* @private
|
|
2995
|
+
*/
|
|
2996
|
+
_refreshSelectionInteractions() {
|
|
2997
|
+
const active = getMode() === null;
|
|
2998
|
+
if (this._interactionDragBox) {
|
|
2999
|
+
this._interactionDragBox.setActive(active && getSelectionMode() === SelectionMode.Box);
|
|
3000
|
+
}
|
|
3001
|
+
if (this._interactionFreehandSelect) {
|
|
3002
|
+
this._interactionFreehandSelect.setActive(active && getSelectionMode() === SelectionMode.Freehand);
|
|
3003
|
+
}
|
|
3004
|
+
}
|
|
3005
|
+
/**
|
|
3006
|
+
* Select the features that intersect the given polygon on the active layer
|
|
3007
|
+
* and add them to the edit list. WFS features are picked from the already
|
|
3008
|
+
* loaded source, while WMS features are requested to the GeoServer with a
|
|
3009
|
+
* WFS GetFeature INTERSECTS filter.
|
|
3010
|
+
*
|
|
3011
|
+
* @param geometry
|
|
3012
|
+
* @private
|
|
3013
|
+
*/
|
|
3014
|
+
async _selectByGeometry(geometry) {
|
|
3015
|
+
const layer = getActiveLayerToInsertEls();
|
|
3016
|
+
if (!layer || !layer.getVisible() || !layer.isVisibleByZoom()) {
|
|
3017
|
+
showError(I18N.errors.layerNotVisible);
|
|
3018
|
+
return;
|
|
3019
|
+
}
|
|
3020
|
+
let features;
|
|
3021
|
+
if (layer instanceof WfsLayer) {
|
|
3022
|
+
const candidates = layer
|
|
3023
|
+
.getSource()
|
|
3024
|
+
.getFeaturesInExtent(geometry.getExtent());
|
|
3025
|
+
// The extent is only a bounding box; for the freehand lasso be
|
|
3026
|
+
// more precise and keep the features whose vertices fall inside it
|
|
3027
|
+
if (getSelectionMode() === SelectionMode.Freehand) {
|
|
3028
|
+
features = candidates.filter((feature) => {
|
|
3029
|
+
const featureGeom = feature.getGeometry();
|
|
3030
|
+
if (!featureGeom) {
|
|
3031
|
+
return false;
|
|
3032
|
+
}
|
|
3033
|
+
const flatCoords = featureGeom.getFlatCoordinates();
|
|
3034
|
+
const stride = featureGeom.getStride();
|
|
3035
|
+
for (let i = 0; i < flatCoords.length; i += stride) {
|
|
3036
|
+
if (geometry.intersectsCoordinate([
|
|
3037
|
+
flatCoords[i],
|
|
3038
|
+
flatCoords[i + 1]
|
|
3039
|
+
])) {
|
|
3040
|
+
return true;
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
return false;
|
|
3044
|
+
});
|
|
3045
|
+
}
|
|
3046
|
+
else {
|
|
3047
|
+
features = candidates;
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
else if (layer instanceof WmsLayer) {
|
|
3051
|
+
features = await layer._getFeaturesInGeometry(geometry);
|
|
3052
|
+
}
|
|
3053
|
+
if (features) {
|
|
3054
|
+
this._selectFeatures(features);
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
/**
|
|
3058
|
+
* Add the given features to the edit mode:
|
|
3059
|
+
* remove them from their original layer and push them into the edit
|
|
3060
|
+
* collection so they can be modified/deleted like the ones selected
|
|
3061
|
+
* with a single click.
|
|
3062
|
+
*
|
|
3063
|
+
* @param features
|
|
3064
|
+
* @private
|
|
3065
|
+
*/
|
|
3066
|
+
_selectFeatures(features) {
|
|
3067
|
+
if (!features || !features.length) {
|
|
3068
|
+
return;
|
|
3069
|
+
}
|
|
3070
|
+
const layer = getActiveLayerToInsertEls();
|
|
3071
|
+
const layerName = layer.get(BaseLayerProperty.NAME);
|
|
3072
|
+
const selectedIds = new Set(this._collectionModify.getArray().map((feature) => feature.getId()));
|
|
3073
|
+
features.forEach((feature) => {
|
|
3074
|
+
if (isFeatureEdited(feature) || selectedIds.has(feature.getId())) {
|
|
3075
|
+
return;
|
|
3076
|
+
}
|
|
3077
|
+
// WFS features are stored in their layer source: remove them from
|
|
3078
|
+
// there while they are being edited
|
|
3079
|
+
if (layer instanceof WfsLayer &&
|
|
3080
|
+
layer.getSource().hasFeature(feature)) {
|
|
3081
|
+
layer.getSource().removeFeature(feature);
|
|
3082
|
+
}
|
|
3083
|
+
this._addFeatureToEditMode(feature, null, layerName);
|
|
3084
|
+
});
|
|
3085
|
+
this._lockSelectedFeatures();
|
|
2376
3086
|
}
|
|
2377
3087
|
/**
|
|
2378
3088
|
* Layer to store temporary the elements to be edited
|
|
@@ -2381,6 +3091,149 @@ class Wfst extends Control {
|
|
|
2381
3091
|
_prepareEditLayer() {
|
|
2382
3092
|
this._map.addLayer(getEditLayer());
|
|
2383
3093
|
}
|
|
3094
|
+
/**
|
|
3095
|
+
* Create the dedicated layer used to highlight the queried feature, so
|
|
3096
|
+
* the source layers keep their own rendering untouched.
|
|
3097
|
+
* @private
|
|
3098
|
+
*/
|
|
3099
|
+
_prepareHighlightLayer() {
|
|
3100
|
+
this._highlightSource = new VectorSource();
|
|
3101
|
+
this._highlightLayer = new VectorLayer({
|
|
3102
|
+
source: this._highlightSource,
|
|
3103
|
+
style: (feature) => queryStyleFunction(),
|
|
3104
|
+
zIndex: 100
|
|
3105
|
+
});
|
|
3106
|
+
this._map.addLayer(this._highlightLayer);
|
|
3107
|
+
}
|
|
3108
|
+
/**
|
|
3109
|
+
* Listen for clicks while the query mode is active: get the clicked
|
|
3110
|
+
* feature of the active layer (from the already loaded source for WFS,
|
|
3111
|
+
* or through a GetFeatureInfo request for WMS), highlight it and show
|
|
3112
|
+
* its attributes in a popup.
|
|
3113
|
+
* @private
|
|
3114
|
+
*/
|
|
3115
|
+
_addQueryInteraction() {
|
|
3116
|
+
this._keyClickQuery = this._map.on(this._options.evtType || 'singleclick', async (evt) => {
|
|
3117
|
+
if (getMode() !== Modes.Query) {
|
|
3118
|
+
return;
|
|
3119
|
+
}
|
|
3120
|
+
const layer = getActiveLayerToInsertEls();
|
|
3121
|
+
// Skip hidden layers
|
|
3122
|
+
if (!layer.getVisible() || !layer.isVisibleByZoom()) {
|
|
3123
|
+
return;
|
|
3124
|
+
}
|
|
3125
|
+
let feature = null;
|
|
3126
|
+
if (layer instanceof WfsLayer) {
|
|
3127
|
+
const featuresAtPixel = this._map.getFeaturesAtPixel(evt.pixel, {
|
|
3128
|
+
hitTolerance: 10,
|
|
3129
|
+
layerFilter: (candidate) => candidate === layer
|
|
3130
|
+
});
|
|
3131
|
+
if (featuresAtPixel && featuresAtPixel.length) {
|
|
3132
|
+
feature = featuresAtPixel[0];
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
else if (layer instanceof WmsLayer) {
|
|
3136
|
+
const features = await layer._getFeaturesByClickEvent(evt);
|
|
3137
|
+
if (features === null || features === void 0 ? void 0 : features.length) {
|
|
3138
|
+
feature = features[0];
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
// Click on empty space: clear the previous result
|
|
3142
|
+
if (!feature) {
|
|
3143
|
+
this._clearQueryResult();
|
|
3144
|
+
return;
|
|
3145
|
+
}
|
|
3146
|
+
this._showQueryResult(feature, layer, evt.coordinate);
|
|
3147
|
+
});
|
|
3148
|
+
}
|
|
3149
|
+
/**
|
|
3150
|
+
* Highlight the given feature and attach a popup with its attributes.
|
|
3151
|
+
* @param feature
|
|
3152
|
+
* @param layer
|
|
3153
|
+
* @param coordinate
|
|
3154
|
+
* @private
|
|
3155
|
+
*/
|
|
3156
|
+
_showQueryResult(feature, layer, coordinate) {
|
|
3157
|
+
this._clearQueryResult();
|
|
3158
|
+
this._highlightSource.addFeature(feature);
|
|
3159
|
+
this._queryOverlay = new QueryOverlay(feature, coordinate, layer);
|
|
3160
|
+
// @ts-expect-error
|
|
3161
|
+
this._queryOverlay.on('close', () => {
|
|
3162
|
+
this._clearQueryResult();
|
|
3163
|
+
});
|
|
3164
|
+
this._map.addOverlay(this._queryOverlay);
|
|
3165
|
+
}
|
|
3166
|
+
/**
|
|
3167
|
+
* Remove the query popup overlay if any.
|
|
3168
|
+
* @private
|
|
3169
|
+
*/
|
|
3170
|
+
_removeQueryOverlay() {
|
|
3171
|
+
if (this._queryOverlay) {
|
|
3172
|
+
this._map.removeOverlay(this._queryOverlay);
|
|
3173
|
+
this._queryOverlay = null;
|
|
3174
|
+
return;
|
|
3175
|
+
}
|
|
3176
|
+
const overlay = this._map.getOverlayById(this._queryOverlayId);
|
|
3177
|
+
if (overlay) {
|
|
3178
|
+
this._map.removeOverlay(overlay);
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
/**
|
|
3182
|
+
* Clear the highlight and remove the query popup.
|
|
3183
|
+
* @private
|
|
3184
|
+
*/
|
|
3185
|
+
_clearQueryResult() {
|
|
3186
|
+
var _a;
|
|
3187
|
+
(_a = this._highlightSource) === null || _a === void 0 ? void 0 : _a.clear();
|
|
3188
|
+
this._removeQueryOverlay();
|
|
3189
|
+
}
|
|
3190
|
+
/**
|
|
3191
|
+
* Activate/deactivate the query mode: while it is active, clicking a
|
|
3192
|
+
* feature of the active layer shows its info in a popup instead of
|
|
3193
|
+
* selecting it for editing.
|
|
3194
|
+
*
|
|
3195
|
+
* @param bool
|
|
3196
|
+
* @public
|
|
3197
|
+
*/
|
|
3198
|
+
activateQueryMode(bool = true) {
|
|
3199
|
+
var _a, _b;
|
|
3200
|
+
if (bool) {
|
|
3201
|
+
// Leave any draw interaction active
|
|
3202
|
+
if (this._interactionDraw) {
|
|
3203
|
+
this._map.removeInteraction(this._interactionDraw);
|
|
3204
|
+
this._viewport.classList.remove('draw-mode');
|
|
3205
|
+
}
|
|
3206
|
+
resetStateButtons();
|
|
3207
|
+
deactivateSelectModeButton();
|
|
3208
|
+
activateQueryButton();
|
|
3209
|
+
this._clearHoverState();
|
|
3210
|
+
activateMode(Modes.Query);
|
|
3211
|
+
// Disable the edit selection interactions while querying
|
|
3212
|
+
if (this._interactionSelectModify) {
|
|
3213
|
+
this._interactionSelectModify.setActive(false);
|
|
3214
|
+
}
|
|
3215
|
+
if (this._interactionWfsSelect) {
|
|
3216
|
+
this._interactionWfsSelect.setActive(false);
|
|
3217
|
+
}
|
|
3218
|
+
(_a = this._interactionModify) === null || _a === void 0 ? void 0 : _a.setActive(false);
|
|
3219
|
+
this._syncExtendLineOverlays();
|
|
3220
|
+
this._refreshSelectionInteractions();
|
|
3221
|
+
}
|
|
3222
|
+
else {
|
|
3223
|
+
activateMode(null);
|
|
3224
|
+
deactivateQueryButton();
|
|
3225
|
+
activateSelectModeButton(getSelectionMode());
|
|
3226
|
+
if (this._interactionSelectModify) {
|
|
3227
|
+
this._interactionSelectModify.setActive(true);
|
|
3228
|
+
}
|
|
3229
|
+
if (this._interactionWfsSelect) {
|
|
3230
|
+
this._interactionWfsSelect.setActive(true);
|
|
3231
|
+
}
|
|
3232
|
+
(_b = this._interactionModify) === null || _b === void 0 ? void 0 : _b.setActive(true);
|
|
3233
|
+
this._refreshSelectionInteractions();
|
|
3234
|
+
}
|
|
3235
|
+
this._clearQueryResult();
|
|
3236
|
+
}
|
|
2384
3237
|
/**
|
|
2385
3238
|
* @private
|
|
2386
3239
|
*/
|
|
@@ -2395,16 +3248,74 @@ class Wfst extends Control {
|
|
|
2395
3248
|
return;
|
|
2396
3249
|
}
|
|
2397
3250
|
if (key === 'Delete') {
|
|
2398
|
-
const selectedFeatures = this._collectionModify;
|
|
2399
|
-
if (selectedFeatures) {
|
|
2400
|
-
|
|
2401
|
-
this._deleteFeature(feature, true);
|
|
2402
|
-
});
|
|
3251
|
+
const selectedFeatures = this._collectionModify.getArray();
|
|
3252
|
+
if (selectedFeatures.length) {
|
|
3253
|
+
this._deleteFeature(selectedFeatures, true);
|
|
2403
3254
|
}
|
|
2404
3255
|
}
|
|
2405
3256
|
});
|
|
2406
3257
|
};
|
|
2407
3258
|
keyboardEvents();
|
|
3259
|
+
/**
|
|
3260
|
+
* Change the cursor to a pointing hand and highlight the feature
|
|
3261
|
+
* when hovering over a WFS feature of the active layer.
|
|
3262
|
+
* @private
|
|
3263
|
+
*/
|
|
3264
|
+
const pointerMoveHandler = (evt) => {
|
|
3265
|
+
if (evt.dragging) {
|
|
3266
|
+
return;
|
|
3267
|
+
}
|
|
3268
|
+
// In edit mode, give pointer feedback on the editable
|
|
3269
|
+
// vertices/segments
|
|
3270
|
+
if (getMode() === Modes.Edit) {
|
|
3271
|
+
this._clearHoverState();
|
|
3272
|
+
this._updateEditCursor(evt);
|
|
3273
|
+
return;
|
|
3274
|
+
}
|
|
3275
|
+
// Only show the hover outside edit and draw modes
|
|
3276
|
+
if (getMode() !== null) {
|
|
3277
|
+
this._clearHoverState();
|
|
3278
|
+
return;
|
|
3279
|
+
}
|
|
3280
|
+
const layer = getActiveLayerToInsertEls();
|
|
3281
|
+
// Hover only works on visible WFS layers
|
|
3282
|
+
if (!layer ||
|
|
3283
|
+
!layer.getVisible() ||
|
|
3284
|
+
!layer.isVisibleByZoom() ||
|
|
3285
|
+
!(layer instanceof WfsLayer)) {
|
|
3286
|
+
this._clearHoverState();
|
|
3287
|
+
return;
|
|
3288
|
+
}
|
|
3289
|
+
const features = this._map.getFeaturesAtPixel(evt.pixel, {
|
|
3290
|
+
hitTolerance: 10,
|
|
3291
|
+
layerFilter: (candidate) => candidate === layer
|
|
3292
|
+
});
|
|
3293
|
+
if (features && features.length) {
|
|
3294
|
+
const feature = features[0];
|
|
3295
|
+
if (feature !== this._hoveredFeature) {
|
|
3296
|
+
this._clearHoverState();
|
|
3297
|
+
this._hoveredFeature = feature;
|
|
3298
|
+
this._hoveredFeature.setStyle((f) => styleFunction(f, true));
|
|
3299
|
+
}
|
|
3300
|
+
if (this._viewport.style.cursor !== 'pointer') {
|
|
3301
|
+
this._viewport.style.cursor = 'pointer';
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
else {
|
|
3305
|
+
this._clearHoverState();
|
|
3306
|
+
}
|
|
3307
|
+
};
|
|
3308
|
+
this._map.on('pointermove', pointerMoveHandler);
|
|
3309
|
+
// Store the shift state when a gesture starts, so the freehand
|
|
3310
|
+
// selection can decide between adding or replacing the selection
|
|
3311
|
+
this._viewport.addEventListener('pointerdown', (evt) => {
|
|
3312
|
+
this._shiftPressed = evt.shiftKey;
|
|
3313
|
+
});
|
|
3314
|
+
// Clear the hover before firing a click, otherwise the Select interaction
|
|
3315
|
+
// would capture the hover style as the original style of the feature and
|
|
3316
|
+
// restore it on deselect, leaving the highlight permanently enabled.
|
|
3317
|
+
this._viewport.addEventListener('pointerdown', () => this._clearHoverState());
|
|
3318
|
+
this._viewport.addEventListener('pointerleave', () => this._clearHoverState(), { once: false });
|
|
2408
3319
|
this._map.on('moveend', () => {
|
|
2409
3320
|
this._currentZoom = this._view.getZoom();
|
|
2410
3321
|
if (this._currentZoom !== this._lastZoom) {
|
|
@@ -2428,6 +3339,62 @@ class Wfst extends Control {
|
|
|
2428
3339
|
}
|
|
2429
3340
|
});
|
|
2430
3341
|
}
|
|
3342
|
+
/**
|
|
3343
|
+
* Allow panning with the middle mouse button at all times, even when the
|
|
3344
|
+
* draw/freehand/box interactions are consuming the left button. The
|
|
3345
|
+
* middle pointerdown is intercepted in the capture phase so OpenLayers
|
|
3346
|
+
* never processes it and the active tool is not affected.
|
|
3347
|
+
*
|
|
3348
|
+
* @private
|
|
3349
|
+
*/
|
|
3350
|
+
_addMiddleButtonPan() {
|
|
3351
|
+
const viewport = this._viewport;
|
|
3352
|
+
let panning = false;
|
|
3353
|
+
let lastX = 0;
|
|
3354
|
+
let lastY = 0;
|
|
3355
|
+
const isMiddleButton = (evt) => evt.pointerType === 'mouse' && evt.button === 1;
|
|
3356
|
+
const stopPanning = () => {
|
|
3357
|
+
panning = false;
|
|
3358
|
+
};
|
|
3359
|
+
viewport.addEventListener('pointerdown', (evt) => {
|
|
3360
|
+
if (!isMiddleButton(evt)) {
|
|
3361
|
+
return;
|
|
3362
|
+
}
|
|
3363
|
+
evt.preventDefault();
|
|
3364
|
+
evt.stopPropagation();
|
|
3365
|
+
viewport.setPointerCapture(evt.pointerId);
|
|
3366
|
+
panning = true;
|
|
3367
|
+
lastX = evt.clientX;
|
|
3368
|
+
lastY = evt.clientY;
|
|
3369
|
+
if (this._view.getAnimating()) {
|
|
3370
|
+
this._view.cancelAnimations();
|
|
3371
|
+
}
|
|
3372
|
+
}, true);
|
|
3373
|
+
viewport.addEventListener('pointermove', (evt) => {
|
|
3374
|
+
if (!panning) {
|
|
3375
|
+
return;
|
|
3376
|
+
}
|
|
3377
|
+
evt.preventDefault();
|
|
3378
|
+
const delta = [lastX - evt.clientX, evt.clientY - lastY];
|
|
3379
|
+
lastX = evt.clientX;
|
|
3380
|
+
lastY = evt.clientY;
|
|
3381
|
+
const resolution = this._view.getResolution();
|
|
3382
|
+
const rotation = this._view.getRotation();
|
|
3383
|
+
const cosAngle = Math.cos(rotation);
|
|
3384
|
+
const sinAngle = Math.sin(rotation);
|
|
3385
|
+
const x = (delta[0] * cosAngle - delta[1] * sinAngle) * resolution;
|
|
3386
|
+
const y = (delta[1] * cosAngle + delta[0] * sinAngle) * resolution;
|
|
3387
|
+
this._view.adjustCenterInternal([x, y]);
|
|
3388
|
+
});
|
|
3389
|
+
const endPan = () => {
|
|
3390
|
+
if (!panning) {
|
|
3391
|
+
return;
|
|
3392
|
+
}
|
|
3393
|
+
stopPanning();
|
|
3394
|
+
};
|
|
3395
|
+
viewport.addEventListener('pointerup', endPan);
|
|
3396
|
+
viewport.addEventListener('pointercancel', endPan);
|
|
3397
|
+
}
|
|
2431
3398
|
/**
|
|
2432
3399
|
* Add map handlers
|
|
2433
3400
|
* @private
|
|
@@ -2437,13 +3404,19 @@ class Wfst extends Control {
|
|
|
2437
3404
|
// This prevent events fired on select and deselect features that has no changes and should
|
|
2438
3405
|
// not be updated in the geoserver
|
|
2439
3406
|
this._interactionModify.on('modifyend', (evt) => {
|
|
2440
|
-
|
|
2441
|
-
|
|
3407
|
+
evt.features.forEach((feature) => {
|
|
3408
|
+
addFeatureToEditedList(feature);
|
|
3409
|
+
});
|
|
2442
3410
|
super.dispatchEvent(evt);
|
|
2443
3411
|
});
|
|
2444
3412
|
this._interactionModify.on('modifystart', (evt) => {
|
|
2445
3413
|
super.dispatchEvent(evt);
|
|
2446
3414
|
});
|
|
3415
|
+
// Keep the "continue drawing" buttons glued to the line endpoints
|
|
3416
|
+
// @ts-expect-error - 'modify' is a valid Modify event not typed in the `on` signature
|
|
3417
|
+
this._interactionModify.on('modify', () => {
|
|
3418
|
+
this._updateExtendOverlayPositions();
|
|
3419
|
+
});
|
|
2447
3420
|
this._onDeselectFeatureEvent();
|
|
2448
3421
|
this._onRemoveFeatureEvent();
|
|
2449
3422
|
}
|
|
@@ -2455,6 +3428,9 @@ class Wfst extends Control {
|
|
|
2455
3428
|
this._layersControl = new LayersControl(this._options.showUpload ? this._uploads : null, this._options.uploadFormats);
|
|
2456
3429
|
// @ts-expect-error
|
|
2457
3430
|
this._layersControl.on('drawMode', () => {
|
|
3431
|
+
if (getMode() === Modes.Query) {
|
|
3432
|
+
this.activateQueryMode(false);
|
|
3433
|
+
}
|
|
2458
3434
|
if (getMode() === Modes.Draw) {
|
|
2459
3435
|
resetStateButtons();
|
|
2460
3436
|
this.activateEditMode();
|
|
@@ -2481,9 +3457,37 @@ class Wfst extends Control {
|
|
|
2481
3457
|
this.activateDrawMode(getActiveLayerToInsertEls());
|
|
2482
3458
|
}
|
|
2483
3459
|
});
|
|
3460
|
+
[
|
|
3461
|
+
['selectModeSingle', SelectionMode.Single],
|
|
3462
|
+
['selectModeBox', SelectionMode.Box],
|
|
3463
|
+
['selectModeFreehand', SelectionMode.Freehand]
|
|
3464
|
+
].forEach(([evtName, mode]) => {
|
|
3465
|
+
// @ts-expect-error
|
|
3466
|
+
this._layersControl.on(evtName, () => {
|
|
3467
|
+
// Leaving the draw mode when a select tool is chosen
|
|
3468
|
+
if (getMode() === Modes.Draw) {
|
|
3469
|
+
resetStateButtons();
|
|
3470
|
+
this.activateEditMode();
|
|
3471
|
+
}
|
|
3472
|
+
// Leaving the query mode when a select tool is chosen
|
|
3473
|
+
if (getMode() === Modes.Query) {
|
|
3474
|
+
this.activateQueryMode(false);
|
|
3475
|
+
}
|
|
3476
|
+
setSelectionMode(mode);
|
|
3477
|
+
activateSelectModeButton(mode);
|
|
3478
|
+
this._refreshSelectionInteractions();
|
|
3479
|
+
});
|
|
3480
|
+
});
|
|
3481
|
+
// @ts-expect-error
|
|
3482
|
+
this._layersControl.on('queryMode', () => {
|
|
3483
|
+
this.activateQueryMode(getMode() !== Modes.Query);
|
|
3484
|
+
});
|
|
2484
3485
|
const controlEl = this._layersControl.render();
|
|
2485
3486
|
this._selectDraw = controlEl.querySelector('.wfst--tools-control--select-draw');
|
|
2486
|
-
this._controlWidgetToolsDiv.append(controlEl);
|
|
3487
|
+
this._controlWidgetToolsDiv.append(controlEl.querySelector('.wfst--tools-control--head'));
|
|
3488
|
+
if (this._options.layers.length > 1) {
|
|
3489
|
+
this._layersWidgetDiv.append(controlEl.querySelector('.wfst--tools-control--select-layers'));
|
|
3490
|
+
}
|
|
2487
3491
|
}
|
|
2488
3492
|
/**
|
|
2489
3493
|
*
|
|
@@ -2502,6 +3506,9 @@ class Wfst extends Control {
|
|
|
2502
3506
|
_restoreFeatureToLayer(feature, layerName) {
|
|
2503
3507
|
layerName = layerName || feature.get('_layerName_');
|
|
2504
3508
|
const layer = getStoredMapLayers()[layerName];
|
|
3509
|
+
// Reset any per-feature style (e.g. hover) before returning the feature
|
|
3510
|
+
// to its layer, so it renders with the layer's default style.
|
|
3511
|
+
feature.setStyle(undefined);
|
|
2505
3512
|
layer.getSource().addFeature(feature);
|
|
2506
3513
|
}
|
|
2507
3514
|
/**
|
|
@@ -2535,10 +3542,18 @@ class Wfst extends Control {
|
|
|
2535
3542
|
this._removeFeatureFromTmpLayer(feature);
|
|
2536
3543
|
}
|
|
2537
3544
|
};
|
|
3545
|
+
if (this._keySelectBound) {
|
|
3546
|
+
return;
|
|
3547
|
+
}
|
|
3548
|
+
this._keySelectBound = true;
|
|
2538
3549
|
// This is fired when a feature is deselected and fires the transaction process
|
|
3550
|
+
if (this._keySelect) {
|
|
3551
|
+
unByKey(this._keySelect);
|
|
3552
|
+
}
|
|
2539
3553
|
this._keySelect = this._collectionModify.on('remove', (evt) => {
|
|
2540
3554
|
const feature = evt.element;
|
|
2541
3555
|
this._deselectEditFeature(feature);
|
|
3556
|
+
this._syncEditOverlays();
|
|
2542
3557
|
checkIfFeatureIsChanged(feature);
|
|
2543
3558
|
this._editModeOff();
|
|
2544
3559
|
});
|
|
@@ -2559,6 +3574,7 @@ class Wfst extends Control {
|
|
|
2559
3574
|
}
|
|
2560
3575
|
if (this._keySelect) {
|
|
2561
3576
|
unByKey(this._keySelect);
|
|
3577
|
+
this._keySelectBound = false;
|
|
2562
3578
|
}
|
|
2563
3579
|
const layerName = feature.get('_layerName_');
|
|
2564
3580
|
const ll = this.getLayerByName(layerName);
|
|
@@ -2574,27 +3590,52 @@ class Wfst extends Control {
|
|
|
2574
3590
|
}
|
|
2575
3591
|
/**
|
|
2576
3592
|
*
|
|
2577
|
-
* @param
|
|
3593
|
+
* @param features
|
|
2578
3594
|
* @private
|
|
2579
3595
|
*/
|
|
2580
|
-
_editModeOn(
|
|
2581
|
-
|
|
3596
|
+
_editModeOn(features) {
|
|
3597
|
+
features = Array.isArray(features) ? features : [features];
|
|
3598
|
+
features.forEach((feature) => {
|
|
3599
|
+
this._editFeaturesOriginal[String(feature.getId())] =
|
|
3600
|
+
feature.clone();
|
|
3601
|
+
});
|
|
2582
3602
|
activateMode(Modes.Edit);
|
|
3603
|
+
// Show the "continue drawing" buttons on the line endpoints
|
|
3604
|
+
this._syncExtendLineOverlays();
|
|
2583
3605
|
// To refresh the style
|
|
2584
3606
|
getEditLayer().getSource().changed();
|
|
2585
|
-
this.
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
3607
|
+
this._refreshSelectionInteractions();
|
|
3608
|
+
const multiOverlay = this._map.getOverlayById(this._multiOverlayId);
|
|
3609
|
+
if (multiOverlay) {
|
|
3610
|
+
this._map.removeOverlay(multiOverlay);
|
|
3611
|
+
}
|
|
3612
|
+
features.forEach((feature) => {
|
|
3613
|
+
this._removeOverlayHelper(feature);
|
|
3614
|
+
});
|
|
3615
|
+
this._controlApplyDiscardChanges = new EditControlChangesEl(features);
|
|
3616
|
+
this._controlApplyDiscardChanges.on('cancel', (evt) => {
|
|
3617
|
+
const list = evt.features;
|
|
3618
|
+
Array.from(list).forEach((feature) => {
|
|
3619
|
+
const original = this._editFeaturesOriginal[String(feature.getId())];
|
|
3620
|
+
if (original) {
|
|
3621
|
+
feature.setGeometry(original.getGeometry());
|
|
3622
|
+
}
|
|
3623
|
+
removeFeatureFromEditList(feature);
|
|
3624
|
+
this._collectionModify.remove(feature);
|
|
3625
|
+
});
|
|
2591
3626
|
});
|
|
2592
|
-
this._controlApplyDiscardChanges.on('apply', (
|
|
3627
|
+
this._controlApplyDiscardChanges.on('apply', async (evt) => {
|
|
2593
3628
|
showLoading();
|
|
2594
|
-
|
|
3629
|
+
const list = evt.features;
|
|
3630
|
+
const ok = await this._transactEditList(list);
|
|
3631
|
+
if (ok) {
|
|
3632
|
+
Array.from(list).forEach((feature) => {
|
|
3633
|
+
this._collectionModify.remove(feature);
|
|
3634
|
+
});
|
|
3635
|
+
}
|
|
2595
3636
|
});
|
|
2596
|
-
this._controlApplyDiscardChanges.on('delete', (
|
|
2597
|
-
this._deleteFeature(
|
|
3637
|
+
this._controlApplyDiscardChanges.on('delete', (evt) => {
|
|
3638
|
+
this._deleteFeature(evt.features, true);
|
|
2598
3639
|
});
|
|
2599
3640
|
this._map.addControl(this._controlApplyDiscardChanges);
|
|
2600
3641
|
}
|
|
@@ -2603,30 +3644,331 @@ class Wfst extends Control {
|
|
|
2603
3644
|
*/
|
|
2604
3645
|
_editModeOff() {
|
|
2605
3646
|
activateMode(null);
|
|
3647
|
+
this._destroyExtendLineOverlays();
|
|
2606
3648
|
this._map.removeControl(this._controlApplyDiscardChanges);
|
|
3649
|
+
this._refreshSelectionInteractions();
|
|
3650
|
+
}
|
|
3651
|
+
/**
|
|
3652
|
+
* Get the first and last coordinate of every line component of a
|
|
3653
|
+
* LineString/MultiLineString geometry, including the line components
|
|
3654
|
+
* nested inside a GeometryCollection. Anything else returns an empty
|
|
3655
|
+
* array.
|
|
3656
|
+
*
|
|
3657
|
+
* @param geometry
|
|
3658
|
+
* @private
|
|
3659
|
+
*/
|
|
3660
|
+
_getLineEndPoints(geometry) {
|
|
3661
|
+
const endPoints = [];
|
|
3662
|
+
const appendLine = (line, collection) => {
|
|
3663
|
+
if (line instanceof LineString) {
|
|
3664
|
+
if (line.getCoordinates().length) {
|
|
3665
|
+
endPoints.push({
|
|
3666
|
+
first: line.getFirstCoordinate(),
|
|
3667
|
+
last: line.getLastCoordinate(),
|
|
3668
|
+
line,
|
|
3669
|
+
lineIndex: 0,
|
|
3670
|
+
collection
|
|
3671
|
+
});
|
|
3672
|
+
}
|
|
3673
|
+
return;
|
|
3674
|
+
}
|
|
3675
|
+
line.getCoordinates().forEach((coordinates, lineIndex) => {
|
|
3676
|
+
if (coordinates.length) {
|
|
3677
|
+
endPoints.push({
|
|
3678
|
+
first: coordinates[0],
|
|
3679
|
+
last: coordinates[coordinates.length - 1],
|
|
3680
|
+
line,
|
|
3681
|
+
lineIndex,
|
|
3682
|
+
collection
|
|
3683
|
+
});
|
|
3684
|
+
}
|
|
3685
|
+
});
|
|
3686
|
+
};
|
|
3687
|
+
// "getGeometries()" returns deep clones (see ol GeometryCollection),
|
|
3688
|
+
// but the extend/merge must work on the actual members.
|
|
3689
|
+
const collect = (geometry, collection) => {
|
|
3690
|
+
if (geometry instanceof LineString ||
|
|
3691
|
+
geometry instanceof MultiLineString$1) {
|
|
3692
|
+
appendLine(geometry, collection);
|
|
3693
|
+
}
|
|
3694
|
+
else if (geometry instanceof GeometryCollection$1) {
|
|
3695
|
+
geometry.getGeometriesArray().forEach((member) => {
|
|
3696
|
+
collect(member, geometry);
|
|
3697
|
+
});
|
|
3698
|
+
}
|
|
3699
|
+
};
|
|
3700
|
+
collect(geometry);
|
|
3701
|
+
return endPoints;
|
|
3702
|
+
}
|
|
3703
|
+
/**
|
|
3704
|
+
* Remove every "continue drawing" overlay and abort any active line
|
|
3705
|
+
* extension draw.
|
|
3706
|
+
*
|
|
3707
|
+
* @private
|
|
3708
|
+
*/
|
|
3709
|
+
_destroyExtendLineOverlays() {
|
|
3710
|
+
if (this._extendDraw) {
|
|
3711
|
+
this._finishExtendDraw();
|
|
3712
|
+
}
|
|
3713
|
+
this._extendOverlays.forEach((overlay) => {
|
|
3714
|
+
this._map.removeOverlay(overlay);
|
|
3715
|
+
});
|
|
3716
|
+
this._extendOverlays = [];
|
|
3717
|
+
}
|
|
3718
|
+
/**
|
|
3719
|
+
* Keep the "continue drawing" overlays in sync with the current edit
|
|
3720
|
+
* selection: they are only shown while editing LineString/MultiLineString
|
|
3721
|
+
* features, and they are repositioned on the endpoints of each line
|
|
3722
|
+
* component.
|
|
3723
|
+
*
|
|
3724
|
+
* @private
|
|
3725
|
+
*/
|
|
3726
|
+
_syncExtendLineOverlays() {
|
|
3727
|
+
if (getMode() !== Modes.Edit || !this._collectionModify) {
|
|
3728
|
+
this._destroyExtendLineOverlays();
|
|
3729
|
+
return;
|
|
3730
|
+
}
|
|
3731
|
+
const items = this._collectionModify.getArray();
|
|
3732
|
+
// Remove overlays whose feature was deselected or lost its line geometry
|
|
3733
|
+
const removed = [];
|
|
3734
|
+
this._extendOverlays.forEach((overlay) => {
|
|
3735
|
+
const stale = !items.includes(overlay.feature) ||
|
|
3736
|
+
overlay.component >=
|
|
3737
|
+
this._getLineEndPoints(overlay.feature.getGeometry())
|
|
3738
|
+
.length;
|
|
3739
|
+
if (stale) {
|
|
3740
|
+
this._map.removeOverlay(overlay);
|
|
3741
|
+
removed.push(overlay);
|
|
3742
|
+
}
|
|
3743
|
+
});
|
|
3744
|
+
this._extendOverlays = this._extendOverlays.filter((overlay) => !removed.includes(overlay));
|
|
3745
|
+
// Add overlays for new LineString/MultiLineString features
|
|
3746
|
+
items.forEach((feature) => {
|
|
3747
|
+
this._getLineEndPoints(feature.getGeometry()).forEach(({ first, last }, component) => {
|
|
3748
|
+
['start', 'end'].forEach((side) => {
|
|
3749
|
+
const exists = this._extendOverlays.some((overlay) => overlay.feature === feature &&
|
|
3750
|
+
overlay.component === component &&
|
|
3751
|
+
overlay.side === side);
|
|
3752
|
+
if (exists) {
|
|
3753
|
+
return;
|
|
3754
|
+
}
|
|
3755
|
+
const overlay = new ExtendLineOverlay(feature, side === 'start' ? first : last, side, component, () => this._startExtendLineDraw(feature, side, component));
|
|
3756
|
+
this._extendOverlays.push(overlay);
|
|
3757
|
+
this._map.addOverlay(overlay);
|
|
3758
|
+
});
|
|
3759
|
+
});
|
|
3760
|
+
});
|
|
3761
|
+
this._updateExtendOverlayPositions();
|
|
3762
|
+
}
|
|
3763
|
+
/**
|
|
3764
|
+
* Move the "continue drawing" overlays to the current position of their
|
|
3765
|
+
* line endpoints, e.g. after a vertex is dragged with the Modify
|
|
3766
|
+
* interaction.
|
|
3767
|
+
*
|
|
3768
|
+
* @private
|
|
3769
|
+
*/
|
|
3770
|
+
_updateExtendOverlayPositions() {
|
|
3771
|
+
this._extendOverlays.forEach((overlay) => {
|
|
3772
|
+
const point = this._getLineEndPoints(overlay.feature.getGeometry())[overlay.component];
|
|
3773
|
+
if (!point) {
|
|
3774
|
+
return;
|
|
3775
|
+
}
|
|
3776
|
+
overlay.setPosition(overlay.side === 'start' ? point.first : point.last);
|
|
3777
|
+
});
|
|
3778
|
+
}
|
|
3779
|
+
/**
|
|
3780
|
+
* Start drawing a continuation of a line from one of its endpoints. The
|
|
3781
|
+
* sketch is anchored on the clicked vertex, so the next clicks append the
|
|
3782
|
+
* new points. A double click (or clicking back on the last point) ends
|
|
3783
|
+
* the drawing and merges the segment into the feature.
|
|
3784
|
+
*
|
|
3785
|
+
* @param feature
|
|
3786
|
+
* @param side 'start' prepends the new segment, 'end' appends it
|
|
3787
|
+
* @param component index of the clicked line component
|
|
3788
|
+
* @private
|
|
3789
|
+
*/
|
|
3790
|
+
_startExtendLineDraw(feature, side, component = 0) {
|
|
3791
|
+
var _a;
|
|
3792
|
+
if (this._extendDraw || getMode() !== Modes.Edit) {
|
|
3793
|
+
return;
|
|
3794
|
+
}
|
|
3795
|
+
const points = this._getLineEndPoints(feature.getGeometry());
|
|
3796
|
+
if (!points.length || component >= points.length) {
|
|
3797
|
+
return;
|
|
3798
|
+
}
|
|
3799
|
+
const anchor = side === 'start' ? points[component].first : points[component].last;
|
|
3800
|
+
// While extending, the Modify/Select interactions must not grab the clicks
|
|
3801
|
+
(_a = this._interactionModify) === null || _a === void 0 ? void 0 : _a.setActive(false);
|
|
3802
|
+
if (this._interactionSelectModify) {
|
|
3803
|
+
this._interactionSelectModify.setActive(false);
|
|
3804
|
+
}
|
|
3805
|
+
if (this._interactionWfsSelect) {
|
|
3806
|
+
this._interactionWfsSelect.setActive(false);
|
|
3807
|
+
}
|
|
3808
|
+
this._extendDraw = new Draw({
|
|
3809
|
+
type: GeometryType.LineString,
|
|
3810
|
+
style: (f) => styleFunction(f),
|
|
3811
|
+
stopClick: true // To prevent firing a map/wms click
|
|
3812
|
+
});
|
|
3813
|
+
this._extendDraw.on('drawend', (evt) => {
|
|
3814
|
+
this._mergeExtendLine(feature, side, evt.feature, component);
|
|
3815
|
+
});
|
|
3816
|
+
this._extendDraw.on('drawabort', () => {
|
|
3817
|
+
this._finishExtendDraw();
|
|
3818
|
+
});
|
|
3819
|
+
this._map.addInteraction(this._extendDraw);
|
|
3820
|
+
// Anchor the sketch on the clicked vertex: the user only has to digitize
|
|
3821
|
+
// the continuation points (this also dispatches the "drawstart" event)
|
|
3822
|
+
this._extendDraw.appendCoordinates([anchor]);
|
|
3823
|
+
}
|
|
3824
|
+
/**
|
|
3825
|
+
* Merge the just-drawn segment into the edited feature's line geometry and
|
|
3826
|
+
* restore the editing interactions.
|
|
3827
|
+
*
|
|
3828
|
+
* @param feature
|
|
3829
|
+
* @param side
|
|
3830
|
+
* @param sketch
|
|
3831
|
+
* @param component index of the extended line component
|
|
3832
|
+
* @private
|
|
3833
|
+
*/
|
|
3834
|
+
_mergeExtendLine(feature, side, sketch, component = 0) {
|
|
3835
|
+
const points = this._getLineEndPoints(feature.getGeometry());
|
|
3836
|
+
const point = points[component];
|
|
3837
|
+
if (!point) {
|
|
3838
|
+
this._finishExtendDraw();
|
|
3839
|
+
return;
|
|
3840
|
+
}
|
|
3841
|
+
const segment = sketch.getGeometry().getCoordinates();
|
|
3842
|
+
const { line, lineIndex } = point;
|
|
3843
|
+
if (line instanceof LineString) {
|
|
3844
|
+
const coords = line.getCoordinates();
|
|
3845
|
+
if (side === 'start') {
|
|
3846
|
+
line.setCoordinates([...segment.slice(1).reverse(), ...coords]);
|
|
3847
|
+
}
|
|
3848
|
+
else {
|
|
3849
|
+
line.setCoordinates([...coords, ...segment.slice(1)]);
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3852
|
+
else if (line instanceof MultiLineString$1) {
|
|
3853
|
+
const coords = line.getCoordinates();
|
|
3854
|
+
if (side === 'start') {
|
|
3855
|
+
coords[lineIndex] = [
|
|
3856
|
+
...segment.slice(1).reverse(),
|
|
3857
|
+
...coords[lineIndex]
|
|
3858
|
+
];
|
|
3859
|
+
}
|
|
3860
|
+
else {
|
|
3861
|
+
coords[lineIndex] = [...coords[lineIndex], ...segment.slice(1)];
|
|
3862
|
+
}
|
|
3863
|
+
line.setCoordinates(coords);
|
|
3864
|
+
}
|
|
3865
|
+
// Only mark the feature as edited when the segment added new vertices
|
|
3866
|
+
if (segment.length > 1) {
|
|
3867
|
+
addFeatureToEditedList(feature);
|
|
3868
|
+
}
|
|
3869
|
+
this._finishExtendDraw();
|
|
3870
|
+
this._syncExtendLineOverlays();
|
|
3871
|
+
}
|
|
3872
|
+
/**
|
|
3873
|
+
* Remove the active line extension draw and restore the interactions that
|
|
3874
|
+
* were disabled while drawing.
|
|
3875
|
+
*
|
|
3876
|
+
* @private
|
|
3877
|
+
*/
|
|
3878
|
+
_finishExtendDraw() {
|
|
3879
|
+
var _a;
|
|
3880
|
+
if (this._extendDraw) {
|
|
3881
|
+
this._map.removeInteraction(this._extendDraw);
|
|
3882
|
+
this._extendDraw = null;
|
|
3883
|
+
}
|
|
3884
|
+
if (getMode() !== Modes.Edit) {
|
|
3885
|
+
return;
|
|
3886
|
+
}
|
|
3887
|
+
(_a = this._interactionModify) === null || _a === void 0 ? void 0 : _a.setActive(true);
|
|
3888
|
+
if (this._interactionSelectModify) {
|
|
3889
|
+
this._interactionSelectModify.setActive(true);
|
|
3890
|
+
}
|
|
3891
|
+
if (this._interactionWfsSelect) {
|
|
3892
|
+
this._interactionWfsSelect.setActive(true);
|
|
3893
|
+
}
|
|
3894
|
+
}
|
|
3895
|
+
/**
|
|
3896
|
+
* Send all the given features in a single WFS-T Update transaction per
|
|
3897
|
+
* layer, so a multi selection is saved with only one request. The "edited"
|
|
3898
|
+
* flag is cleared before transacting, so a deselect fired while the
|
|
3899
|
+
* request is in flight does not trigger a duplicate transaction.
|
|
3900
|
+
*
|
|
3901
|
+
* @param list
|
|
3902
|
+
* @returns false when there is nothing to transact or the transaction fails
|
|
3903
|
+
* @private
|
|
3904
|
+
*/
|
|
3905
|
+
async _transactEditList(list) {
|
|
3906
|
+
if (!list.length) {
|
|
3907
|
+
return true;
|
|
3908
|
+
}
|
|
3909
|
+
const byLayer = {};
|
|
3910
|
+
list.forEach((feature) => {
|
|
3911
|
+
const layerName = feature.get('_layerName_');
|
|
3912
|
+
if (!byLayer[layerName]) {
|
|
3913
|
+
byLayer[layerName] = [];
|
|
3914
|
+
}
|
|
3915
|
+
byLayer[layerName].push(feature);
|
|
3916
|
+
});
|
|
3917
|
+
for (const layerName of Object.keys(byLayer)) {
|
|
3918
|
+
const layer = this._options.layers.find((layer) => layer.get(BaseLayerProperty.NAME) === layerName);
|
|
3919
|
+
if (!layer) {
|
|
3920
|
+
continue;
|
|
3921
|
+
}
|
|
3922
|
+
byLayer[layerName].forEach((feature) => {
|
|
3923
|
+
removeFeatureFromEditList(feature);
|
|
3924
|
+
});
|
|
3925
|
+
let ok;
|
|
3926
|
+
try {
|
|
3927
|
+
ok = await layer.transactFeatures(TransactionType.Update, byLayer[layerName]);
|
|
3928
|
+
}
|
|
3929
|
+
catch (err) {
|
|
3930
|
+
return false;
|
|
3931
|
+
}
|
|
3932
|
+
if (ok === false) {
|
|
3933
|
+
return false;
|
|
3934
|
+
}
|
|
3935
|
+
}
|
|
3936
|
+
return true;
|
|
2607
3937
|
}
|
|
2608
3938
|
/**
|
|
2609
|
-
* Remove
|
|
3939
|
+
* Remove features from the edit Layer and from the Geoserver
|
|
2610
3940
|
*
|
|
2611
3941
|
* @param feature
|
|
3942
|
+
* @param confirm
|
|
2612
3943
|
* @private
|
|
2613
3944
|
*/
|
|
2614
3945
|
_deleteFeature(feature, confirm) {
|
|
2615
3946
|
const deleteEl = () => {
|
|
3947
|
+
var _a;
|
|
2616
3948
|
const features = Array.isArray(feature) ? feature : [feature];
|
|
2617
3949
|
features.forEach((feature) => {
|
|
2618
3950
|
feature.set('_delete_', true, true);
|
|
2619
3951
|
getEditLayer().getSource().removeFeature(feature);
|
|
2620
3952
|
});
|
|
2621
3953
|
this._collectionModify.clear();
|
|
2622
|
-
|
|
2623
|
-
|
|
3954
|
+
this._editModeOff();
|
|
3955
|
+
this._syncEditOverlays();
|
|
3956
|
+
const layerName = (_a = features[0]) === null || _a === void 0 ? void 0 : _a.get('_layerName_');
|
|
3957
|
+
const layer = layerName
|
|
3958
|
+
? this._options.layers.find((layer) => layer.get(BaseLayerProperty.NAME) === layerName)
|
|
3959
|
+
: null;
|
|
2624
3960
|
if (layer instanceof WfsLayer) {
|
|
2625
|
-
|
|
3961
|
+
features.forEach((feature) => {
|
|
3962
|
+
this._interactionWfsSelect.getFeatures().remove(feature);
|
|
3963
|
+
});
|
|
2626
3964
|
}
|
|
2627
3965
|
};
|
|
2628
3966
|
if (confirm) {
|
|
2629
|
-
const
|
|
3967
|
+
const features = Array.isArray(feature) ? feature : [feature];
|
|
3968
|
+
const message = features.length > 1
|
|
3969
|
+
? I18N_('confirmDeleteElements', features.length)
|
|
3970
|
+
: I18N.labels.confirmDelete;
|
|
3971
|
+
const confirmModal = Modal.confirm(message, Object.assign({}, this._options.modal));
|
|
2630
3972
|
confirmModal.show().once('dismiss', function (modal, ev, button) {
|
|
2631
3973
|
if (button && button.value) {
|
|
2632
3974
|
deleteEl();
|
|
@@ -2646,33 +3988,102 @@ class Wfst extends Control {
|
|
|
2646
3988
|
* @private
|
|
2647
3989
|
*/
|
|
2648
3990
|
_addFeatureToEditMode(feature, coordinate = null, layerName = null) {
|
|
2649
|
-
// For now, only allow one element at time
|
|
2650
|
-
// @TODO: allow edit multiples elements
|
|
2651
|
-
if (this._collectionModify.getLength())
|
|
2652
|
-
return;
|
|
2653
3991
|
if (layerName) {
|
|
2654
3992
|
// Store the layer information inside the feature
|
|
2655
3993
|
feature.set('_layerName_', layerName);
|
|
2656
3994
|
}
|
|
3995
|
+
if (coordinate) {
|
|
3996
|
+
// Keep the click coordinate to position the overlay helper
|
|
3997
|
+
feature.set('_editOverlayCoord_', coordinate);
|
|
3998
|
+
}
|
|
2657
3999
|
const props = feature ? feature.getProperties() : '';
|
|
2658
4000
|
if (props) {
|
|
2659
4001
|
if (feature.getGeometry()) {
|
|
2660
4002
|
getEditLayer().getSource().addFeature(feature);
|
|
2661
4003
|
this._collectionModify.push(feature);
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
4004
|
+
this._syncEditOverlays();
|
|
4005
|
+
}
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
4008
|
+
/**
|
|
4009
|
+
* Lock the whole current selection with a single LockFeature request per
|
|
4010
|
+
* layer, so the transaction LockId covers all the selected features
|
|
4011
|
+
*
|
|
4012
|
+
* @private
|
|
4013
|
+
*/
|
|
4014
|
+
async _lockSelectedFeatures() {
|
|
4015
|
+
const byLayer = {};
|
|
4016
|
+
this._collectionModify.getArray().forEach((selected) => {
|
|
4017
|
+
const layerName = selected.get('_layerName_');
|
|
4018
|
+
if (!layerName) {
|
|
4019
|
+
return;
|
|
4020
|
+
}
|
|
4021
|
+
(byLayer[layerName] || (byLayer[layerName] = [])).push(selected.getId());
|
|
4022
|
+
});
|
|
4023
|
+
const locks = [];
|
|
4024
|
+
Object.keys(byLayer).forEach((layerName) => {
|
|
4025
|
+
const layer = getStoredLayer(layerName);
|
|
4026
|
+
if (layer) {
|
|
4027
|
+
locks.push(layer.maybeLockFeature(byLayer[layerName]));
|
|
4028
|
+
}
|
|
4029
|
+
});
|
|
4030
|
+
if (locks.length) {
|
|
4031
|
+
await Promise.all(locks);
|
|
4032
|
+
}
|
|
4033
|
+
}
|
|
4034
|
+
/**
|
|
4035
|
+
* Create an EditOverlay helper and attach the edit listeners
|
|
4036
|
+
*
|
|
4037
|
+
* @param feature
|
|
4038
|
+
* @param coordinate
|
|
4039
|
+
* @param id
|
|
4040
|
+
* @private
|
|
4041
|
+
*/
|
|
4042
|
+
_createEditOverlay(feature = null, coordinate = null, id = null) {
|
|
4043
|
+
coordinate =
|
|
4044
|
+
coordinate || (feature ? feature.get('_editOverlayCoord_') : null);
|
|
4045
|
+
const overlay = new EditOverlay(feature, coordinate, id);
|
|
4046
|
+
// @ts-expect-error
|
|
4047
|
+
overlay.on('editFields', () => {
|
|
4048
|
+
this._editFields.show(this._collectionModify.getArray());
|
|
4049
|
+
});
|
|
4050
|
+
// @ts-expect-error
|
|
4051
|
+
overlay.on('editGeom', () => {
|
|
4052
|
+
this._editModeOn(this._collectionModify.getArray());
|
|
4053
|
+
});
|
|
4054
|
+
this._map.addOverlay(overlay);
|
|
4055
|
+
}
|
|
4056
|
+
/**
|
|
4057
|
+
* Sync the edit overlay helpers with the current selection: a single
|
|
4058
|
+
* per-feature overlay while a feature is selected, or a unique overlay
|
|
4059
|
+
* centered between all the selected features when editing several at once
|
|
4060
|
+
*
|
|
4061
|
+
* @private
|
|
4062
|
+
*/
|
|
4063
|
+
_syncEditOverlays() {
|
|
4064
|
+
if (getMode() === Modes.Edit) {
|
|
4065
|
+
return;
|
|
4066
|
+
}
|
|
4067
|
+
const multiOverlay = this._map.getOverlayById(this._multiOverlayId);
|
|
4068
|
+
if (multiOverlay) {
|
|
4069
|
+
this._map.removeOverlay(multiOverlay);
|
|
4070
|
+
}
|
|
4071
|
+
const items = this._collectionModify.getArray();
|
|
4072
|
+
if (items.length > 1) {
|
|
4073
|
+
items.forEach((feature) => {
|
|
4074
|
+
this._removeOverlayHelper(feature);
|
|
4075
|
+
});
|
|
4076
|
+
const extent = createEmpty();
|
|
4077
|
+
items.forEach((feature) => {
|
|
4078
|
+
extend(extent, feature.getGeometry().getExtent());
|
|
4079
|
+
});
|
|
4080
|
+
this._createEditOverlay(null, getCenter(extent), this._multiOverlayId);
|
|
4081
|
+
}
|
|
4082
|
+
else if (items.length === 1) {
|
|
4083
|
+
const feature = items[0];
|
|
4084
|
+
const featureId = feature.getId();
|
|
4085
|
+
if (featureId && !this._map.getOverlayById(featureId)) {
|
|
4086
|
+
this._createEditOverlay(feature);
|
|
2676
4087
|
}
|
|
2677
4088
|
}
|
|
2678
4089
|
}
|
|
@@ -2719,15 +4130,20 @@ class Wfst extends Control {
|
|
|
2719
4130
|
if (!layer.getVisible()) {
|
|
2720
4131
|
return;
|
|
2721
4132
|
}
|
|
4133
|
+
// Clear hover style while drawing
|
|
4134
|
+
this._clearHoverState();
|
|
2722
4135
|
activateDrawButton();
|
|
4136
|
+
deactivateSelectModeButton();
|
|
2723
4137
|
this._viewport.classList.add('draw-mode');
|
|
2724
4138
|
addDrawInteraction(layer);
|
|
2725
4139
|
}
|
|
2726
4140
|
else {
|
|
2727
4141
|
this._map.removeInteraction(this._interactionDraw);
|
|
2728
4142
|
this._viewport.classList.remove('draw-mode');
|
|
4143
|
+
activateSelectModeButton(getSelectionMode());
|
|
2729
4144
|
}
|
|
2730
4145
|
activateMode(layer ? Modes.Draw : null);
|
|
4146
|
+
this._refreshSelectionInteractions();
|
|
2731
4147
|
}
|
|
2732
4148
|
/**
|
|
2733
4149
|
* Activate/desactivate the edit mode
|
|
@@ -2743,6 +4159,8 @@ class Wfst extends Control {
|
|
|
2743
4159
|
else {
|
|
2744
4160
|
// Deselct features
|
|
2745
4161
|
this._collectionModify.clear();
|
|
4162
|
+
// Clear hover state when leaving the edit mode
|
|
4163
|
+
this._clearHoverState();
|
|
2746
4164
|
}
|
|
2747
4165
|
if (this._interactionSelectModify) {
|
|
2748
4166
|
this._interactionSelectModify.setActive(bool);
|
|
@@ -2750,6 +4168,61 @@ class Wfst extends Control {
|
|
|
2750
4168
|
this._interactionModify.setActive(bool);
|
|
2751
4169
|
if (this._interactionWfsSelect)
|
|
2752
4170
|
this._interactionWfsSelect.setActive(bool);
|
|
4171
|
+
this._refreshSelectionInteractions();
|
|
4172
|
+
}
|
|
4173
|
+
/**
|
|
4174
|
+
* Reset the cursor and remove the hover style of the last hovered feature
|
|
4175
|
+
* @private
|
|
4176
|
+
*/
|
|
4177
|
+
_clearHoverState() {
|
|
4178
|
+
if (this._viewport) {
|
|
4179
|
+
this._viewport.style.cursor = '';
|
|
4180
|
+
}
|
|
4181
|
+
if (this._hoveredFeature) {
|
|
4182
|
+
this._hoveredFeature.setStyle(undefined);
|
|
4183
|
+
this._hoveredFeature = null;
|
|
4184
|
+
}
|
|
4185
|
+
}
|
|
4186
|
+
/**
|
|
4187
|
+
* Change the map cursor while in edit mode: `move` when the pointer is
|
|
4188
|
+
* over an editable vertex and `copy` when it is over a segment (where a
|
|
4189
|
+
* new vertex can be inserted), mirroring the Modify interaction hit
|
|
4190
|
+
* tolerance.
|
|
4191
|
+
*
|
|
4192
|
+
* @param evt
|
|
4193
|
+
* @private
|
|
4194
|
+
*/
|
|
4195
|
+
_updateEditCursor(evt) {
|
|
4196
|
+
if (!this._collectionModify) {
|
|
4197
|
+
return;
|
|
4198
|
+
}
|
|
4199
|
+
const tolerance = 12;
|
|
4200
|
+
const toleranceSq = tolerance * tolerance;
|
|
4201
|
+
const pixel = evt.pixel;
|
|
4202
|
+
for (const feature of this._collectionModify.getArray()) {
|
|
4203
|
+
const vertices = getFeatureVertices(feature);
|
|
4204
|
+
const coordinates = vertices ? vertices.getCoordinates() : [];
|
|
4205
|
+
if (!coordinates.length) {
|
|
4206
|
+
continue;
|
|
4207
|
+
}
|
|
4208
|
+
// Hovering an existing vertex (draggable handle)
|
|
4209
|
+
for (const coordinate of coordinates) {
|
|
4210
|
+
const vertexPixel = this._map.getPixelFromCoordinate(coordinate);
|
|
4211
|
+
if (squaredDistance(vertexPixel, pixel) <= toleranceSq) {
|
|
4212
|
+
this._viewport.style.cursor = 'move';
|
|
4213
|
+
return;
|
|
4214
|
+
}
|
|
4215
|
+
}
|
|
4216
|
+
// Hovering a segment (allows inserting a new vertex)
|
|
4217
|
+
for (let i = 0; i < coordinates.length - 1; i++) {
|
|
4218
|
+
const start = this._map.getPixelFromCoordinate(coordinates[i]);
|
|
4219
|
+
const end = this._map.getPixelFromCoordinate(coordinates[i + 1]);
|
|
4220
|
+
if (squaredDistanceToSegment(pixel, [start, end]) <= toleranceSq) {
|
|
4221
|
+
this._viewport.style.cursor = 'copy';
|
|
4222
|
+
return;
|
|
4223
|
+
}
|
|
4224
|
+
}
|
|
4225
|
+
}
|
|
2753
4226
|
}
|
|
2754
4227
|
/**
|
|
2755
4228
|
* Remove the overlay helper atttached to a specify feature
|