ol-wfst 4.4.0 → 5.1.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 +191 -175
- package/dist/@types.d.ts +15 -1
- 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 +8 -2
- package/dist/WfsLayer.d.ts.map +1 -1
- package/dist/WmsLayer.d.ts +32 -2
- 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 +563 -149
- 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 +23 -4
- 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 +8 -1
- 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 +251 -5
- package/dist/ol-wfst.d.ts.map +1 -1
- package/dist/ol-wfst.js +2008 -391
- 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 +15 -1
- 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 +8 -2
- package/lib/WfsLayer.d.ts.map +1 -1
- package/lib/WmsLayer.d.ts +32 -2
- 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 +23 -4
- 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 +8 -1
- 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 +251 -5
- package/lib/ol-wfst.d.ts.map +1 -1
- package/lib/ol-wfst.js +2013 -392
- package/lib/ol-wfst.js.map +1 -1
- package/lib/style/css/ol-gisify.css +563 -149
- 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 +728 -213
- 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 -
|
|
2
|
+
* ol-wfst - v5.1.0
|
|
3
3
|
* https://github.com/GastonZalba/ol-wfst#readme
|
|
4
|
-
* Built:
|
|
4
|
+
* Built: Thu Oct 08 2026 20:27:34 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,18 +87,23 @@ 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',
|
|
89
97
|
delete: 'Eliminar',
|
|
90
98
|
cancel: 'Cancelar',
|
|
91
|
-
apply: 'Aplicar cambios',
|
|
92
99
|
upload: 'Subir',
|
|
93
100
|
editMode: 'Modo Edición',
|
|
94
101
|
confirmDelete: '¿Estás seguro de borrar el elemento?',
|
|
102
|
+
confirmDeleteElements: '¿Borrar los {} elementos?',
|
|
95
103
|
geomTypeNotSupported: 'Geometría no compatible con la capa',
|
|
96
104
|
editFields: 'Editar campos',
|
|
97
105
|
editGeom: 'Editar geometría',
|
|
106
|
+
continueLine: 'Continuar dibujando desde aquí',
|
|
98
107
|
selectDrawType: 'Tipo de geometría para dibujar',
|
|
99
108
|
uploadToLayer: 'Subir archivo a la capa seleccionada',
|
|
100
109
|
uploadFeatures: 'Subida de elementos a la capa',
|
|
@@ -102,7 +111,16 @@ const es = {
|
|
|
102
111
|
invalidFeatures: 'Invalidas',
|
|
103
112
|
loading: 'Cargando...',
|
|
104
113
|
toggleVisibility: 'Cambiar visibilidad de la capa',
|
|
105
|
-
|
|
114
|
+
fullscreen: 'Pantalla completa',
|
|
115
|
+
close: 'Cerrar',
|
|
116
|
+
multipleValues: 'Valores múltiples',
|
|
117
|
+
editElements: 'Editar elementos ({})',
|
|
118
|
+
multipleEditNotice: 'Edición múltiple activada. Los cambios se aplicarán a los {} elementos seleccionados.',
|
|
119
|
+
tableMode: 'Modo tabla',
|
|
120
|
+
formMode: 'Modo formulario',
|
|
121
|
+
feature: 'Elemento',
|
|
122
|
+
geometry: 'Geometría',
|
|
123
|
+
geoserverLayers: 'Capas de GeoServer'
|
|
106
124
|
},
|
|
107
125
|
errors: {
|
|
108
126
|
capabilities: 'No se pudieron obtener las Capabilidades del GeoServer',
|
|
@@ -123,18 +141,23 @@ const es = {
|
|
|
123
141
|
const en = {
|
|
124
142
|
labels: {
|
|
125
143
|
select: 'Select',
|
|
144
|
+
selectBox: 'Select features by drawing a box',
|
|
145
|
+
selectFreehand: 'Select features by freehand drawing',
|
|
146
|
+
query: 'Query feature info',
|
|
147
|
+
featureInfo: 'Feature info',
|
|
126
148
|
addElement: 'Toggle Draw mode',
|
|
127
149
|
editElement: 'Edit feature',
|
|
128
150
|
save: 'Save',
|
|
129
151
|
delete: 'Delete',
|
|
130
152
|
cancel: 'Cancel',
|
|
131
|
-
apply: 'Apply changes',
|
|
132
153
|
upload: 'Upload',
|
|
133
154
|
editMode: 'Edit Mode',
|
|
134
155
|
confirmDelete: 'Are you sure to delete the feature?',
|
|
156
|
+
confirmDeleteElements: 'Delete the {} selected features?',
|
|
135
157
|
geomTypeNotSupported: 'Geometry not supported by layer',
|
|
136
158
|
editFields: 'Edit fields',
|
|
137
159
|
editGeom: 'Edit geometry',
|
|
160
|
+
continueLine: 'Continue drawing from here',
|
|
138
161
|
selectDrawType: 'Geometry type to draw',
|
|
139
162
|
uploadToLayer: 'Upload file to selected layer',
|
|
140
163
|
uploadFeatures: 'Uploaded features to layer',
|
|
@@ -142,7 +165,16 @@ const en = {
|
|
|
142
165
|
invalidFeatures: 'Invalid',
|
|
143
166
|
loading: 'Loading...',
|
|
144
167
|
toggleVisibility: 'Toggle layer visibility',
|
|
145
|
-
|
|
168
|
+
fullscreen: 'Fullscreen',
|
|
169
|
+
close: 'Close',
|
|
170
|
+
multipleValues: 'Multiple values',
|
|
171
|
+
editElements: 'Edit features ({})',
|
|
172
|
+
multipleEditNotice: 'Multi-edit active. Changes will be applied to the {} selected features.',
|
|
173
|
+
tableMode: 'Table mode',
|
|
174
|
+
formMode: 'Form mode',
|
|
175
|
+
feature: 'Feature',
|
|
176
|
+
geometry: 'Geometry',
|
|
177
|
+
geoserverLayers: 'Geoserver Layers'
|
|
146
178
|
},
|
|
147
179
|
errors: {
|
|
148
180
|
capabilities: 'GeoServer Capabilities could not be downloaded.',
|
|
@@ -163,18 +195,23 @@ const en = {
|
|
|
163
195
|
const zh = {
|
|
164
196
|
labels: {
|
|
165
197
|
select: '选择',
|
|
198
|
+
selectBox: '框选元素',
|
|
199
|
+
selectFreehand: '手绘选择元素',
|
|
200
|
+
query: '查询元素信息',
|
|
201
|
+
featureInfo: '元素信息',
|
|
166
202
|
addElement: '切换绘图类型',
|
|
167
203
|
editElement: '编辑元素',
|
|
168
204
|
save: '保存',
|
|
169
205
|
delete: '删除',
|
|
170
206
|
cancel: '取消',
|
|
171
|
-
apply: '确认并应用改变',
|
|
172
207
|
upload: '上传',
|
|
173
208
|
editMode: '编辑模式',
|
|
174
209
|
confirmDelete: '确认删除元素?',
|
|
210
|
+
confirmDeleteElements: '确认删除选中的 {} 个元素?',
|
|
175
211
|
geomTypeNotSupported: '图层不支持该几何',
|
|
176
212
|
editFields: '编辑区域',
|
|
177
213
|
editGeom: '编辑几何',
|
|
214
|
+
continueLine: '从此处继续绘制',
|
|
178
215
|
selectDrawType: '几何类型',
|
|
179
216
|
uploadToLayer: '通过文件上传图层',
|
|
180
217
|
uploadFeatures: '上传元素到图层',
|
|
@@ -182,7 +219,16 @@ const zh = {
|
|
|
182
219
|
invalidFeatures: '不合法',
|
|
183
220
|
loading: '加载中...',
|
|
184
221
|
toggleVisibility: '切换图层透明度',
|
|
185
|
-
|
|
222
|
+
fullscreen: '全屏',
|
|
223
|
+
close: '关闭',
|
|
224
|
+
multipleValues: '多个值',
|
|
225
|
+
editElements: '编辑元素({})',
|
|
226
|
+
multipleEditNotice: '多选编辑已激活。更改将应用于所有 {} 个选定元素。',
|
|
227
|
+
tableMode: '表格模式',
|
|
228
|
+
formMode: '表单模式',
|
|
229
|
+
feature: '要素',
|
|
230
|
+
geometry: '几何',
|
|
231
|
+
geoserverLayers: 'GeoServer 图层'
|
|
186
232
|
},
|
|
187
233
|
errors: {
|
|
188
234
|
capabilities: '无法加载GeoServer服务所支持的能力.',
|
|
@@ -217,12 +263,37 @@ const setLang = (lang = 'en', customI18n = null) => {
|
|
|
217
263
|
I18N = Object.assign(Object.assign({}, I18N), customI18n);
|
|
218
264
|
}
|
|
219
265
|
};
|
|
266
|
+
/**
|
|
267
|
+
* /**
|
|
268
|
+
* For translations thas has a variable "{}"" to be replaced inside
|
|
269
|
+
* @param string
|
|
270
|
+
* @param args
|
|
271
|
+
* @returns
|
|
272
|
+
*/
|
|
273
|
+
const I18N_ = (string, ...args) => {
|
|
274
|
+
var _a;
|
|
275
|
+
let text = (_a = I18N.labels) === null || _a === void 0 ? void 0 : _a[string];
|
|
276
|
+
if (!text) {
|
|
277
|
+
text = I18N[string];
|
|
278
|
+
}
|
|
279
|
+
if (!text) {
|
|
280
|
+
console.error('Translation not found', string);
|
|
281
|
+
text = string;
|
|
282
|
+
}
|
|
283
|
+
if (args.length) {
|
|
284
|
+
args.forEach((arg) => {
|
|
285
|
+
text = text.replace(/{}/, arg);
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
return text;
|
|
289
|
+
};
|
|
220
290
|
|
|
221
291
|
let loadingDiv;
|
|
222
292
|
const initLoading = () => {
|
|
223
293
|
loadingDiv = document.createElement('div');
|
|
224
294
|
loadingDiv.className = 'ol-wfst--tools-control--loading';
|
|
225
|
-
loadingDiv.
|
|
295
|
+
loadingDiv.setAttribute('role', 'progressbar');
|
|
296
|
+
loadingDiv.setAttribute('aria-label', I18N.labels.loading);
|
|
226
297
|
return loadingDiv;
|
|
227
298
|
};
|
|
228
299
|
const showLoading = (bool = true) => {
|
|
@@ -260,13 +331,27 @@ var Modes;
|
|
|
260
331
|
(function (Modes) {
|
|
261
332
|
Modes["Edit"] = "EDIT";
|
|
262
333
|
Modes["Draw"] = "DRAW";
|
|
334
|
+
Modes["Query"] = "QUERY";
|
|
263
335
|
})(Modes || (Modes = {}));
|
|
336
|
+
var SelectionMode;
|
|
337
|
+
(function (SelectionMode) {
|
|
338
|
+
SelectionMode["Single"] = "SINGLE";
|
|
339
|
+
SelectionMode["Box"] = "BOX";
|
|
340
|
+
SelectionMode["Freehand"] = "FREEHAND";
|
|
341
|
+
})(SelectionMode || (SelectionMode = {}));
|
|
342
|
+
let selectionMode = SelectionMode.Single;
|
|
264
343
|
function activateMode(m = null) {
|
|
265
344
|
mode = m;
|
|
266
345
|
}
|
|
267
346
|
function getMode() {
|
|
268
347
|
return mode;
|
|
269
348
|
}
|
|
349
|
+
function setSelectionMode(m) {
|
|
350
|
+
selectionMode = m;
|
|
351
|
+
}
|
|
352
|
+
function getSelectionMode() {
|
|
353
|
+
return selectionMode;
|
|
354
|
+
}
|
|
270
355
|
const editedFeatures = new Set();
|
|
271
356
|
const mapLayers = {};
|
|
272
357
|
function setMap(m) {
|
|
@@ -305,16 +390,23 @@ function isFeatureEdited(feature) {
|
|
|
305
390
|
*/
|
|
306
391
|
class BaseLayer extends Layer {
|
|
307
392
|
/**
|
|
393
|
+
* Initialize the layer: request DescribeFeatureType and resolve when it
|
|
394
|
+
* finishes (whether successful or not).
|
|
395
|
+
*
|
|
308
396
|
* @private
|
|
397
|
+
* @returns Promise that resolves when the layer is initialized
|
|
309
398
|
*/
|
|
310
|
-
_init() {
|
|
399
|
+
async _init() {
|
|
311
400
|
const geoserver = this.getGeoserver();
|
|
312
401
|
if (geoserver.isLoaded()) {
|
|
313
|
-
this.getAndUpdateDescribeFeatureType();
|
|
402
|
+
await this.getAndUpdateDescribeFeatureType();
|
|
314
403
|
}
|
|
315
404
|
else {
|
|
316
|
-
|
|
317
|
-
|
|
405
|
+
await new Promise((resolve) => {
|
|
406
|
+
geoserver.once('change:capabilities', async () => {
|
|
407
|
+
await this.getAndUpdateDescribeFeatureType();
|
|
408
|
+
resolve();
|
|
409
|
+
});
|
|
318
410
|
});
|
|
319
411
|
}
|
|
320
412
|
}
|
|
@@ -390,13 +482,13 @@ class BaseLayer extends Layer {
|
|
|
390
482
|
}
|
|
391
483
|
/**
|
|
392
484
|
* @public
|
|
393
|
-
* @param
|
|
485
|
+
* @param featureIds
|
|
394
486
|
* @returns
|
|
395
487
|
*/
|
|
396
|
-
async maybeLockFeature(
|
|
488
|
+
async maybeLockFeature(featureIds) {
|
|
397
489
|
const geoserver = this.getGeoserver();
|
|
398
490
|
if (geoserver.getUseLockFeature() && geoserver.hasLockFeature()) {
|
|
399
|
-
return await geoserver.lockFeature(
|
|
491
|
+
return await geoserver.lockFeature(featureIds, this.get(BaseLayerProperty.NAME));
|
|
400
492
|
}
|
|
401
493
|
return null;
|
|
402
494
|
}
|
|
@@ -435,44 +527,48 @@ var BaseLayerProperty;
|
|
|
435
527
|
*/
|
|
436
528
|
class WfsSource extends VectorSource {
|
|
437
529
|
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
|
-
|
|
530
|
+
super(Object.assign(Object.assign({}, options), { format: new GeoJSON(), loader: (extent, resolution, projection, success, failure) => {
|
|
531
|
+
void (async () => {
|
|
532
|
+
try {
|
|
533
|
+
// If bbox, add extent to the request
|
|
534
|
+
if (options.strategy == bbox) {
|
|
535
|
+
const extentGeoServer = transformExtent(extent, projection.getCode(), options.geoServerAdvanced.projection);
|
|
536
|
+
// https://docs.geoserver.org/stable/en/user/services/wfs/reference.html
|
|
537
|
+
// request features using a bounding box with CRS maybe different from featureTypes native CRS
|
|
538
|
+
this.urlParams.set('bbox', extentGeoServer.toString() +
|
|
539
|
+
`,${options.geoServerAdvanced.projection}`);
|
|
540
|
+
}
|
|
541
|
+
const url_fetch = options.geoserverUrl +
|
|
542
|
+
'?' +
|
|
543
|
+
this.urlParams.toString();
|
|
544
|
+
const response = await fetch(url_fetch, {
|
|
545
|
+
headers: options.headers,
|
|
546
|
+
credentials: options.credentials
|
|
547
|
+
});
|
|
548
|
+
if (!response.ok) {
|
|
549
|
+
throw new Error('');
|
|
550
|
+
}
|
|
551
|
+
const data = await response.json();
|
|
552
|
+
if (data.exceptions) {
|
|
553
|
+
throw new Error(parseError(data));
|
|
554
|
+
}
|
|
555
|
+
const features = this.getFormat().readFeatures(data, {
|
|
556
|
+
featureProjection: projection.getCode(),
|
|
557
|
+
dataProjection: options.geoServerAdvanced.projection
|
|
558
|
+
});
|
|
559
|
+
features.forEach((feature) => {
|
|
560
|
+
feature.set('_layerName_', options.name,
|
|
561
|
+
/* silent = */ true);
|
|
562
|
+
});
|
|
563
|
+
this.addFeatures(features);
|
|
564
|
+
success(features);
|
|
455
565
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
566
|
+
catch (err) {
|
|
567
|
+
this.removeLoadedExtent(extent);
|
|
568
|
+
showError(I18N.errors.geoserver, err, options.name);
|
|
569
|
+
failure();
|
|
459
570
|
}
|
|
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
|
-
}
|
|
571
|
+
})();
|
|
476
572
|
} }));
|
|
477
573
|
this.urlParams = new URLSearchParams({
|
|
478
574
|
SERVICE: 'wfs',
|
|
@@ -620,7 +716,7 @@ class WmsLayer extends Mixin(BaseLayer, (TileLayer)) {
|
|
|
620
716
|
this._loadingCount = 0;
|
|
621
717
|
this._loadedCount = 0;
|
|
622
718
|
/**
|
|
623
|
-
* Return the full accuracy
|
|
719
|
+
* Return the full accuracy geometries to replace the features from GetFeatureInfo
|
|
624
720
|
* @param featuresId
|
|
625
721
|
* @returns
|
|
626
722
|
*/
|
|
@@ -631,10 +727,10 @@ class WmsLayer extends Mixin(BaseLayer, (TileLayer)) {
|
|
|
631
727
|
INFO_FORMAT: 'application/json',
|
|
632
728
|
REQUEST: 'GetFeature',
|
|
633
729
|
TYPENAME: this.get('name'),
|
|
634
|
-
MAXFEATURES:
|
|
730
|
+
MAXFEATURES: String(featuresId.length),
|
|
635
731
|
OUTPUTFORMAT: 'application/json',
|
|
636
732
|
SRSNAME: getMap().getView().getProjection().getCode(),
|
|
637
|
-
FEATUREID:
|
|
733
|
+
FEATUREID: featuresId.join(',')
|
|
638
734
|
});
|
|
639
735
|
const url = this.getSource().getUrls()[0] + '?' + queryParams.toString();
|
|
640
736
|
try {
|
|
@@ -647,7 +743,11 @@ class WmsLayer extends Mixin(BaseLayer, (TileLayer)) {
|
|
|
647
743
|
throw new Error(`${I18N.errors.getFeatures} ${response.status}`);
|
|
648
744
|
}
|
|
649
745
|
const data = await response.json();
|
|
650
|
-
|
|
746
|
+
const fullById = {};
|
|
747
|
+
this._parseFeaturesFromResponse(data).forEach((feature) => {
|
|
748
|
+
fullById[String(feature.getId())] = feature;
|
|
749
|
+
});
|
|
750
|
+
return featuresId.map((id) => fullById[String(id)]).filter(Boolean);
|
|
651
751
|
}
|
|
652
752
|
catch (err) {
|
|
653
753
|
console.error(err);
|
|
@@ -715,6 +815,112 @@ class WmsLayer extends Mixin(BaseLayer, (TileLayer)) {
|
|
|
715
815
|
FEATURE_COUNT: 1,
|
|
716
816
|
EXCEPTIONS: 'application/json'
|
|
717
817
|
});
|
|
818
|
+
return this._requestFeatures(url);
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Request the WFS features that intersect the given geometry, selecting
|
|
822
|
+
* them with a WFS GetFeature + CQL INTERSECTS filter. Used to select
|
|
823
|
+
* features from a WMS layer with a box or a freehand lasso.
|
|
824
|
+
*
|
|
825
|
+
* @param geometry Geometry in the map view projection
|
|
826
|
+
* @returns
|
|
827
|
+
* @private
|
|
828
|
+
*/
|
|
829
|
+
async _getFeaturesInGeometry(geometry) {
|
|
830
|
+
var _a, _b, _c;
|
|
831
|
+
// Make sure the DescribeFeatureType is loaded to get the geometry
|
|
832
|
+
// field, waiting for it if the layer is still initializing
|
|
833
|
+
if (!this.getDescribeFeatureType()) {
|
|
834
|
+
await this.getAndUpdateDescribeFeatureType();
|
|
835
|
+
}
|
|
836
|
+
const geomField = (_b = (_a = this.getDescribeFeatureType()) === null || _a === void 0 ? void 0 : _a._parsed) === null || _b === void 0 ? void 0 : _b.geomField;
|
|
837
|
+
if (!geomField) {
|
|
838
|
+
showError(`${I18N.errors.layer} "${this.get('name')}"`);
|
|
839
|
+
return [];
|
|
840
|
+
}
|
|
841
|
+
const viewProj = getMap().getView().getProjection().getCode();
|
|
842
|
+
const nativeSrs = this._getNativeSrs();
|
|
843
|
+
// Work on a copy transformed to the layer native projection so the
|
|
844
|
+
// server evaluates the INTERSECTS filter against the proper SRID
|
|
845
|
+
const polygon = geometry.clone();
|
|
846
|
+
if (nativeSrs !== viewProj) {
|
|
847
|
+
try {
|
|
848
|
+
polygon.transform(viewProj, nativeSrs);
|
|
849
|
+
}
|
|
850
|
+
catch (err) {
|
|
851
|
+
console.error(err);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
const ring = polygon.getCoordinates()[0] || [];
|
|
855
|
+
const ringWkt = ring.map(([x, y]) => `${x} ${y}`).join(', ');
|
|
856
|
+
// Force the filter geometry CRS with the EWKT SRID prefix so the
|
|
857
|
+
// server evaluates the INTERSECTS against the layer projection
|
|
858
|
+
// instead of its native storage SRS
|
|
859
|
+
const sridMatch = (_c = nativeSrs.match(/(?:EPSG\s*::?\s*|EPSG\/0\/)(\d+)/i)) !== null && _c !== void 0 ? _c : nativeSrs.match(/(\d+)$/);
|
|
860
|
+
const cql = sridMatch
|
|
861
|
+
? `INTERSECTS(${geomField}, SRID=${sridMatch[1]};POLYGON((${ringWkt})))`
|
|
862
|
+
: `INTERSECTS(${geomField}, POLYGON((${ringWkt})))`;
|
|
863
|
+
const queryParams = new URLSearchParams({
|
|
864
|
+
SERVICE: 'wfs',
|
|
865
|
+
VERSION: '2.0.0',
|
|
866
|
+
REQUEST: 'GetFeature',
|
|
867
|
+
TYPENAME: this.get('name'),
|
|
868
|
+
OUTPUTFORMAT: 'application/json',
|
|
869
|
+
SRSNAME: viewProj,
|
|
870
|
+
CQL_FILTER: cql
|
|
871
|
+
});
|
|
872
|
+
const url = this.getSource().getUrls()[0] + '?' + queryParams.toString();
|
|
873
|
+
// The server returns the features in the map view projection (same as
|
|
874
|
+
// the full resolution request), so no further transformation is needed
|
|
875
|
+
return this._requestFeatures(url);
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* Resolve the layer native SRS so spatial filters are evaluated in the
|
|
879
|
+
* correct projection. It reads the DefaultCRS from the WFS capabilities,
|
|
880
|
+
* falling back to the geoserver advanced projection option and finally to
|
|
881
|
+
* the map view projection.
|
|
882
|
+
*
|
|
883
|
+
* @returns
|
|
884
|
+
* @private
|
|
885
|
+
*/
|
|
886
|
+
_getNativeSrs() {
|
|
887
|
+
var _a;
|
|
888
|
+
const geoserver = this.getGeoserver();
|
|
889
|
+
const viewProj = getMap().getView().getProjection().getCode();
|
|
890
|
+
const layerName = String(this.get('name') || '');
|
|
891
|
+
const layerLocalName = layerName.split(':').pop();
|
|
892
|
+
const featureTypeList = (_a = geoserver.getParsedCapabilities()) === null || _a === void 0 ? void 0 : _a.FeatureTypeList;
|
|
893
|
+
const featureType = Array.isArray(featureTypeList)
|
|
894
|
+
? featureTypeList.find((ft) => {
|
|
895
|
+
const featureTypeName = String((ft === null || ft === void 0 ? void 0 : ft.Name) || '');
|
|
896
|
+
return (featureTypeName === layerName ||
|
|
897
|
+
featureTypeName === layerLocalName ||
|
|
898
|
+
featureTypeName.split(':').pop() === layerLocalName);
|
|
899
|
+
})
|
|
900
|
+
: undefined;
|
|
901
|
+
const defaultCrs = (featureType === null || featureType === void 0 ? void 0 : featureType.DefaultCRS)
|
|
902
|
+
? String(featureType.DefaultCRS)
|
|
903
|
+
: '';
|
|
904
|
+
const epsgMatch = defaultCrs.match(/(?:EPSG\s*::?\s*|EPSG\/0\/)(\d+)/i);
|
|
905
|
+
if (epsgMatch) {
|
|
906
|
+
return `EPSG:${epsgMatch[1]}`;
|
|
907
|
+
}
|
|
908
|
+
const advanced = geoserver.getAdvanced().projection;
|
|
909
|
+
if (advanced) {
|
|
910
|
+
return String(advanced);
|
|
911
|
+
}
|
|
912
|
+
return viewProj;
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* Fetch and parse the features from a request url, replacing the
|
|
916
|
+
* low resolution geometries (GetFeatureInfo) with the full resolution
|
|
917
|
+
* ones requested by FEATUREID when possible.
|
|
918
|
+
*
|
|
919
|
+
* @param url
|
|
920
|
+
* @returns
|
|
921
|
+
* @private
|
|
922
|
+
*/
|
|
923
|
+
async _requestFeatures(url) {
|
|
718
924
|
const geoserver = this.getGeoserver();
|
|
719
925
|
try {
|
|
720
926
|
const response = await fetch(url, {
|
|
@@ -728,7 +934,7 @@ class WmsLayer extends Mixin(BaseLayer, (TileLayer)) {
|
|
|
728
934
|
let features = this._parseFeaturesFromResponse(data);
|
|
729
935
|
const featuresId = features.map((f) => f.getId());
|
|
730
936
|
if (!featuresId.length) {
|
|
731
|
-
return;
|
|
937
|
+
return [];
|
|
732
938
|
}
|
|
733
939
|
const fullResList = await this._getFullResGeometryById(featuresId);
|
|
734
940
|
if (fullResList) {
|
|
@@ -779,50 +985,95 @@ class WmsLayer extends Mixin(BaseLayer, (TileLayer)) {
|
|
|
779
985
|
}
|
|
780
986
|
}
|
|
781
987
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
return tagName(attrs, children);
|
|
793
|
-
const elem = tagName === null
|
|
794
|
-
? new DocumentFragment()
|
|
988
|
+
function createElement(tagName, props) {
|
|
989
|
+
props = props || {};
|
|
990
|
+
let children = props.children || [];
|
|
991
|
+
children = Array.isArray(children) ? children : [children];
|
|
992
|
+
delete props.children;
|
|
993
|
+
if (typeof tagName === 'function') {
|
|
994
|
+
return tagName(props, children);
|
|
995
|
+
}
|
|
996
|
+
const elem = tagName === 'fragment'
|
|
997
|
+
? document.createDocumentFragment()
|
|
795
998
|
: document.createElement(tagName);
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
value
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
999
|
+
if (elem instanceof HTMLElement) {
|
|
1000
|
+
Object.entries(props).forEach(([name, value]) => {
|
|
1001
|
+
if (value != null) {
|
|
1002
|
+
// listener
|
|
1003
|
+
if (name.startsWith('on') && typeof value === 'function') {
|
|
1004
|
+
elem.addEventListener(name
|
|
1005
|
+
.slice(2)
|
|
1006
|
+
.toLowerCase(), value);
|
|
1007
|
+
}
|
|
1008
|
+
else if (name === 'className') {
|
|
804
1009
|
elem.setAttribute('class', value.toString());
|
|
805
|
-
|
|
1010
|
+
}
|
|
1011
|
+
else if (name === 'htmlFor') {
|
|
806
1012
|
elem.setAttribute('for', value.toString());
|
|
807
|
-
|
|
1013
|
+
}
|
|
1014
|
+
else {
|
|
808
1015
|
elem.setAttribute(name, value.toString());
|
|
1016
|
+
}
|
|
809
1017
|
}
|
|
810
|
-
}
|
|
811
|
-
}
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
812
1020
|
for (const child of children) {
|
|
813
|
-
if (!child)
|
|
1021
|
+
if (!child) {
|
|
814
1022
|
continue;
|
|
815
|
-
|
|
1023
|
+
}
|
|
1024
|
+
if (Array.isArray(child)) {
|
|
816
1025
|
elem.append(...child);
|
|
1026
|
+
}
|
|
1027
|
+
else if (child instanceof Node) {
|
|
1028
|
+
elem.appendChild(child);
|
|
1029
|
+
}
|
|
817
1030
|
else {
|
|
818
|
-
|
|
819
|
-
elem.innerHTML += child;
|
|
820
|
-
else
|
|
821
|
-
elem.appendChild(child);
|
|
1031
|
+
elem.innerHTML += child;
|
|
822
1032
|
}
|
|
823
1033
|
}
|
|
824
1034
|
return elem;
|
|
825
1035
|
}
|
|
1036
|
+
function jsx(type, props) {
|
|
1037
|
+
return createElement(type, props);
|
|
1038
|
+
}
|
|
1039
|
+
function jsxs(type, props) {
|
|
1040
|
+
return createElement(type, props);
|
|
1041
|
+
}
|
|
1042
|
+
function Fragment(type, children) {
|
|
1043
|
+
return createElement('fragment', { children });
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
function uploadSvg() {
|
|
1047
|
+
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;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
function drawSvg() {
|
|
1051
|
+
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;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
function selectSvg() {
|
|
1055
|
+
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;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
function selectBoxSvg() {
|
|
1059
|
+
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;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
function selectFreehandSvg() {
|
|
1063
|
+
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;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
function infoSvg() {
|
|
1067
|
+
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;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
function visibilityOnSvg() {
|
|
1071
|
+
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;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
function visibilityOffSvg() {
|
|
1075
|
+
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;
|
|
1076
|
+
}
|
|
826
1077
|
|
|
827
1078
|
/**
|
|
828
1079
|
* Removes in the DOM the class of the tools
|
|
@@ -839,6 +1090,19 @@ const activateModeButtons = () => {
|
|
|
839
1090
|
if (btn) {
|
|
840
1091
|
btn.classList.add('wfst--active');
|
|
841
1092
|
}
|
|
1093
|
+
deactivateQueryButton();
|
|
1094
|
+
};
|
|
1095
|
+
const activateQueryButton = () => {
|
|
1096
|
+
const btn = document.querySelector('.ol-wfst--tools-control-btn-query');
|
|
1097
|
+
if (btn) {
|
|
1098
|
+
btn.classList.add('wfst--active');
|
|
1099
|
+
}
|
|
1100
|
+
};
|
|
1101
|
+
const deactivateQueryButton = () => {
|
|
1102
|
+
const btn = document.querySelector('.ol-wfst--tools-control-btn-query');
|
|
1103
|
+
if (btn) {
|
|
1104
|
+
btn.classList.remove('wfst--active');
|
|
1105
|
+
}
|
|
842
1106
|
};
|
|
843
1107
|
const activateDrawButton = () => {
|
|
844
1108
|
const btn = document.querySelector('.ol-wfst--tools-control-btn-draw');
|
|
@@ -846,6 +1110,29 @@ const activateDrawButton = () => {
|
|
|
846
1110
|
btn.classList.add('wfst--active');
|
|
847
1111
|
}
|
|
848
1112
|
};
|
|
1113
|
+
const activateSelectModeButton = (mode) => {
|
|
1114
|
+
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');
|
|
1115
|
+
if (activeBtn) {
|
|
1116
|
+
activeBtn.classList.remove('wfst--active');
|
|
1117
|
+
}
|
|
1118
|
+
deactivateQueryButton();
|
|
1119
|
+
const btnMap = {
|
|
1120
|
+
[SelectionMode.Single]: '.ol-wfst--tools-control-btn-select',
|
|
1121
|
+
[SelectionMode.Box]: '.ol-wfst--tools-control-btn-select-box',
|
|
1122
|
+
[SelectionMode.Freehand]: '.ol-wfst--tools-control-btn-select-freehand'
|
|
1123
|
+
};
|
|
1124
|
+
const btn = document.querySelector(btnMap[mode]);
|
|
1125
|
+
if (btn) {
|
|
1126
|
+
btn.classList.add('wfst--active');
|
|
1127
|
+
}
|
|
1128
|
+
};
|
|
1129
|
+
const deactivateSelectModeButton = () => {
|
|
1130
|
+
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');
|
|
1131
|
+
if (activeBtn) {
|
|
1132
|
+
activeBtn.classList.remove('wfst--active');
|
|
1133
|
+
}
|
|
1134
|
+
deactivateQueryButton();
|
|
1135
|
+
};
|
|
849
1136
|
class LayersControl extends Observable {
|
|
850
1137
|
constructor(uploads, uploadFormats) {
|
|
851
1138
|
super();
|
|
@@ -858,23 +1145,15 @@ class LayersControl extends Observable {
|
|
|
858
1145
|
* @public
|
|
859
1146
|
*/
|
|
860
1147
|
addLayerEl(layer) {
|
|
861
|
-
const container = document.querySelector('.wfst--tools-control--select-layers');
|
|
1148
|
+
const container = document.querySelector('.wfst--tools-control--select-layers-list');
|
|
862
1149
|
const layerName = layer.get(BaseLayerProperty.NAME);
|
|
863
1150
|
const checked = layer === getActiveLayerToInsertEls() ? { checked: true } : {};
|
|
864
|
-
const input = (
|
|
865
|
-
const layerDom = (
|
|
1151
|
+
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) })));
|
|
1152
|
+
const layerDom = (jsx("div", { className: `wfst--layer-control
|
|
866
1153
|
${layer.getVisible() ? 'ol-wfst--visible-on' : ''}
|
|
867
1154
|
${layer === getActiveLayerToInsertEls()
|
|
868
1155
|
? 'ol-wfst--selected-on'
|
|
869
|
-
: ''}`, "data-layer": layerName },
|
|
870
|
-
createElement("div", { className: "ol-wfst--tools-control-visible" },
|
|
871
|
-
createElement("span", { className: "ol-wfst--tools-control-visible-btn ol-wfst--visible-btn-on", title: I18N.labels.toggleVisibility, onClick: (evt) => this._visibilityClickHandler(evt) },
|
|
872
|
-
createElement("img", { src: img$3 })),
|
|
873
|
-
createElement("span", { className: "ol-wfst--tools-control-visible-btn ol-wfst--visible-btn-off", title: I18N.labels.toggleVisibility, onClick: (evt) => this._visibilityClickHandler(evt) },
|
|
874
|
-
createElement("img", { src: img$2 }))),
|
|
875
|
-
createElement("label", { htmlFor: `wfst--${layerName}` },
|
|
876
|
-
input,
|
|
877
|
-
createElement("span", { title: layer.getDescribeFeatureType()._parsed.geomType }, layer.get(BaseLayerProperty.LABEL)))));
|
|
1156
|
+
: ''}`, "data-layer": layerName, children: jsxs("label", { htmlFor: `wfst--${layerName}`, children: [jsxs("div", { className: "ol-wfst--layer-control-visible ", children: [jsx("span", { className: "ol-wfst--layer-control-visible -btn ol-wfst--visible-btn-on", title: I18N.labels.toggleVisibility, onClick: (evt) => this._visibilityClickHandler(evt), children: visibilityOnSvg() }), jsx("span", { className: "ol-wfst--layer-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) })] }) }));
|
|
878
1157
|
container.appendChild(layerDom);
|
|
879
1158
|
if (layer === getActiveLayerToInsertEls()) {
|
|
880
1159
|
input.dispatchEvent(new Event('change'));
|
|
@@ -970,34 +1249,32 @@ class LayersControl extends Observable {
|
|
|
970
1249
|
this.dispatchEvent('changeLayer');
|
|
971
1250
|
}
|
|
972
1251
|
render() {
|
|
973
|
-
return (
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
})))),
|
|
1000
|
-
createElement("div", { className: "wfst--tools-control--select-layers" })));
|
|
1252
|
+
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: () => {
|
|
1253
|
+
this.dispatchEvent('selectModeSingle');
|
|
1254
|
+
}, children: selectSvg() }), jsx("button", { className: "ol-wfst--tools-control-btn ol-wfst--tools-control-btn-select-box", type: "button", title: I18N.labels.selectBox, onClick: () => {
|
|
1255
|
+
this.dispatchEvent('selectModeBox');
|
|
1256
|
+
}, children: selectBoxSvg() }), jsx("button", { className: "ol-wfst--tools-control-btn ol-wfst--tools-control-btn-select-freehand", type: "button", title: I18N.labels.selectFreehand, onClick: () => {
|
|
1257
|
+
this.dispatchEvent('selectModeFreehand');
|
|
1258
|
+
}, 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: () => {
|
|
1259
|
+
this.dispatchEvent('queryMode');
|
|
1260
|
+
}, 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: () => {
|
|
1261
|
+
this.dispatchEvent('drawMode');
|
|
1262
|
+
}, children: drawSvg() }), jsx("select", { title: I18N.labels.selectDrawType, className: "wfst--tools-control--select-draw", onChange: (evt) => {
|
|
1263
|
+
const selectedValue = evt.target.value;
|
|
1264
|
+
this._changeStateSelect(getActiveLayerToInsertEls(), selectedValue);
|
|
1265
|
+
this.dispatchEvent('changeGeom');
|
|
1266
|
+
}, children: [
|
|
1267
|
+
GeometryType.Point,
|
|
1268
|
+
GeometryType.MultiPoint,
|
|
1269
|
+
GeometryType.LineString,
|
|
1270
|
+
GeometryType.MultiLineString,
|
|
1271
|
+
GeometryType.Polygon,
|
|
1272
|
+
GeometryType.MultiPolygon,
|
|
1273
|
+
GeometryType.Circle
|
|
1274
|
+
].map((type) => {
|
|
1275
|
+
// Show all options, but enable only the accepted ones
|
|
1276
|
+
return jsx("option", { value: type, children: type });
|
|
1277
|
+
}) })] }), 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" })] })] }));
|
|
1001
1278
|
}
|
|
1002
1279
|
}
|
|
1003
1280
|
|
|
@@ -1236,6 +1513,7 @@ const getDefaultOptions = () => {
|
|
|
1236
1513
|
evtType: 'singleclick',
|
|
1237
1514
|
active: true,
|
|
1238
1515
|
showControl: true,
|
|
1516
|
+
fullscreen: true,
|
|
1239
1517
|
language: DEFAULT_LANGUAGE,
|
|
1240
1518
|
uploadFormats: '.geojson,.json,.kml',
|
|
1241
1519
|
processUpload: null,
|
|
@@ -1253,91 +1531,145 @@ const getDefaultOptions = () => {
|
|
|
1253
1531
|
};
|
|
1254
1532
|
|
|
1255
1533
|
class EditControlChangesEl extends Control {
|
|
1256
|
-
constructor(
|
|
1534
|
+
constructor(features) {
|
|
1257
1535
|
super({
|
|
1258
|
-
element: (
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
} }, I18N.labels.cancel),
|
|
1268
|
-
createElement("button", { type: "button", className: "btn btn-sm btn-primary", onClick: () => {
|
|
1269
|
-
this.dispatchEvent(new VectorSourceEvent('apply', feature));
|
|
1270
|
-
} }, I18N.labels.apply),
|
|
1271
|
-
createElement("button", { type: "button", className: "btn btn-sm btn-danger-outline", onClick: () => {
|
|
1272
|
-
this.dispatchEvent(new VectorSourceEvent('delete', feature));
|
|
1273
|
-
} }, I18N.labels.delete))))
|
|
1536
|
+
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
|
|
1537
|
+
? I18N_('editElements', features.length)
|
|
1538
|
+
: String(features[0].getId()) })] }), jsx("button", { type: "button", className: "btn btn-sm btn-danger", onClick: () => {
|
|
1539
|
+
this._dispatch('delete', features);
|
|
1540
|
+
}, children: I18N.labels.delete }), jsx("button", { type: "button", className: "btn btn-sm btn-secondary", onClick: () => {
|
|
1541
|
+
this._dispatch('cancel', features);
|
|
1542
|
+
}, children: I18N.labels.cancel }), jsx("button", { type: "button", className: "btn btn-sm btn-primary", onClick: () => {
|
|
1543
|
+
this._dispatch('apply', features);
|
|
1544
|
+
}, children: I18N.labels.save })] }) }))
|
|
1274
1545
|
});
|
|
1275
1546
|
}
|
|
1547
|
+
_dispatch(type, features) {
|
|
1548
|
+
const evt = new VectorSourceEvent(type, features[0]);
|
|
1549
|
+
evt.features = features;
|
|
1550
|
+
this.dispatchEvent(evt);
|
|
1551
|
+
}
|
|
1276
1552
|
}
|
|
1277
1553
|
|
|
1278
1554
|
// Ol
|
|
1555
|
+
/**
|
|
1556
|
+
* Modern vertex handle mimicking the round "extend line" buttons shown at
|
|
1557
|
+
* line endpoints: a soft halo for depth and a white disc with a subtle
|
|
1558
|
+
* border. When `highlighted` is `true` (e.g. hovering a vertex with the
|
|
1559
|
+
* Modify interaction) the handle gets bigger with a stronger halo and a
|
|
1560
|
+
* darker border.
|
|
1561
|
+
*
|
|
1562
|
+
* @param highlighted Whether to render the bigger/highlighted state
|
|
1563
|
+
* @param geometry Style geometry function to position the handles
|
|
1564
|
+
* @private
|
|
1565
|
+
*/
|
|
1566
|
+
const editVertexStyles = (highlighted = false, geometry) => {
|
|
1567
|
+
const halo = highlighted ? 10 : 8;
|
|
1568
|
+
const haloColor = highlighted
|
|
1569
|
+
? 'rgba(0, 0, 0, 0.40)'
|
|
1570
|
+
: 'rgba(0, 0, 0, 0.28)';
|
|
1571
|
+
const radius = highlighted ? 7 : 6;
|
|
1572
|
+
const border = highlighted ? '#4b5563' : '#b3b3b3';
|
|
1573
|
+
const borderWidth = highlighted ? 2 : 1.5;
|
|
1574
|
+
return [
|
|
1575
|
+
new Style(Object.assign({ image: new Circle({
|
|
1576
|
+
radius: halo,
|
|
1577
|
+
fill: new Fill({
|
|
1578
|
+
color: haloColor
|
|
1579
|
+
})
|
|
1580
|
+
}) }, (geometry ? { geometry } : {}))),
|
|
1581
|
+
new Style(Object.assign({ image: new Circle({
|
|
1582
|
+
radius: radius,
|
|
1583
|
+
fill: new Fill({
|
|
1584
|
+
color: '#ffffff'
|
|
1585
|
+
}),
|
|
1586
|
+
stroke: new Stroke({
|
|
1587
|
+
width: borderWidth,
|
|
1588
|
+
color: border
|
|
1589
|
+
})
|
|
1590
|
+
}) }, (geometry ? { geometry } : {})))
|
|
1591
|
+
];
|
|
1592
|
+
};
|
|
1593
|
+
/**
|
|
1594
|
+
* Extract a `MultiPoint` with every vertex of the given feature geometry
|
|
1595
|
+
* (polygons and multi-line strings are flattened). Returns `undefined` when
|
|
1596
|
+
* the geometry has no coordinates.
|
|
1597
|
+
*
|
|
1598
|
+
* @param feature
|
|
1599
|
+
* @private
|
|
1600
|
+
*/
|
|
1601
|
+
const getFeatureVertices = (feature) => {
|
|
1602
|
+
let geometry = feature.getGeometry();
|
|
1603
|
+
if (geometry instanceof GeometryCollection) {
|
|
1604
|
+
geometry = geometry.getGeometries()[0];
|
|
1605
|
+
}
|
|
1606
|
+
const coordinates = geometry.getCoordinates();
|
|
1607
|
+
let flatCoordinates = null;
|
|
1608
|
+
if (geometry instanceof Polygon || geometry instanceof MultiLineString) {
|
|
1609
|
+
flatCoordinates = coordinates.flat(1);
|
|
1610
|
+
}
|
|
1611
|
+
else if (geometry instanceof MultiPolygon) {
|
|
1612
|
+
flatCoordinates = coordinates.flat(2);
|
|
1613
|
+
}
|
|
1614
|
+
else {
|
|
1615
|
+
flatCoordinates = coordinates;
|
|
1616
|
+
}
|
|
1617
|
+
if (!flatCoordinates || !flatCoordinates.length) {
|
|
1618
|
+
return;
|
|
1619
|
+
}
|
|
1620
|
+
return new MultiPoint(flatCoordinates);
|
|
1621
|
+
};
|
|
1279
1622
|
/**
|
|
1280
1623
|
* Master style that handles two modes on the Edit Layer:
|
|
1281
1624
|
* - one is the basic, showing only the vertices
|
|
1282
1625
|
* - and the other when modify is active, showing bigger vertices
|
|
1283
1626
|
*
|
|
1627
|
+
* When `hovered` is `true`, a white/red halo is prepended to highlight the
|
|
1628
|
+
* feature, indicating it can be selected on click.
|
|
1629
|
+
*
|
|
1284
1630
|
* @param feature
|
|
1631
|
+
* @param hovered
|
|
1285
1632
|
* @private
|
|
1286
1633
|
*/
|
|
1287
|
-
function styleFunction(feature) {
|
|
1288
|
-
const getVertexs = (feature) => {
|
|
1289
|
-
let geometry = feature.getGeometry();
|
|
1290
|
-
if (geometry instanceof GeometryCollection) {
|
|
1291
|
-
geometry = geometry.getGeometries()[0];
|
|
1292
|
-
}
|
|
1293
|
-
const coordinates = geometry.getCoordinates();
|
|
1294
|
-
let flatCoordinates = null;
|
|
1295
|
-
if (geometry instanceof Polygon ||
|
|
1296
|
-
geometry instanceof MultiLineString) {
|
|
1297
|
-
flatCoordinates = coordinates.flat(1);
|
|
1298
|
-
}
|
|
1299
|
-
else if (geometry instanceof MultiPolygon) {
|
|
1300
|
-
flatCoordinates = coordinates.flat(2);
|
|
1301
|
-
}
|
|
1302
|
-
else {
|
|
1303
|
-
flatCoordinates = coordinates;
|
|
1304
|
-
}
|
|
1305
|
-
if (!flatCoordinates || !flatCoordinates.length) {
|
|
1306
|
-
return;
|
|
1307
|
-
}
|
|
1308
|
-
return new MultiPoint(flatCoordinates);
|
|
1309
|
-
};
|
|
1634
|
+
function styleFunction(feature, hovered = false) {
|
|
1310
1635
|
let geometry = feature.getGeometry();
|
|
1311
1636
|
let type = geometry.getType();
|
|
1312
1637
|
if (geometry instanceof GeometryCollection) {
|
|
1313
1638
|
geometry = geometry.getGeometries()[0];
|
|
1314
1639
|
type = geometry.getType();
|
|
1315
1640
|
}
|
|
1641
|
+
const haloStyle = () => {
|
|
1642
|
+
return [
|
|
1643
|
+
new Style({
|
|
1644
|
+
image: new Circle({
|
|
1645
|
+
radius: 9,
|
|
1646
|
+
fill: new Fill({
|
|
1647
|
+
color: 'rgba(255, 255, 255, 0.9)'
|
|
1648
|
+
}),
|
|
1649
|
+
stroke: new Stroke({
|
|
1650
|
+
color: '#ff0000',
|
|
1651
|
+
width: 3
|
|
1652
|
+
})
|
|
1653
|
+
}),
|
|
1654
|
+
stroke: new Stroke({
|
|
1655
|
+
color: 'rgba(255, 255, 255, 0.9)',
|
|
1656
|
+
width: 8
|
|
1657
|
+
}),
|
|
1658
|
+
fill: new Fill({
|
|
1659
|
+
color: 'rgba(255, 255, 255, 0.9)'
|
|
1660
|
+
})
|
|
1661
|
+
})
|
|
1662
|
+
];
|
|
1663
|
+
};
|
|
1316
1664
|
switch (type) {
|
|
1317
1665
|
case GeometryType.Point:
|
|
1318
1666
|
case GeometryType.MultiPoint:
|
|
1319
1667
|
if (getMode() === Modes.Edit) {
|
|
1320
|
-
return
|
|
1321
|
-
new Style({
|
|
1322
|
-
image: new Circle({
|
|
1323
|
-
radius: 6,
|
|
1324
|
-
fill: new Fill({
|
|
1325
|
-
color: '#000000'
|
|
1326
|
-
})
|
|
1327
|
-
})
|
|
1328
|
-
}),
|
|
1329
|
-
new Style({
|
|
1330
|
-
image: new Circle({
|
|
1331
|
-
radius: 4,
|
|
1332
|
-
fill: new Fill({
|
|
1333
|
-
color: '#ff0000'
|
|
1334
|
-
})
|
|
1335
|
-
})
|
|
1336
|
-
})
|
|
1337
|
-
];
|
|
1668
|
+
return editVertexStyles();
|
|
1338
1669
|
}
|
|
1339
1670
|
else {
|
|
1340
1671
|
return [
|
|
1672
|
+
...(hovered ? haloStyle() : []),
|
|
1341
1673
|
new Style({
|
|
1342
1674
|
image: new Circle({
|
|
1343
1675
|
radius: 5,
|
|
@@ -1369,19 +1701,7 @@ function styleFunction(feature) {
|
|
|
1369
1701
|
color: 'rgba(255, 0, 0, 0.7)'
|
|
1370
1702
|
})
|
|
1371
1703
|
}),
|
|
1372
|
-
|
|
1373
|
-
image: new Circle({
|
|
1374
|
-
radius: 4,
|
|
1375
|
-
fill: new Fill({
|
|
1376
|
-
color: '#ff0000'
|
|
1377
|
-
}),
|
|
1378
|
-
stroke: new Stroke({
|
|
1379
|
-
width: 2,
|
|
1380
|
-
color: 'rgba(5, 5, 5, 0.9)'
|
|
1381
|
-
})
|
|
1382
|
-
}),
|
|
1383
|
-
geometry: (feature) => getVertexs(feature)
|
|
1384
|
-
}),
|
|
1704
|
+
...editVertexStyles(false, getFeatureVertices),
|
|
1385
1705
|
new Style({
|
|
1386
1706
|
stroke: new Stroke({
|
|
1387
1707
|
color: 'rgba(255, 255, 255, 0.7)',
|
|
@@ -1392,6 +1712,7 @@ function styleFunction(feature) {
|
|
|
1392
1712
|
}
|
|
1393
1713
|
else {
|
|
1394
1714
|
return [
|
|
1715
|
+
...(hovered ? haloStyle() : []),
|
|
1395
1716
|
new Style({
|
|
1396
1717
|
image: new Circle({
|
|
1397
1718
|
radius: 2,
|
|
@@ -1399,7 +1720,7 @@ function styleFunction(feature) {
|
|
|
1399
1720
|
color: '#000000'
|
|
1400
1721
|
})
|
|
1401
1722
|
}),
|
|
1402
|
-
geometry: (feature) =>
|
|
1723
|
+
geometry: (feature) => getFeatureVertices(feature)
|
|
1403
1724
|
}),
|
|
1404
1725
|
new Style({
|
|
1405
1726
|
stroke: new Stroke({
|
|
@@ -1414,22 +1735,67 @@ function styleFunction(feature) {
|
|
|
1414
1735
|
}
|
|
1415
1736
|
}
|
|
1416
1737
|
}
|
|
1417
|
-
|
|
1418
|
-
// Ol
|
|
1419
1738
|
/**
|
|
1420
|
-
*
|
|
1739
|
+
* Style used to highlight a feature after a query, drawn on a dedicated
|
|
1740
|
+
* highlight layer so the source layers keep their own rendering.
|
|
1421
1741
|
*
|
|
1422
1742
|
* @param feature
|
|
1423
1743
|
* @private
|
|
1424
1744
|
*/
|
|
1745
|
+
function queryStyleFunction(_feature) {
|
|
1746
|
+
return [
|
|
1747
|
+
new Style({
|
|
1748
|
+
image: new Circle({
|
|
1749
|
+
radius: 6,
|
|
1750
|
+
fill: new Fill({
|
|
1751
|
+
color: 'rgba(0, 170, 255, 0.4)'
|
|
1752
|
+
}),
|
|
1753
|
+
stroke: new Stroke({
|
|
1754
|
+
color: '#0066cc',
|
|
1755
|
+
width: 2
|
|
1756
|
+
})
|
|
1757
|
+
}),
|
|
1758
|
+
stroke: new Stroke({
|
|
1759
|
+
color: '#00aaff',
|
|
1760
|
+
width: 4
|
|
1761
|
+
}),
|
|
1762
|
+
fill: new Fill({
|
|
1763
|
+
color: 'rgba(0, 170, 255, 0.3)'
|
|
1764
|
+
})
|
|
1765
|
+
})
|
|
1766
|
+
];
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
function tableSvg() {
|
|
1770
|
+
return (new DOMParser().parseFromString("<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\">\n <path d=\"M20 2H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM8 20H4v-4h4v4zm0-6H4v-4h4v4zm0-6H4V4h4v4zm6 12h-4v-4h4v4zm0-6h-4v-4h4v4zm0-6h-4V4h4v4zm6 12h-4v-4h4v4zm0-6h-4v-4h4v4zm0-6h-4V4h4v4z\"></path>\n</svg>", 'image/svg+xml')).firstChild;
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
function formSvg() {
|
|
1774
|
+
return (new DOMParser().parseFromString("<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\">\n <path d=\"M14 17H4v2h10v-2zm6-8H4v2h16V9zM4 15h16v-2H4v2zM4 5v2h16V5H4z\"></path>\n</svg>", 'image/svg+xml')).firstChild;
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
/**
|
|
1778
|
+
* Shows a fields form in a modal window to allow changes in the properties of
|
|
1779
|
+
* one or several features. When editing multiple features, a "table mode"
|
|
1780
|
+
* button renders one feature per row, spreadsheet-style, so each feature can be
|
|
1781
|
+
* edited separately (like an Excel sheet). Otherwise a single form is shown
|
|
1782
|
+
* where fields whose value differs across the selection show the "multiple
|
|
1783
|
+
* values" placeholder and only the fields actually changed are applied to the
|
|
1784
|
+
* whole selection.
|
|
1785
|
+
*
|
|
1786
|
+
* @param features
|
|
1787
|
+
* @private
|
|
1788
|
+
*/
|
|
1425
1789
|
class EditFieldsModal extends Observable {
|
|
1426
1790
|
constructor(options) {
|
|
1427
1791
|
super();
|
|
1428
1792
|
this._options = options;
|
|
1429
|
-
this.
|
|
1793
|
+
this._tableMode = false;
|
|
1794
|
+
this._fieldList = [];
|
|
1795
|
+
this._modal = new Modal(Object.assign(Object.assign({}, this._options.modal), { header: true, headerClose: true, title: '', content: '<div></div>', footer: `
|
|
1430
1796
|
<button
|
|
1431
1797
|
type="button"
|
|
1432
|
-
class="btn btn-sm btn-
|
|
1798
|
+
class="btn btn-sm btn-danger"
|
|
1433
1799
|
data-action="delete"
|
|
1434
1800
|
data-dismiss="modal"
|
|
1435
1801
|
>
|
|
@@ -1454,75 +1820,208 @@ class EditFieldsModal extends Observable {
|
|
|
1454
1820
|
this._modal.on('dismiss', (modal, event) => {
|
|
1455
1821
|
// On saving changes
|
|
1456
1822
|
if (event.target.dataset.action === 'save') {
|
|
1457
|
-
|
|
1458
|
-
.
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
this._feature.changed();
|
|
1465
|
-
addFeatureToEditedList(this._feature);
|
|
1466
|
-
this.dispatchEvent(new VectorSourceEvent('save', this._feature));
|
|
1823
|
+
if (this._tableMode) {
|
|
1824
|
+
this._saveTable(modal);
|
|
1825
|
+
}
|
|
1826
|
+
else {
|
|
1827
|
+
this._saveForm(modal);
|
|
1828
|
+
}
|
|
1829
|
+
this._dispatch('save', this._features);
|
|
1467
1830
|
}
|
|
1468
1831
|
else if (event.target.dataset.action === 'delete') {
|
|
1469
|
-
this.
|
|
1832
|
+
this._dispatch('delete', this._features);
|
|
1470
1833
|
}
|
|
1471
1834
|
});
|
|
1472
1835
|
}
|
|
1473
|
-
show(
|
|
1474
|
-
this.
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
const
|
|
1836
|
+
show(features) {
|
|
1837
|
+
this._features = Array.isArray(features) ? features : [features];
|
|
1838
|
+
// Keep the form (current behavior) as default entry view
|
|
1839
|
+
this._tableMode = false;
|
|
1840
|
+
const multiple = this._features.length > 1;
|
|
1841
|
+
const modalTitle = multiple
|
|
1842
|
+
? I18N_('editElements', this._features.length)
|
|
1843
|
+
: `${I18N.labels.editElement} ${this._features[0].getId()} `;
|
|
1844
|
+
const layerName = this._features[0].get('_layerName_');
|
|
1478
1845
|
// Data schema from the geoserver
|
|
1479
1846
|
const layer = getStoredLayer(layerName);
|
|
1480
|
-
const
|
|
1847
|
+
const describeFeatureType = layer.getDescribeFeatureType()._parsed;
|
|
1848
|
+
this._fieldList = describeFeatureType.properties.filter((field) => field.name !== describeFeatureType.geomField);
|
|
1849
|
+
this._layer = layer;
|
|
1850
|
+
this._modal._html.dialog.classList.remove('ol-wfst--edit-table-modal');
|
|
1851
|
+
this._renderBody();
|
|
1852
|
+
this._modal._html.header.innerHTML = modalTitle;
|
|
1853
|
+
this._modal.show();
|
|
1854
|
+
}
|
|
1855
|
+
_renderBody() {
|
|
1856
|
+
const multiple = this._features.length > 1;
|
|
1481
1857
|
this._modal._html.body.innerHTML = '';
|
|
1482
|
-
this._modal._html.body.append(
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1858
|
+
this._modal._html.body.append(jsxs("div", { children: [multiple && (jsxs("div", { className: "ol-wfst--edit-modal-notice", children: [jsx("span", { className: "ol-wfst--edit-modal-notice-txt", children: I18N_('multipleEditNotice', this._features.length) }), jsx("button", { type: "button", className: "ol-wfst--edit-modal-toolbar-btn", title: this._tableMode
|
|
1859
|
+
? I18N.labels.formMode
|
|
1860
|
+
: I18N.labels.tableMode, onClick: () => this._toggleTable(), children: this._tableMode ? formSvg() : tableSvg() })] })), this._tableMode ? this._renderTable() : this._renderForm()] }));
|
|
1861
|
+
}
|
|
1862
|
+
_toggleTable() {
|
|
1863
|
+
this._tableMode = !this._tableMode;
|
|
1864
|
+
this._modal._html.dialog.classList.toggle('ol-wfst--edit-table-modal', this._tableMode);
|
|
1865
|
+
this._renderBody();
|
|
1866
|
+
}
|
|
1867
|
+
_renderForm() {
|
|
1868
|
+
const multiple = this._features.length > 1;
|
|
1869
|
+
return (jsx("form", { autocomplete: "false", children: this._fieldList.flatMap((field) => {
|
|
1870
|
+
const key = field.name;
|
|
1871
|
+
const values = this._features.map((feature) => feature.get(key));
|
|
1872
|
+
const uniqueValues = Array.from(new Set(values.map((value) => String(value !== null && value !== void 0 ? value : ''))));
|
|
1873
|
+
const hasMultipleValues = multiple && uniqueValues.length > 1;
|
|
1874
|
+
const value = hasMultipleValues
|
|
1875
|
+
? null
|
|
1876
|
+
: uniqueValues[0] || null;
|
|
1877
|
+
const type = this._inputType(field.type);
|
|
1878
|
+
let input = (jsx("input", { placeholder: hasMultipleValues
|
|
1879
|
+
? I18N.labels.multipleValues
|
|
1880
|
+
: 'NULL', className: 'ol-wfst--input-field-input' +
|
|
1881
|
+
(hasMultipleValues
|
|
1882
|
+
? ' ol-wfst--input-multiple'
|
|
1883
|
+
: ''), type: type, name: key, value: value, "data-multiple": hasMultipleValues ? 'true' : null }));
|
|
1884
|
+
if (this._layer.beforeShowFieldsModal) {
|
|
1885
|
+
const hookInput = this._layer.beforeShowFieldsModal(field, value, input);
|
|
1886
|
+
if (!hookInput) {
|
|
1887
|
+
return [];
|
|
1888
|
+
}
|
|
1889
|
+
if (typeof hookInput === 'string') {
|
|
1890
|
+
input = new DOMParser().parseFromString(hookInput, 'text/html').body.childNodes[0];
|
|
1891
|
+
}
|
|
1892
|
+
else {
|
|
1893
|
+
input = hookInput;
|
|
1894
|
+
}
|
|
1511
1895
|
}
|
|
1512
|
-
if (
|
|
1513
|
-
input =
|
|
1896
|
+
if (hasMultipleValues) {
|
|
1897
|
+
input.dataset.multiple = 'true';
|
|
1898
|
+
input.classList.add('ol-wfst--input-multiple');
|
|
1514
1899
|
}
|
|
1515
|
-
|
|
1516
|
-
|
|
1900
|
+
return (jsxs("div", { className: "ol-wfst--input-field-container", children: [jsx("label", { className: "ol-wfst--input-field-label", htmlFor: key, children: key }), input] }));
|
|
1901
|
+
}) }));
|
|
1902
|
+
}
|
|
1903
|
+
_renderTable() {
|
|
1904
|
+
return (jsx("form", { autocomplete: "false", children: jsx("div", { className: "ol-wfst--edit-table-wrap", children: jsxs("table", { className: "ol-wfst--edit-table", children: [jsx("thead", { children: jsxs("tr", { children: [jsx("th", { children: I18N.labels.feature }), jsx("th", { children: I18N.labels.geometry }), this._fieldList.map((field) => (jsx("th", { children: field.name })))] }) }), jsx("tbody", { children: this._features.map((feature, idx) => {
|
|
1905
|
+
var _a;
|
|
1906
|
+
return (jsxs("tr", { children: [jsx("td", { className: "ol-wfst--edit-table-fid", children: String((_a = feature.getId()) !== null && _a !== void 0 ? _a : idx + 1) }), jsx("td", { className: "ol-wfst--edit-table-geom", children: geometryTypeLabel(feature.getGeometry()) }), this._fieldList.map((field) => {
|
|
1907
|
+
var _a;
|
|
1908
|
+
const key = field.name;
|
|
1909
|
+
const type = this._inputType(field.type);
|
|
1910
|
+
const value = (_a = feature.get(key)) !== null && _a !== void 0 ? _a : '';
|
|
1911
|
+
let input = (jsx("input", { type: type, name: key, className: "ol-wfst--input-field-input", "data-feat-idx": String(idx), value: String(value) }));
|
|
1912
|
+
if (this._layer.beforeShowFieldsModal) {
|
|
1913
|
+
const hookInput = this._layer.beforeShowFieldsModal(field, value, input);
|
|
1914
|
+
if (!hookInput) {
|
|
1915
|
+
return jsx("td", {});
|
|
1916
|
+
}
|
|
1917
|
+
if (typeof hookInput === 'string') {
|
|
1918
|
+
input =
|
|
1919
|
+
new DOMParser().parseFromString(hookInput, 'text/html').body
|
|
1920
|
+
.childNodes[0];
|
|
1921
|
+
}
|
|
1922
|
+
else {
|
|
1923
|
+
input = hookInput;
|
|
1924
|
+
}
|
|
1925
|
+
input.setAttribute('name', key);
|
|
1926
|
+
input.setAttribute('data-feat-idx', String(idx));
|
|
1927
|
+
input.classList.add('ol-wfst--input-field-input');
|
|
1928
|
+
}
|
|
1929
|
+
return jsx("td", { children: input });
|
|
1930
|
+
})] }));
|
|
1931
|
+
}) })] }) }) }));
|
|
1932
|
+
}
|
|
1933
|
+
_inputType(typeXsd) {
|
|
1934
|
+
switch (typeXsd) {
|
|
1935
|
+
case 'xsd:double':
|
|
1936
|
+
case 'xsd:number':
|
|
1937
|
+
case 'xsd:int':
|
|
1938
|
+
return 'number';
|
|
1939
|
+
case 'xsd:date':
|
|
1940
|
+
return 'date';
|
|
1941
|
+
case 'xsd:date-time':
|
|
1942
|
+
return 'datetime';
|
|
1943
|
+
case 'xsd:string':
|
|
1944
|
+
default:
|
|
1945
|
+
return 'text';
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
_saveForm(modal) {
|
|
1949
|
+
const formElements = modal.el.querySelector('form')
|
|
1950
|
+
.elements;
|
|
1951
|
+
const multipleFields = new Set(Array.from(formElements)
|
|
1952
|
+
.filter((el) => el.dataset.multiple === 'true')
|
|
1953
|
+
.map((el) => el.name));
|
|
1954
|
+
Array.from(formElements).forEach((el) => {
|
|
1955
|
+
const value = el.value;
|
|
1956
|
+
const field = el.name;
|
|
1957
|
+
// Do not overwrite untouched fields that had
|
|
1958
|
+
// multiple values in the selection
|
|
1959
|
+
if (multipleFields.has(field) && value === '') {
|
|
1960
|
+
return;
|
|
1961
|
+
}
|
|
1962
|
+
this._features.forEach((feature) => {
|
|
1963
|
+
if (feature.get(field) !== value) {
|
|
1964
|
+
feature.set(field, value, /* isSilent = */ true);
|
|
1965
|
+
}
|
|
1966
|
+
});
|
|
1967
|
+
});
|
|
1968
|
+
this._features.forEach((feature) => {
|
|
1969
|
+
feature.changed();
|
|
1970
|
+
addFeatureToEditedList(feature);
|
|
1971
|
+
});
|
|
1972
|
+
}
|
|
1973
|
+
_saveTable(modal) {
|
|
1974
|
+
const inputList = Array.from(modal.el.querySelectorAll('form input[data-feat-idx]'));
|
|
1975
|
+
const grouped = new Map();
|
|
1976
|
+
inputList.forEach((input) => {
|
|
1977
|
+
const idx = Number(input.dataset.featIdx);
|
|
1978
|
+
if (!grouped.has(idx)) {
|
|
1979
|
+
grouped.set(idx, []);
|
|
1980
|
+
}
|
|
1981
|
+
grouped.get(idx).push(input);
|
|
1982
|
+
});
|
|
1983
|
+
grouped.forEach((inputs, idx) => {
|
|
1984
|
+
const feature = this._features[idx];
|
|
1985
|
+
if (!feature) {
|
|
1986
|
+
return;
|
|
1987
|
+
}
|
|
1988
|
+
inputs.forEach((input) => {
|
|
1989
|
+
const field = input.name;
|
|
1990
|
+
const value = input.value;
|
|
1991
|
+
if (feature.get(field) !== value) {
|
|
1992
|
+
feature.set(field, value, /* isSilent = */ true);
|
|
1517
1993
|
}
|
|
1994
|
+
});
|
|
1995
|
+
});
|
|
1996
|
+
this._features.forEach((feature) => {
|
|
1997
|
+
feature.changed();
|
|
1998
|
+
addFeatureToEditedList(feature);
|
|
1999
|
+
});
|
|
2000
|
+
}
|
|
2001
|
+
_dispatch(type, features) {
|
|
2002
|
+
const evt = new VectorSourceEvent(type, features[0]);
|
|
2003
|
+
evt.features = features;
|
|
2004
|
+
this.dispatchEvent(evt);
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
function geometryTypeLabel(geometry) {
|
|
2008
|
+
if (!geometry) {
|
|
2009
|
+
return '-';
|
|
2010
|
+
}
|
|
2011
|
+
if (geometry instanceof GeometryCollection$1) {
|
|
2012
|
+
const types = new Set();
|
|
2013
|
+
const collect = (geom) => {
|
|
2014
|
+
if (geom instanceof GeometryCollection$1) {
|
|
2015
|
+
geom.getGeometriesArray().forEach(collect);
|
|
1518
2016
|
}
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
|
|
2017
|
+
else {
|
|
2018
|
+
types.add(geom.getType());
|
|
2019
|
+
}
|
|
2020
|
+
};
|
|
2021
|
+
collect(geometry);
|
|
2022
|
+
return types.size ? Array.from(types).join(', ') : '-';
|
|
1525
2023
|
}
|
|
2024
|
+
return geometry.getType();
|
|
1526
2025
|
}
|
|
1527
2026
|
|
|
1528
2027
|
// Ol
|
|
@@ -1548,8 +2047,8 @@ class Geoserver extends BaseObject {
|
|
|
1548
2047
|
wfsTransactionVersion: '1.1.0',
|
|
1549
2048
|
projection: DEFAULT_GEOSERVER_SRS,
|
|
1550
2049
|
lockFeatureParams: {
|
|
1551
|
-
expiry: 5,
|
|
1552
|
-
lockId: '
|
|
2050
|
+
expiry: 5, // minutes
|
|
2051
|
+
lockId: 'WFST-editor',
|
|
1553
2052
|
releaseAction: 'SOME'
|
|
1554
2053
|
}
|
|
1555
2054
|
},
|
|
@@ -1567,6 +2066,7 @@ class Geoserver extends BaseObject {
|
|
|
1567
2066
|
this._insertFeatures = [];
|
|
1568
2067
|
this._updateFeatures = [];
|
|
1569
2068
|
this._deleteFeatures = [];
|
|
2069
|
+
this._lockedId = this._options.advanced.lockFeatureParams.lockId;
|
|
1570
2070
|
// Formats
|
|
1571
2071
|
this._formatWFS = new WFS();
|
|
1572
2072
|
this._formatGeoJSON = new GeoJSON();
|
|
@@ -1905,7 +2405,7 @@ class Geoserver extends BaseObject {
|
|
|
1905
2405
|
if (this.hasLockFeature &&
|
|
1906
2406
|
this.getUseLockFeature() &&
|
|
1907
2407
|
transactionType !== TransactionType.Insert) {
|
|
1908
|
-
payload = payload.replace(`</Transaction>`, `<LockId>${this.
|
|
2408
|
+
payload = payload.replace(`</Transaction>`, `<LockId>${this._lockedId}</LockId></Transaction>`);
|
|
1909
2409
|
}
|
|
1910
2410
|
const headers = Object.assign({ 'Content-Type': 'text/xml' }, this.getHeaders());
|
|
1911
2411
|
const response = await fetch(this.getUrl(), {
|
|
@@ -1933,6 +2433,9 @@ class Geoserver extends BaseObject {
|
|
|
1933
2433
|
if (transactionType !== TransactionType.Delete) {
|
|
1934
2434
|
this._removeFeatures(features);
|
|
1935
2435
|
}
|
|
2436
|
+
features.forEach((feature) => {
|
|
2437
|
+
removeFeatureFromEditList(feature);
|
|
2438
|
+
});
|
|
1936
2439
|
wlayer.refresh();
|
|
1937
2440
|
showLoading(false);
|
|
1938
2441
|
this._insertFeatures = [];
|
|
@@ -1992,30 +2495,34 @@ class Geoserver extends BaseObject {
|
|
|
1992
2495
|
const featureProperties = feature.getProperties();
|
|
1993
2496
|
delete featureProperties.boundedBy;
|
|
1994
2497
|
delete featureProperties._layerName_;
|
|
2498
|
+
delete featureProperties._editOverlayCoord_;
|
|
1995
2499
|
const clone = new Feature(featureProperties);
|
|
1996
2500
|
clone.setId(feature.getId());
|
|
1997
2501
|
return clone;
|
|
1998
2502
|
}
|
|
1999
2503
|
/**
|
|
2000
|
-
* Lock
|
|
2001
|
-
* to avoid changes from multiples
|
|
2504
|
+
* Lock one or several features in the geoserver. Useful before editing,
|
|
2505
|
+
* to avoid changes from multiples users. Locking several features at once
|
|
2506
|
+
* with a single request keeps the lock covered by one LockId
|
|
2002
2507
|
*
|
|
2003
|
-
* @param
|
|
2508
|
+
* @param featureIds
|
|
2004
2509
|
* @param layerName
|
|
2005
2510
|
* @param retry
|
|
2006
2511
|
* @public
|
|
2007
2512
|
*/
|
|
2008
|
-
async lockFeature(
|
|
2513
|
+
async lockFeature(featureIds, layerName, retry = 0) {
|
|
2009
2514
|
const params = new URLSearchParams({
|
|
2010
2515
|
service: 'wfs',
|
|
2011
2516
|
version: this.getAdvanced().lockFeatureVersion,
|
|
2012
2517
|
request: 'LockFeature',
|
|
2013
2518
|
typeName: layerName,
|
|
2014
2519
|
expiry: String(this._options.advanced.lockFeatureParams.expiry),
|
|
2015
|
-
|
|
2520
|
+
handle: this._options.advanced.lockFeatureParams.lockId,
|
|
2016
2521
|
releaseAction: this._options.advanced.lockFeatureParams.releaseAction,
|
|
2017
2522
|
exceptions: 'application/json',
|
|
2018
|
-
featureid:
|
|
2523
|
+
featureid: Array.isArray(featureIds)
|
|
2524
|
+
? featureIds.join(',')
|
|
2525
|
+
: String(featureIds)
|
|
2019
2526
|
});
|
|
2020
2527
|
const url_fetch = this.getUrl() + '?' + params.toString();
|
|
2021
2528
|
try {
|
|
@@ -2036,7 +2543,7 @@ class Geoserver extends BaseObject {
|
|
|
2036
2543
|
if (exceptions[0].code === 'CannotLockAllFeatures') {
|
|
2037
2544
|
// Maybe the Feature is already blocked, ant thats trigger error, so, we try one locking more time again
|
|
2038
2545
|
if (!retry) {
|
|
2039
|
-
this.lockFeature(
|
|
2546
|
+
this.lockFeature(featureIds, layerName, 1);
|
|
2040
2547
|
}
|
|
2041
2548
|
else {
|
|
2042
2549
|
throw error;
|
|
@@ -2048,21 +2555,20 @@ class Geoserver extends BaseObject {
|
|
|
2048
2555
|
}
|
|
2049
2556
|
}
|
|
2050
2557
|
catch (err) {
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2558
|
+
// XML response (not JSON): parse the real lock id returned by
|
|
2559
|
+
// the server so the transaction references the lock that was
|
|
2560
|
+
// actually created instead of the configured one
|
|
2561
|
+
try {
|
|
2562
|
+
const dataDoc = new window.DOMParser().parseFromString(data, 'text/xml');
|
|
2563
|
+
const lockIdNode = dataDoc.getElementsByTagName('wfs:LockId')[0] ||
|
|
2564
|
+
dataDoc.getElementsByTagNameNS('http://www.opengis.net/wfs', 'LockId')[0];
|
|
2565
|
+
if (lockIdNode && lockIdNode.textContent) {
|
|
2566
|
+
this._lockedId = lockIdNode.textContent;
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
catch (parseErr) {
|
|
2570
|
+
// Keep the configured lockId
|
|
2063
2571
|
}
|
|
2064
|
-
|
|
2065
|
-
*/
|
|
2066
2572
|
}
|
|
2067
2573
|
return data;
|
|
2068
2574
|
}
|
|
@@ -2086,35 +2592,108 @@ var GeoserverProperty;
|
|
|
2086
2592
|
GeoserverProperty["ISLOADED"] = "isLoaded";
|
|
2087
2593
|
})(GeoserverProperty || (GeoserverProperty = {}));
|
|
2088
2594
|
|
|
2089
|
-
|
|
2595
|
+
function editFieldsSvg() {
|
|
2596
|
+
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;
|
|
2597
|
+
}
|
|
2090
2598
|
|
|
2091
|
-
|
|
2599
|
+
function editGeomSvg() {
|
|
2600
|
+
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;
|
|
2601
|
+
}
|
|
2092
2602
|
|
|
2093
2603
|
class EditOverlay extends Overlay {
|
|
2094
|
-
constructor(feature, coordinate = null) {
|
|
2604
|
+
constructor(feature = null, coordinate = null, id) {
|
|
2095
2605
|
super({
|
|
2096
|
-
id: feature.getId(),
|
|
2097
|
-
position: coordinate ||
|
|
2606
|
+
id: id !== null && id !== void 0 ? id : (feature ? feature.getId() : undefined),
|
|
2607
|
+
position: coordinate ||
|
|
2608
|
+
(feature
|
|
2609
|
+
? getCenter(feature.getGeometry().getExtent())
|
|
2610
|
+
: undefined),
|
|
2098
2611
|
positioning: 'center-center',
|
|
2099
2612
|
offset: [0, -40],
|
|
2100
2613
|
stopEvent: true,
|
|
2101
|
-
element: (
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
createElement("img", { src: img$1, alt: I18N.labels.editFields }))),
|
|
2107
|
-
createElement("div", { className: "ol-wfst--edit-button-cnt", onClick: () => {
|
|
2108
|
-
this.dispatchEvent('editGeom');
|
|
2109
|
-
} },
|
|
2110
|
-
createElement("button", { class: "ol-wfst--edit-button", type: "button", title: I18N.labels.editGeom },
|
|
2111
|
-
createElement("img", { src: img, alt: I18N.labels.editGeom })))))
|
|
2614
|
+
element: (jsxs("div", { children: [jsx("div", { className: "ol-wfst--edit-button-cnt", onClick: () => {
|
|
2615
|
+
this.dispatchEvent('editFields');
|
|
2616
|
+
}, 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: () => {
|
|
2617
|
+
this.dispatchEvent('editGeom');
|
|
2618
|
+
}, children: jsx("button", { class: "ol-wfst--edit-button", type: "button", title: I18N.labels.editGeom, children: editGeomSvg() }) })] }))
|
|
2112
2619
|
});
|
|
2113
2620
|
}
|
|
2114
2621
|
}
|
|
2115
2622
|
|
|
2116
|
-
|
|
2117
|
-
|
|
2623
|
+
function addLineSvg() {
|
|
2624
|
+
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;
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2627
|
+
class ExtendLineOverlay extends Overlay {
|
|
2628
|
+
constructor(feature, coordinate, side, component = 0, onClick) {
|
|
2629
|
+
super({
|
|
2630
|
+
position: coordinate,
|
|
2631
|
+
positioning: 'center-center',
|
|
2632
|
+
stopEvent: true,
|
|
2633
|
+
element: (jsx("div", { className: "ol-wfst--extend-button-cnt", children: jsx("button", { className: "ol-wfst--extend-button", type: "button", title: I18N.labels.continueLine, onClick: () => {
|
|
2634
|
+
onClick(side);
|
|
2635
|
+
}, children: addLineSvg() }) }))
|
|
2636
|
+
});
|
|
2637
|
+
this.feature = feature;
|
|
2638
|
+
this.side = side;
|
|
2639
|
+
this.component = component;
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2643
|
+
const QUERY_OVERLAY_ID = 'ol-wfst--query-overlay';
|
|
2644
|
+
/**
|
|
2645
|
+
* Popup overlay that shows the attributes of a queried feature and its
|
|
2646
|
+
* layer label. The geometry is highlighted separately on a dedicated layer.
|
|
2647
|
+
*
|
|
2648
|
+
* @extends {ol/Overlay~Overlay}
|
|
2649
|
+
* @param feature
|
|
2650
|
+
* @param coordinate
|
|
2651
|
+
* @param layer
|
|
2652
|
+
* @private
|
|
2653
|
+
*/
|
|
2654
|
+
class QueryOverlay extends Overlay {
|
|
2655
|
+
constructor(feature, coordinate, layer) {
|
|
2656
|
+
const describeFeatureType = layer.getDescribeFeatureType();
|
|
2657
|
+
const parsed = describeFeatureType === null || describeFeatureType === void 0 ? void 0 : describeFeatureType._parsed;
|
|
2658
|
+
const fields = (parsed === null || parsed === void 0 ? void 0 : parsed.properties) || [];
|
|
2659
|
+
const geomField = parsed === null || parsed === void 0 ? void 0 : parsed.geomField;
|
|
2660
|
+
const rows = fields
|
|
2661
|
+
.filter((field) => field.name !== geomField)
|
|
2662
|
+
.map((field) => {
|
|
2663
|
+
let value = feature.get(field.name);
|
|
2664
|
+
if (value === undefined || value === null) {
|
|
2665
|
+
value = '';
|
|
2666
|
+
}
|
|
2667
|
+
else if (typeof value === 'object') {
|
|
2668
|
+
value = JSON.stringify(value);
|
|
2669
|
+
}
|
|
2670
|
+
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) })] }));
|
|
2671
|
+
});
|
|
2672
|
+
super({
|
|
2673
|
+
id: QUERY_OVERLAY_ID,
|
|
2674
|
+
position: coordinate || getCenter(feature.getGeometry().getExtent()),
|
|
2675
|
+
positioning: 'bottom-center',
|
|
2676
|
+
offset: [0, -14],
|
|
2677
|
+
stopEvent: true,
|
|
2678
|
+
autoPan: true,
|
|
2679
|
+
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: () => {
|
|
2680
|
+
this.dispatchEvent('close');
|
|
2681
|
+
}, 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 })] }))
|
|
2682
|
+
});
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
function fullscreenSvg() {
|
|
2687
|
+
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;
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2690
|
+
function fullscreenExitSvg() {
|
|
2691
|
+
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;
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
const controlElement = document.createElement('div');
|
|
2695
|
+
const INIT_LOADING_TIMEOUT = 20000;
|
|
2696
|
+
/**
|
|
2118
2697
|
* Tiny WFS-T client to insert (drawing/uploading), modify and delete
|
|
2119
2698
|
* features on GeoServers using OpenLayers. Layers with these types
|
|
2120
2699
|
* of geometries are supported: "GeometryCollection" (in this case, you can
|
|
@@ -2142,6 +2721,14 @@ class Wfst extends Control {
|
|
|
2142
2721
|
}
|
|
2143
2722
|
});
|
|
2144
2723
|
this._initialized = false;
|
|
2724
|
+
this._queryOverlayId = 'ol-wfst--query-overlay';
|
|
2725
|
+
this._shiftPressed = false;
|
|
2726
|
+
this._hoveredFeature = null;
|
|
2727
|
+
this._keySelectBound = false;
|
|
2728
|
+
// Editing
|
|
2729
|
+
this._editFeaturesOriginal = {};
|
|
2730
|
+
this._multiOverlayId = 'ol-wfst--multi-edit-overlay';
|
|
2731
|
+
this._extendOverlays = [];
|
|
2145
2732
|
setLang(options.language, options.i18n);
|
|
2146
2733
|
const defaultOptions = getDefaultOptions();
|
|
2147
2734
|
this._options = deepObjectAssign(defaultOptions, options);
|
|
@@ -2182,8 +2769,10 @@ class Wfst extends Control {
|
|
|
2182
2769
|
if (layers.length) {
|
|
2183
2770
|
let layerRendered = 0;
|
|
2184
2771
|
let layersNumber = 0; // Only count visibles
|
|
2772
|
+
showLoading();
|
|
2773
|
+
const layerInitPromises = [];
|
|
2185
2774
|
layers.forEach((layer) => {
|
|
2186
|
-
if (layer.
|
|
2775
|
+
if (layer.isVisible(this._view))
|
|
2187
2776
|
layersNumber++;
|
|
2188
2777
|
layer.on('layerRendered', () => {
|
|
2189
2778
|
layerRendered++;
|
|
@@ -2197,15 +2786,17 @@ class Wfst extends Control {
|
|
|
2197
2786
|
}
|
|
2198
2787
|
});
|
|
2199
2788
|
layer.on('change:describeFeatureType', () => {
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2789
|
+
if (this._options.showControl && layers.length > 1) {
|
|
2790
|
+
const domEl = this._layersControl.addLayerEl(layer);
|
|
2791
|
+
layer.on('change:isVisible', () => {
|
|
2792
|
+
const layerNotVisible = 'ol-wfst--layer-not-visible';
|
|
2793
|
+
const visible = layer.isVisibleByZoom();
|
|
2794
|
+
if (visible)
|
|
2795
|
+
domEl.classList.remove(layerNotVisible);
|
|
2796
|
+
else
|
|
2797
|
+
domEl.classList.add(layerNotVisible);
|
|
2798
|
+
});
|
|
2799
|
+
}
|
|
2209
2800
|
layer.set(BaseLayerProperty.ISVISIBLE, this._currentZoom > layer.getMinZoom());
|
|
2210
2801
|
this.dispatchEvent(new WfstEvent({
|
|
2211
2802
|
type: 'describeFeatureType',
|
|
@@ -2213,13 +2804,21 @@ class Wfst extends Control {
|
|
|
2213
2804
|
data: layer.getDescribeFeatureType()
|
|
2214
2805
|
}));
|
|
2215
2806
|
});
|
|
2216
|
-
layer._init();
|
|
2807
|
+
layerInitPromises.push(layer._init());
|
|
2217
2808
|
this._map.addLayer(layer);
|
|
2218
2809
|
setMapLayers({
|
|
2219
2810
|
[layer.get(BaseLayerProperty.NAME)]: layer
|
|
2220
2811
|
});
|
|
2221
2812
|
});
|
|
2222
2813
|
this._createMapElements(this._options.showControl, this._options.active);
|
|
2814
|
+
// Show the loading bar until every layer has resolved its
|
|
2815
|
+
// DescribeFeatureType (success or error), with a fail-safe
|
|
2816
|
+
// timeout so it never stays visible on a dead connection.
|
|
2817
|
+
await Promise.race([
|
|
2818
|
+
Promise.all(layerInitPromises),
|
|
2819
|
+
new Promise((resolve) => setTimeout(resolve, INIT_LOADING_TIMEOUT))
|
|
2820
|
+
]);
|
|
2821
|
+
showLoading(false);
|
|
2223
2822
|
}
|
|
2224
2823
|
}
|
|
2225
2824
|
catch (err) {
|
|
@@ -2234,6 +2833,7 @@ class Wfst extends Control {
|
|
|
2234
2833
|
this._map = super.getMap();
|
|
2235
2834
|
this._view = this._map.getView();
|
|
2236
2835
|
this._viewport = this._map.getViewport();
|
|
2836
|
+
this._map.getTargetElement().classList.add('ol-wfst--map');
|
|
2237
2837
|
setMap(this._map);
|
|
2238
2838
|
//@ts-expect-error
|
|
2239
2839
|
this._uploads.on('addedFeatures', ({ features }) => {
|
|
@@ -2252,17 +2852,26 @@ class Wfst extends Control {
|
|
|
2252
2852
|
});
|
|
2253
2853
|
});
|
|
2254
2854
|
// @ts-expect-error
|
|
2255
|
-
this._editFields.on('save', ({ feature }) => {
|
|
2256
|
-
|
|
2257
|
-
this.
|
|
2855
|
+
this._editFields.on('save', async ({ feature, features }) => {
|
|
2856
|
+
const list = features || [feature];
|
|
2857
|
+
const ok = await this._transactEditList(list);
|
|
2858
|
+
if (ok) {
|
|
2859
|
+
Array.from(list).forEach((f) => this._collectionModify.remove(f));
|
|
2860
|
+
}
|
|
2258
2861
|
});
|
|
2259
2862
|
// @ts-expect-error
|
|
2260
|
-
this._editFields.on('delete', ({
|
|
2261
|
-
this._deleteFeature(
|
|
2863
|
+
this._editFields.on('delete', ({ features }) => {
|
|
2864
|
+
this._deleteFeature(features, true);
|
|
2262
2865
|
});
|
|
2263
2866
|
this._addMapEvents();
|
|
2867
|
+
this._addMiddleButtonPan();
|
|
2264
2868
|
initModal(this._options['modal']);
|
|
2265
|
-
this.
|
|
2869
|
+
this._layersWidgetDiv = document.createElement('div');
|
|
2870
|
+
this._layersWidgetDiv.className = 'ol-wfst--layers-control';
|
|
2871
|
+
this._layersWidgetDiv.append(initLoading());
|
|
2872
|
+
this._map.addControl(new Control({
|
|
2873
|
+
element: this._layersWidgetDiv
|
|
2874
|
+
}));
|
|
2266
2875
|
this._initMapAndLayers();
|
|
2267
2876
|
}
|
|
2268
2877
|
/**
|
|
@@ -2278,8 +2887,18 @@ class Wfst extends Control {
|
|
|
2278
2887
|
this._prepareEditLayer();
|
|
2279
2888
|
this._addInteractions();
|
|
2280
2889
|
this._addInteractionHandlers();
|
|
2890
|
+
this._prepareHighlightLayer();
|
|
2891
|
+
this._addQueryInteraction();
|
|
2281
2892
|
if (showControl) {
|
|
2282
2893
|
this._addMapControl();
|
|
2894
|
+
if (this._options.fullscreen) {
|
|
2895
|
+
this._map.addControl(new FullScreen({
|
|
2896
|
+
className: 'ol-wfst--fullscreen',
|
|
2897
|
+
tipLabel: I18N.labels.fullscreen,
|
|
2898
|
+
label: fullscreenSvg(),
|
|
2899
|
+
labelActive: fullscreenExitSvg()
|
|
2900
|
+
}));
|
|
2901
|
+
}
|
|
2283
2902
|
}
|
|
2284
2903
|
// By default, init in edit mode
|
|
2285
2904
|
this.activateEditMode(active);
|
|
@@ -2298,9 +2917,10 @@ class Wfst extends Control {
|
|
|
2298
2917
|
this._interactionWfsSelect = new Select({
|
|
2299
2918
|
hitTolerance: 10,
|
|
2300
2919
|
style: (feature) => styleFunction(feature),
|
|
2301
|
-
toggleCondition:
|
|
2920
|
+
toggleCondition: shiftKeyOnly, // Allow adding features to the selection with shift
|
|
2302
2921
|
filter: (feature, layer) => {
|
|
2303
2922
|
return (getMode() !== Modes.Edit &&
|
|
2923
|
+
getMode() !== Modes.Query &&
|
|
2304
2924
|
layer &&
|
|
2305
2925
|
layer instanceof WfsLayer &&
|
|
2306
2926
|
layer === getActiveLayerToInsertEls());
|
|
@@ -2309,6 +2929,11 @@ class Wfst extends Control {
|
|
|
2309
2929
|
this._map.addInteraction(this._interactionWfsSelect);
|
|
2310
2930
|
this._interactionWfsSelect.on('select', ({ selected, deselected, mapBrowserEvent }) => {
|
|
2311
2931
|
const coordinate = mapBrowserEvent.coordinate;
|
|
2932
|
+
// Clear hover to avoid keeping the style on the selected feature
|
|
2933
|
+
this._clearHoverState();
|
|
2934
|
+
if (getMode() === Modes.Query) {
|
|
2935
|
+
return;
|
|
2936
|
+
}
|
|
2312
2937
|
if (selected.length) {
|
|
2313
2938
|
selected.forEach((feature) => {
|
|
2314
2939
|
if (!isFeatureEdited(feature)) {
|
|
@@ -2318,6 +2943,7 @@ class Wfst extends Control {
|
|
|
2318
2943
|
this._addFeatureToEditMode(feature, coordinate, layer.get(BaseLayerProperty.NAME));
|
|
2319
2944
|
}
|
|
2320
2945
|
});
|
|
2946
|
+
this._lockSelectedFeatures();
|
|
2321
2947
|
}
|
|
2322
2948
|
if (deselected.length) {
|
|
2323
2949
|
if (getMode() !== Modes.Edit) {
|
|
@@ -2339,18 +2965,33 @@ class Wfst extends Control {
|
|
|
2339
2965
|
this._interactionSelectModify = new Select({
|
|
2340
2966
|
style: (feature) => styleFunction(feature),
|
|
2341
2967
|
layers: [getEditLayer()],
|
|
2342
|
-
toggleCondition:
|
|
2968
|
+
toggleCondition: shiftKeyOnly, // Allow adding features with shift
|
|
2343
2969
|
removeCondition: () => (getMode() === Modes.Edit ? true : false) // Prevent deselect on clicking outside the feature
|
|
2344
2970
|
});
|
|
2345
2971
|
this._map.addInteraction(this._interactionSelectModify);
|
|
2972
|
+
// When clicking without shift, replace the current selection: remove
|
|
2973
|
+
// any previously selected and not re-selected feature from the edit list
|
|
2974
|
+
this._interactionSelectModify.on('select', ({ selected, mapBrowserEvent }) => {
|
|
2975
|
+
if (!selected.length ||
|
|
2976
|
+
!mapBrowserEvent ||
|
|
2977
|
+
shiftKeyOnly(mapBrowserEvent)) {
|
|
2978
|
+
return;
|
|
2979
|
+
}
|
|
2980
|
+
const selectedSet = new Set(selected);
|
|
2981
|
+
Array.from(this._collectionModify.getArray()).forEach((feature) => {
|
|
2982
|
+
if (!selectedSet.has(feature)) {
|
|
2983
|
+
this._collectionModify.remove(feature);
|
|
2984
|
+
}
|
|
2985
|
+
});
|
|
2986
|
+
});
|
|
2346
2987
|
this._collectionModify =
|
|
2347
2988
|
this._interactionSelectModify.getFeatures();
|
|
2348
|
-
this._keyClickWms = this._map.on(this._options.evtType, async (evt) => {
|
|
2989
|
+
this._keyClickWms = this._map.on(this._options.evtType || 'singleclick', async (evt) => {
|
|
2349
2990
|
if (this._map.hasFeatureAtPixel(evt.pixel)) {
|
|
2350
2991
|
return;
|
|
2351
2992
|
}
|
|
2352
2993
|
// Only get other features if editmode is disabled
|
|
2353
|
-
if (getMode() !== Modes.Edit) {
|
|
2994
|
+
if (getMode() !== Modes.Edit && getMode() !== Modes.Query) {
|
|
2354
2995
|
const layer = getActiveLayerToInsertEls();
|
|
2355
2996
|
// If layer is hidden or is a wfs, skip
|
|
2356
2997
|
if (!layer.getVisible() ||
|
|
@@ -2362,8 +3003,14 @@ class Wfst extends Control {
|
|
|
2362
3003
|
if (!(features === null || features === void 0 ? void 0 : features.length)) {
|
|
2363
3004
|
return;
|
|
2364
3005
|
}
|
|
2365
|
-
//
|
|
3006
|
+
// Without shift, replace the current selection
|
|
3007
|
+
if (!shiftKeyOnly(evt)) {
|
|
3008
|
+
Array.from(this._collectionModify.getArray()).forEach((feature) => {
|
|
3009
|
+
this._collectionModify.remove(feature);
|
|
3010
|
+
});
|
|
3011
|
+
}
|
|
2366
3012
|
this._addFeatureToEditMode(features[0], evt.coordinate, layer.get(BaseLayerProperty.NAME));
|
|
3013
|
+
this._lockSelectedFeatures();
|
|
2367
3014
|
}
|
|
2368
3015
|
});
|
|
2369
3016
|
};
|
|
@@ -2374,20 +3021,9 @@ class Wfst extends Control {
|
|
|
2374
3021
|
prepareWmsInteraction();
|
|
2375
3022
|
}
|
|
2376
3023
|
this._interactionModify = new Modify({
|
|
2377
|
-
style: () => {
|
|
3024
|
+
style: (feature) => {
|
|
2378
3025
|
if (getMode() === Modes.Edit) {
|
|
2379
|
-
return
|
|
2380
|
-
image: new CircleStyle({
|
|
2381
|
-
radius: 6,
|
|
2382
|
-
fill: new Fill$1({
|
|
2383
|
-
color: '#ff0000'
|
|
2384
|
-
}),
|
|
2385
|
-
stroke: new Stroke$1({
|
|
2386
|
-
width: 2,
|
|
2387
|
-
color: 'rgba(5, 5, 5, 0.9)'
|
|
2388
|
-
})
|
|
2389
|
-
})
|
|
2390
|
-
});
|
|
3026
|
+
return editVertexStyles(feature.get('existing') === true);
|
|
2391
3027
|
}
|
|
2392
3028
|
else {
|
|
2393
3029
|
return;
|
|
@@ -2403,6 +3039,175 @@ class Wfst extends Control {
|
|
|
2403
3039
|
source: getEditLayer().getSource()
|
|
2404
3040
|
});
|
|
2405
3041
|
this._map.addInteraction(this._interactionSnap);
|
|
3042
|
+
this._addSelectionInteractions();
|
|
3043
|
+
}
|
|
3044
|
+
/**
|
|
3045
|
+
* Interactions to select features by drawing a box (DragBox) or a freehand
|
|
3046
|
+
* polygon (freehand Draw lasso). The selected features are added to the
|
|
3047
|
+
* edit list, keeping the same behaviour as the single click selection.
|
|
3048
|
+
*
|
|
3049
|
+
* @private
|
|
3050
|
+
*/
|
|
3051
|
+
_addSelectionInteractions() {
|
|
3052
|
+
const selectionStyle = new Style$1({
|
|
3053
|
+
fill: new Fill$1({
|
|
3054
|
+
color: 'rgba(255, 200, 0, 0.1)'
|
|
3055
|
+
}),
|
|
3056
|
+
stroke: new Stroke$1({
|
|
3057
|
+
color: '#ffc800',
|
|
3058
|
+
width: 2,
|
|
3059
|
+
lineDash: [6, 4]
|
|
3060
|
+
})
|
|
3061
|
+
});
|
|
3062
|
+
// Box selection (dragging a rectangle)
|
|
3063
|
+
this._interactionDragBox = new DragBox({
|
|
3064
|
+
condition: (evt) => primaryAction(evt) && this._isSelectMode(SelectionMode.Box),
|
|
3065
|
+
className: 'ol-wfst--dragbox'
|
|
3066
|
+
});
|
|
3067
|
+
this._interactionDragBox.on('boxend', (evt) => {
|
|
3068
|
+
if (!this._isSelectMode(SelectionMode.Box)) {
|
|
3069
|
+
return;
|
|
3070
|
+
}
|
|
3071
|
+
// Without shift, replace the current selection
|
|
3072
|
+
if (!evt.mapBrowserEvent.originalEvent.shiftKey) {
|
|
3073
|
+
this._collectionModify.clear();
|
|
3074
|
+
}
|
|
3075
|
+
this._selectByGeometry(this._interactionDragBox.getGeometry());
|
|
3076
|
+
});
|
|
3077
|
+
this._map.addInteraction(this._interactionDragBox);
|
|
3078
|
+
// Freehand selection (holding the mouse to draw a closed lasso)
|
|
3079
|
+
this._interactionFreehandSelect = new Draw({
|
|
3080
|
+
type: GeometryType.Polygon,
|
|
3081
|
+
freehand: true,
|
|
3082
|
+
stopClick: true,
|
|
3083
|
+
style: selectionStyle,
|
|
3084
|
+
condition: (evt) => primaryAction(evt) && this._isSelectMode(SelectionMode.Freehand)
|
|
3085
|
+
});
|
|
3086
|
+
this._interactionFreehandSelect.on('drawend', (evt) => {
|
|
3087
|
+
if (!this._isSelectMode(SelectionMode.Freehand)) {
|
|
3088
|
+
return;
|
|
3089
|
+
}
|
|
3090
|
+
const geometry = evt.feature.getGeometry();
|
|
3091
|
+
const ring = geometry ? geometry.getCoordinates()[0] : [];
|
|
3092
|
+
// Ignore degenerate lassos (a simple click without dragging)
|
|
3093
|
+
if (!ring.length || ring.length < 4 || geometry.getArea() <= 0) {
|
|
3094
|
+
return;
|
|
3095
|
+
}
|
|
3096
|
+
// Without shift, replace the current selection
|
|
3097
|
+
if (!this._shiftPressed) {
|
|
3098
|
+
this._collectionModify.clear();
|
|
3099
|
+
}
|
|
3100
|
+
this._selectByGeometry(geometry);
|
|
3101
|
+
});
|
|
3102
|
+
this._map.addInteraction(this._interactionFreehandSelect);
|
|
3103
|
+
this._refreshSelectionInteractions();
|
|
3104
|
+
}
|
|
3105
|
+
/**
|
|
3106
|
+
* Whether a selection tool mode is currently usable: only outside the
|
|
3107
|
+
* edit/draw modes.
|
|
3108
|
+
*
|
|
3109
|
+
* @param mode
|
|
3110
|
+
* @private
|
|
3111
|
+
*/
|
|
3112
|
+
_isSelectMode(mode) {
|
|
3113
|
+
return getMode() === null && getSelectionMode() === mode;
|
|
3114
|
+
}
|
|
3115
|
+
/**
|
|
3116
|
+
* Enable/disable the box and freehand selection interactions according to
|
|
3117
|
+
* the current selection tool and map mode.
|
|
3118
|
+
*
|
|
3119
|
+
* @private
|
|
3120
|
+
*/
|
|
3121
|
+
_refreshSelectionInteractions() {
|
|
3122
|
+
const active = getMode() === null;
|
|
3123
|
+
if (this._interactionDragBox) {
|
|
3124
|
+
this._interactionDragBox.setActive(active && getSelectionMode() === SelectionMode.Box);
|
|
3125
|
+
}
|
|
3126
|
+
if (this._interactionFreehandSelect) {
|
|
3127
|
+
this._interactionFreehandSelect.setActive(active && getSelectionMode() === SelectionMode.Freehand);
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
/**
|
|
3131
|
+
* Select the features that intersect the given polygon on the active layer
|
|
3132
|
+
* and add them to the edit list. WFS features are picked from the already
|
|
3133
|
+
* loaded source, while WMS features are requested to the GeoServer with a
|
|
3134
|
+
* WFS GetFeature INTERSECTS filter.
|
|
3135
|
+
*
|
|
3136
|
+
* @param geometry
|
|
3137
|
+
* @private
|
|
3138
|
+
*/
|
|
3139
|
+
async _selectByGeometry(geometry) {
|
|
3140
|
+
const layer = getActiveLayerToInsertEls();
|
|
3141
|
+
if (!layer || !layer.getVisible() || !layer.isVisibleByZoom()) {
|
|
3142
|
+
showError(I18N.errors.layerNotVisible);
|
|
3143
|
+
return;
|
|
3144
|
+
}
|
|
3145
|
+
let features;
|
|
3146
|
+
if (layer instanceof WfsLayer) {
|
|
3147
|
+
const candidates = layer
|
|
3148
|
+
.getSource()
|
|
3149
|
+
.getFeaturesInExtent(geometry.getExtent());
|
|
3150
|
+
// The extent is only a bounding box; for the freehand lasso be
|
|
3151
|
+
// more precise and keep the features whose vertices fall inside it
|
|
3152
|
+
if (getSelectionMode() === SelectionMode.Freehand) {
|
|
3153
|
+
features = candidates.filter((feature) => {
|
|
3154
|
+
const featureGeom = feature.getGeometry();
|
|
3155
|
+
if (!featureGeom) {
|
|
3156
|
+
return false;
|
|
3157
|
+
}
|
|
3158
|
+
const flatCoords = featureGeom.getFlatCoordinates();
|
|
3159
|
+
const stride = featureGeom.getStride();
|
|
3160
|
+
for (let i = 0; i < flatCoords.length; i += stride) {
|
|
3161
|
+
if (geometry.intersectsCoordinate([
|
|
3162
|
+
flatCoords[i],
|
|
3163
|
+
flatCoords[i + 1]
|
|
3164
|
+
])) {
|
|
3165
|
+
return true;
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
return false;
|
|
3169
|
+
});
|
|
3170
|
+
}
|
|
3171
|
+
else {
|
|
3172
|
+
features = candidates;
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
else if (layer instanceof WmsLayer) {
|
|
3176
|
+
features = await layer._getFeaturesInGeometry(geometry);
|
|
3177
|
+
}
|
|
3178
|
+
if (features) {
|
|
3179
|
+
this._selectFeatures(features);
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
/**
|
|
3183
|
+
* Add the given features to the edit mode:
|
|
3184
|
+
* remove them from their original layer and push them into the edit
|
|
3185
|
+
* collection so they can be modified/deleted like the ones selected
|
|
3186
|
+
* with a single click.
|
|
3187
|
+
*
|
|
3188
|
+
* @param features
|
|
3189
|
+
* @private
|
|
3190
|
+
*/
|
|
3191
|
+
_selectFeatures(features) {
|
|
3192
|
+
if (!features || !features.length) {
|
|
3193
|
+
return;
|
|
3194
|
+
}
|
|
3195
|
+
const layer = getActiveLayerToInsertEls();
|
|
3196
|
+
const layerName = layer.get(BaseLayerProperty.NAME);
|
|
3197
|
+
const selectedIds = new Set(this._collectionModify.getArray().map((feature) => feature.getId()));
|
|
3198
|
+
features.forEach((feature) => {
|
|
3199
|
+
if (isFeatureEdited(feature) || selectedIds.has(feature.getId())) {
|
|
3200
|
+
return;
|
|
3201
|
+
}
|
|
3202
|
+
// WFS features are stored in their layer source: remove them from
|
|
3203
|
+
// there while they are being edited
|
|
3204
|
+
if (layer instanceof WfsLayer &&
|
|
3205
|
+
layer.getSource().hasFeature(feature)) {
|
|
3206
|
+
layer.getSource().removeFeature(feature);
|
|
3207
|
+
}
|
|
3208
|
+
this._addFeatureToEditMode(feature, null, layerName);
|
|
3209
|
+
});
|
|
3210
|
+
this._lockSelectedFeatures();
|
|
2406
3211
|
}
|
|
2407
3212
|
/**
|
|
2408
3213
|
* Layer to store temporary the elements to be edited
|
|
@@ -2411,6 +3216,166 @@ class Wfst extends Control {
|
|
|
2411
3216
|
_prepareEditLayer() {
|
|
2412
3217
|
this._map.addLayer(getEditLayer());
|
|
2413
3218
|
}
|
|
3219
|
+
/**
|
|
3220
|
+
* Create the dedicated layer used to highlight the queried feature, so
|
|
3221
|
+
* the source layers keep their own rendering untouched.
|
|
3222
|
+
* @private
|
|
3223
|
+
*/
|
|
3224
|
+
_prepareHighlightLayer() {
|
|
3225
|
+
this._highlightSource = new VectorSource();
|
|
3226
|
+
this._highlightLayer = new VectorLayer({
|
|
3227
|
+
source: this._highlightSource,
|
|
3228
|
+
style: (feature) => queryStyleFunction(),
|
|
3229
|
+
zIndex: 100
|
|
3230
|
+
});
|
|
3231
|
+
this._map.addLayer(this._highlightLayer);
|
|
3232
|
+
}
|
|
3233
|
+
/**
|
|
3234
|
+
* Listen for clicks while the query mode is active: get the clicked
|
|
3235
|
+
* feature of the active layer (from the already loaded source for WFS,
|
|
3236
|
+
* or through a GetFeatureInfo request for WMS), highlight it and show
|
|
3237
|
+
* its attributes in a popup.
|
|
3238
|
+
* @private
|
|
3239
|
+
*/
|
|
3240
|
+
_addQueryInteraction() {
|
|
3241
|
+
this._keyClickQuery = this._map.on(this._options.evtType || 'singleclick', async (evt) => {
|
|
3242
|
+
if (getMode() !== Modes.Query) {
|
|
3243
|
+
return;
|
|
3244
|
+
}
|
|
3245
|
+
const layer = getActiveLayerToInsertEls();
|
|
3246
|
+
// Skip hidden layers
|
|
3247
|
+
if (!layer.getVisible() || !layer.isVisibleByZoom()) {
|
|
3248
|
+
return;
|
|
3249
|
+
}
|
|
3250
|
+
let feature = null;
|
|
3251
|
+
if (layer instanceof WfsLayer) {
|
|
3252
|
+
const featuresAtPixel = this._map.getFeaturesAtPixel(evt.pixel, {
|
|
3253
|
+
hitTolerance: 10,
|
|
3254
|
+
layerFilter: (candidate) => candidate === layer
|
|
3255
|
+
});
|
|
3256
|
+
if (featuresAtPixel && featuresAtPixel.length) {
|
|
3257
|
+
feature = featuresAtPixel[0];
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3260
|
+
else if (layer instanceof WmsLayer) {
|
|
3261
|
+
const features = await layer._getFeaturesByClickEvent(evt);
|
|
3262
|
+
if (features === null || features === void 0 ? void 0 : features.length) {
|
|
3263
|
+
feature = features[0];
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
// Click on empty space: clear the previous result
|
|
3267
|
+
if (!feature) {
|
|
3268
|
+
this._clearQueryResult();
|
|
3269
|
+
return;
|
|
3270
|
+
}
|
|
3271
|
+
this._showQueryResult(feature, layer, evt.coordinate);
|
|
3272
|
+
});
|
|
3273
|
+
}
|
|
3274
|
+
/**
|
|
3275
|
+
* Highlight the given feature and attach a popup with its attributes.
|
|
3276
|
+
* @param feature
|
|
3277
|
+
* @param layer
|
|
3278
|
+
* @param coordinate
|
|
3279
|
+
* @private
|
|
3280
|
+
*/
|
|
3281
|
+
_showQueryResult(feature, layer, coordinate) {
|
|
3282
|
+
this._clearQueryResult();
|
|
3283
|
+
this._highlightSource.addFeature(feature);
|
|
3284
|
+
this._queryOverlay = new QueryOverlay(feature, coordinate, layer);
|
|
3285
|
+
// @ts-expect-error
|
|
3286
|
+
this._queryOverlay.on('close', () => {
|
|
3287
|
+
this._clearQueryResult();
|
|
3288
|
+
});
|
|
3289
|
+
this._map.addOverlay(this._queryOverlay);
|
|
3290
|
+
}
|
|
3291
|
+
/**
|
|
3292
|
+
* Remove the query popup overlay if any.
|
|
3293
|
+
* @private
|
|
3294
|
+
*/
|
|
3295
|
+
_removeQueryOverlay() {
|
|
3296
|
+
if (this._queryOverlay) {
|
|
3297
|
+
this._map.removeOverlay(this._queryOverlay);
|
|
3298
|
+
this._queryOverlay = null;
|
|
3299
|
+
return;
|
|
3300
|
+
}
|
|
3301
|
+
const overlay = this._map.getOverlayById(this._queryOverlayId);
|
|
3302
|
+
if (overlay) {
|
|
3303
|
+
this._map.removeOverlay(overlay);
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
/**
|
|
3307
|
+
* Clear the highlight and remove the query popup.
|
|
3308
|
+
* @private
|
|
3309
|
+
*/
|
|
3310
|
+
_clearQueryResult() {
|
|
3311
|
+
var _a;
|
|
3312
|
+
(_a = this._highlightSource) === null || _a === void 0 ? void 0 : _a.clear();
|
|
3313
|
+
this._removeQueryOverlay();
|
|
3314
|
+
}
|
|
3315
|
+
/**
|
|
3316
|
+
* Activate/deactivate the query mode: while it is active, clicking a
|
|
3317
|
+
* feature of the active layer shows its info in a popup instead of
|
|
3318
|
+
* selecting it for editing.
|
|
3319
|
+
*
|
|
3320
|
+
* @param bool
|
|
3321
|
+
* @public
|
|
3322
|
+
*/
|
|
3323
|
+
activateQueryMode(bool = true) {
|
|
3324
|
+
var _a, _b;
|
|
3325
|
+
if (bool) {
|
|
3326
|
+
// While a geometry/fields edit is in progress the tools toolbar is
|
|
3327
|
+
// hidden)Skip, so the only way out is Apply, Discard or Delete.
|
|
3328
|
+
// Guard here too so programmatic calls cannot leave the edit mode.
|
|
3329
|
+
if (getMode() === Modes.Edit) {
|
|
3330
|
+
return;
|
|
3331
|
+
}
|
|
3332
|
+
// If features are merely selected (not being edited), deselect
|
|
3333
|
+
// them without transacting: each removal restores the feature to
|
|
3334
|
+
// its original layer and turns the edit mode off.
|
|
3335
|
+
if (this._collectionModify.getLength()) {
|
|
3336
|
+
this._collectionModify
|
|
3337
|
+
.getArray()
|
|
3338
|
+
.slice()
|
|
3339
|
+
.forEach((feature) => {
|
|
3340
|
+
this._collectionModify.remove(feature);
|
|
3341
|
+
});
|
|
3342
|
+
}
|
|
3343
|
+
// Leave any draw interaction active
|
|
3344
|
+
if (this._interactionDraw) {
|
|
3345
|
+
this._map.removeInteraction(this._interactionDraw);
|
|
3346
|
+
this._viewport.classList.remove('draw-mode');
|
|
3347
|
+
}
|
|
3348
|
+
resetStateButtons();
|
|
3349
|
+
deactivateSelectModeButton();
|
|
3350
|
+
activateQueryButton();
|
|
3351
|
+
this._clearHoverState();
|
|
3352
|
+
activateMode(Modes.Query);
|
|
3353
|
+
// Disable the edit selection interactions while querying
|
|
3354
|
+
if (this._interactionSelectModify) {
|
|
3355
|
+
this._interactionSelectModify.setActive(false);
|
|
3356
|
+
}
|
|
3357
|
+
if (this._interactionWfsSelect) {
|
|
3358
|
+
this._interactionWfsSelect.setActive(false);
|
|
3359
|
+
}
|
|
3360
|
+
(_a = this._interactionModify) === null || _a === void 0 ? void 0 : _a.setActive(false);
|
|
3361
|
+
this._syncExtendLineOverlays();
|
|
3362
|
+
this._refreshSelectionInteractions();
|
|
3363
|
+
}
|
|
3364
|
+
else {
|
|
3365
|
+
activateMode(null);
|
|
3366
|
+
deactivateQueryButton();
|
|
3367
|
+
activateSelectModeButton(getSelectionMode());
|
|
3368
|
+
if (this._interactionSelectModify) {
|
|
3369
|
+
this._interactionSelectModify.setActive(true);
|
|
3370
|
+
}
|
|
3371
|
+
if (this._interactionWfsSelect) {
|
|
3372
|
+
this._interactionWfsSelect.setActive(true);
|
|
3373
|
+
}
|
|
3374
|
+
(_b = this._interactionModify) === null || _b === void 0 ? void 0 : _b.setActive(true);
|
|
3375
|
+
this._refreshSelectionInteractions();
|
|
3376
|
+
}
|
|
3377
|
+
this._clearQueryResult();
|
|
3378
|
+
}
|
|
2414
3379
|
/**
|
|
2415
3380
|
* @private
|
|
2416
3381
|
*/
|
|
@@ -2425,16 +3390,74 @@ class Wfst extends Control {
|
|
|
2425
3390
|
return;
|
|
2426
3391
|
}
|
|
2427
3392
|
if (key === 'Delete') {
|
|
2428
|
-
const selectedFeatures = this._collectionModify;
|
|
2429
|
-
if (selectedFeatures) {
|
|
2430
|
-
|
|
2431
|
-
this._deleteFeature(feature, true);
|
|
2432
|
-
});
|
|
3393
|
+
const selectedFeatures = this._collectionModify.getArray();
|
|
3394
|
+
if (selectedFeatures.length) {
|
|
3395
|
+
this._deleteFeature(selectedFeatures, true);
|
|
2433
3396
|
}
|
|
2434
3397
|
}
|
|
2435
3398
|
});
|
|
2436
3399
|
};
|
|
2437
3400
|
keyboardEvents();
|
|
3401
|
+
/**
|
|
3402
|
+
* Change the cursor to a pointing hand and highlight the feature
|
|
3403
|
+
* when hovering over a WFS feature of the active layer.
|
|
3404
|
+
* @private
|
|
3405
|
+
*/
|
|
3406
|
+
const pointerMoveHandler = (evt) => {
|
|
3407
|
+
if (evt.dragging) {
|
|
3408
|
+
return;
|
|
3409
|
+
}
|
|
3410
|
+
// In edit mode, give pointer feedback on the editable
|
|
3411
|
+
// vertices/segments
|
|
3412
|
+
if (getMode() === Modes.Edit) {
|
|
3413
|
+
this._clearHoverState();
|
|
3414
|
+
this._updateEditCursor(evt);
|
|
3415
|
+
return;
|
|
3416
|
+
}
|
|
3417
|
+
// Only show the hover outside edit and draw modes
|
|
3418
|
+
if (getMode() !== null) {
|
|
3419
|
+
this._clearHoverState();
|
|
3420
|
+
return;
|
|
3421
|
+
}
|
|
3422
|
+
const layer = getActiveLayerToInsertEls();
|
|
3423
|
+
// Hover only works on visible WFS layers
|
|
3424
|
+
if (!layer ||
|
|
3425
|
+
!layer.getVisible() ||
|
|
3426
|
+
!layer.isVisibleByZoom() ||
|
|
3427
|
+
!(layer instanceof WfsLayer)) {
|
|
3428
|
+
this._clearHoverState();
|
|
3429
|
+
return;
|
|
3430
|
+
}
|
|
3431
|
+
const features = this._map.getFeaturesAtPixel(evt.pixel, {
|
|
3432
|
+
hitTolerance: 10,
|
|
3433
|
+
layerFilter: (candidate) => candidate === layer
|
|
3434
|
+
});
|
|
3435
|
+
if (features && features.length) {
|
|
3436
|
+
const feature = features[0];
|
|
3437
|
+
if (feature !== this._hoveredFeature) {
|
|
3438
|
+
this._clearHoverState();
|
|
3439
|
+
this._hoveredFeature = feature;
|
|
3440
|
+
this._hoveredFeature.setStyle((f) => styleFunction(f, true));
|
|
3441
|
+
}
|
|
3442
|
+
if (this._viewport.style.cursor !== 'pointer') {
|
|
3443
|
+
this._viewport.style.cursor = 'pointer';
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
else {
|
|
3447
|
+
this._clearHoverState();
|
|
3448
|
+
}
|
|
3449
|
+
};
|
|
3450
|
+
this._map.on('pointermove', pointerMoveHandler);
|
|
3451
|
+
// Store the shift state when a gesture starts, so the freehand
|
|
3452
|
+
// selection can decide between adding or replacing the selection
|
|
3453
|
+
this._viewport.addEventListener('pointerdown', (evt) => {
|
|
3454
|
+
this._shiftPressed = evt.shiftKey;
|
|
3455
|
+
});
|
|
3456
|
+
// Clear the hover before firing a click, otherwise the Select interaction
|
|
3457
|
+
// would capture the hover style as the original style of the feature and
|
|
3458
|
+
// restore it on deselect, leaving the highlight permanently enabled.
|
|
3459
|
+
this._viewport.addEventListener('pointerdown', () => this._clearHoverState());
|
|
3460
|
+
this._viewport.addEventListener('pointerleave', () => this._clearHoverState(), { once: false });
|
|
2438
3461
|
this._map.on('moveend', () => {
|
|
2439
3462
|
this._currentZoom = this._view.getZoom();
|
|
2440
3463
|
if (this._currentZoom !== this._lastZoom) {
|
|
@@ -2458,6 +3481,62 @@ class Wfst extends Control {
|
|
|
2458
3481
|
}
|
|
2459
3482
|
});
|
|
2460
3483
|
}
|
|
3484
|
+
/**
|
|
3485
|
+
* Allow panning with the middle mouse button at all times, even when the
|
|
3486
|
+
* draw/freehand/box interactions are consuming the left button. The
|
|
3487
|
+
* middle pointerdown is intercepted in the capture phase so OpenLayers
|
|
3488
|
+
* never processes it and the active tool is not affected.
|
|
3489
|
+
*
|
|
3490
|
+
* @private
|
|
3491
|
+
*/
|
|
3492
|
+
_addMiddleButtonPan() {
|
|
3493
|
+
const viewport = this._viewport;
|
|
3494
|
+
let panning = false;
|
|
3495
|
+
let lastX = 0;
|
|
3496
|
+
let lastY = 0;
|
|
3497
|
+
const isMiddleButton = (evt) => evt.pointerType === 'mouse' && evt.button === 1;
|
|
3498
|
+
const stopPanning = () => {
|
|
3499
|
+
panning = false;
|
|
3500
|
+
};
|
|
3501
|
+
viewport.addEventListener('pointerdown', (evt) => {
|
|
3502
|
+
if (!isMiddleButton(evt)) {
|
|
3503
|
+
return;
|
|
3504
|
+
}
|
|
3505
|
+
evt.preventDefault();
|
|
3506
|
+
evt.stopPropagation();
|
|
3507
|
+
viewport.setPointerCapture(evt.pointerId);
|
|
3508
|
+
panning = true;
|
|
3509
|
+
lastX = evt.clientX;
|
|
3510
|
+
lastY = evt.clientY;
|
|
3511
|
+
if (this._view.getAnimating()) {
|
|
3512
|
+
this._view.cancelAnimations();
|
|
3513
|
+
}
|
|
3514
|
+
}, true);
|
|
3515
|
+
viewport.addEventListener('pointermove', (evt) => {
|
|
3516
|
+
if (!panning) {
|
|
3517
|
+
return;
|
|
3518
|
+
}
|
|
3519
|
+
evt.preventDefault();
|
|
3520
|
+
const delta = [lastX - evt.clientX, evt.clientY - lastY];
|
|
3521
|
+
lastX = evt.clientX;
|
|
3522
|
+
lastY = evt.clientY;
|
|
3523
|
+
const resolution = this._view.getResolution();
|
|
3524
|
+
const rotation = this._view.getRotation();
|
|
3525
|
+
const cosAngle = Math.cos(rotation);
|
|
3526
|
+
const sinAngle = Math.sin(rotation);
|
|
3527
|
+
const x = (delta[0] * cosAngle - delta[1] * sinAngle) * resolution;
|
|
3528
|
+
const y = (delta[1] * cosAngle + delta[0] * sinAngle) * resolution;
|
|
3529
|
+
this._view.adjustCenterInternal([x, y]);
|
|
3530
|
+
});
|
|
3531
|
+
const endPan = () => {
|
|
3532
|
+
if (!panning) {
|
|
3533
|
+
return;
|
|
3534
|
+
}
|
|
3535
|
+
stopPanning();
|
|
3536
|
+
};
|
|
3537
|
+
viewport.addEventListener('pointerup', endPan);
|
|
3538
|
+
viewport.addEventListener('pointercancel', endPan);
|
|
3539
|
+
}
|
|
2461
3540
|
/**
|
|
2462
3541
|
* Add map handlers
|
|
2463
3542
|
* @private
|
|
@@ -2467,13 +3546,19 @@ class Wfst extends Control {
|
|
|
2467
3546
|
// This prevent events fired on select and deselect features that has no changes and should
|
|
2468
3547
|
// not be updated in the geoserver
|
|
2469
3548
|
this._interactionModify.on('modifyend', (evt) => {
|
|
2470
|
-
|
|
2471
|
-
|
|
3549
|
+
evt.features.forEach((feature) => {
|
|
3550
|
+
addFeatureToEditedList(feature);
|
|
3551
|
+
});
|
|
2472
3552
|
super.dispatchEvent(evt);
|
|
2473
3553
|
});
|
|
2474
3554
|
this._interactionModify.on('modifystart', (evt) => {
|
|
2475
3555
|
super.dispatchEvent(evt);
|
|
2476
3556
|
});
|
|
3557
|
+
// Keep the "continue drawing" buttons glued to the line endpoints
|
|
3558
|
+
// @ts-expect-error - 'modify' is a valid Modify event not typed in the `on` signature
|
|
3559
|
+
this._interactionModify.on('modify', () => {
|
|
3560
|
+
this._updateExtendOverlayPositions();
|
|
3561
|
+
});
|
|
2477
3562
|
this._onDeselectFeatureEvent();
|
|
2478
3563
|
this._onRemoveFeatureEvent();
|
|
2479
3564
|
}
|
|
@@ -2485,6 +3570,9 @@ class Wfst extends Control {
|
|
|
2485
3570
|
this._layersControl = new LayersControl(this._options.showUpload ? this._uploads : null, this._options.uploadFormats);
|
|
2486
3571
|
// @ts-expect-error
|
|
2487
3572
|
this._layersControl.on('drawMode', () => {
|
|
3573
|
+
if (getMode() === Modes.Query) {
|
|
3574
|
+
this.activateQueryMode(false);
|
|
3575
|
+
}
|
|
2488
3576
|
if (getMode() === Modes.Draw) {
|
|
2489
3577
|
resetStateButtons();
|
|
2490
3578
|
this.activateEditMode();
|
|
@@ -2511,9 +3599,37 @@ class Wfst extends Control {
|
|
|
2511
3599
|
this.activateDrawMode(getActiveLayerToInsertEls());
|
|
2512
3600
|
}
|
|
2513
3601
|
});
|
|
3602
|
+
[
|
|
3603
|
+
['selectModeSingle', SelectionMode.Single],
|
|
3604
|
+
['selectModeBox', SelectionMode.Box],
|
|
3605
|
+
['selectModeFreehand', SelectionMode.Freehand]
|
|
3606
|
+
].forEach(([evtName, mode]) => {
|
|
3607
|
+
// @ts-expect-error
|
|
3608
|
+
this._layersControl.on(evtName, () => {
|
|
3609
|
+
// Leaving the draw mode when a select tool is chosen
|
|
3610
|
+
if (getMode() === Modes.Draw) {
|
|
3611
|
+
resetStateButtons();
|
|
3612
|
+
this.activateEditMode();
|
|
3613
|
+
}
|
|
3614
|
+
// Leaving the query mode when a select tool is chosen
|
|
3615
|
+
if (getMode() === Modes.Query) {
|
|
3616
|
+
this.activateQueryMode(false);
|
|
3617
|
+
}
|
|
3618
|
+
setSelectionMode(mode);
|
|
3619
|
+
activateSelectModeButton(mode);
|
|
3620
|
+
this._refreshSelectionInteractions();
|
|
3621
|
+
});
|
|
3622
|
+
});
|
|
3623
|
+
// @ts-expect-error
|
|
3624
|
+
this._layersControl.on('queryMode', () => {
|
|
3625
|
+
this.activateQueryMode(getMode() !== Modes.Query);
|
|
3626
|
+
});
|
|
2514
3627
|
const controlEl = this._layersControl.render();
|
|
2515
3628
|
this._selectDraw = controlEl.querySelector('.wfst--tools-control--select-draw');
|
|
2516
|
-
this._controlWidgetToolsDiv.append(controlEl);
|
|
3629
|
+
this._controlWidgetToolsDiv.append(controlEl.querySelector('.wfst--tools-control--head'));
|
|
3630
|
+
if (this._options.layers.length > 1) {
|
|
3631
|
+
this._layersWidgetDiv.append(controlEl.querySelector('.wfst--tools-control--select-layers'));
|
|
3632
|
+
}
|
|
2517
3633
|
}
|
|
2518
3634
|
/**
|
|
2519
3635
|
*
|
|
@@ -2532,6 +3648,9 @@ class Wfst extends Control {
|
|
|
2532
3648
|
_restoreFeatureToLayer(feature, layerName) {
|
|
2533
3649
|
layerName = layerName || feature.get('_layerName_');
|
|
2534
3650
|
const layer = getStoredMapLayers()[layerName];
|
|
3651
|
+
// Reset any per-feature style (e.g. hover) before returning the feature
|
|
3652
|
+
// to its layer, so it renders with the layer's default style.
|
|
3653
|
+
feature.setStyle(undefined);
|
|
2535
3654
|
layer.getSource().addFeature(feature);
|
|
2536
3655
|
}
|
|
2537
3656
|
/**
|
|
@@ -2565,10 +3684,18 @@ class Wfst extends Control {
|
|
|
2565
3684
|
this._removeFeatureFromTmpLayer(feature);
|
|
2566
3685
|
}
|
|
2567
3686
|
};
|
|
3687
|
+
if (this._keySelectBound) {
|
|
3688
|
+
return;
|
|
3689
|
+
}
|
|
3690
|
+
this._keySelectBound = true;
|
|
2568
3691
|
// This is fired when a feature is deselected and fires the transaction process
|
|
3692
|
+
if (this._keySelect) {
|
|
3693
|
+
unByKey(this._keySelect);
|
|
3694
|
+
}
|
|
2569
3695
|
this._keySelect = this._collectionModify.on('remove', (evt) => {
|
|
2570
3696
|
const feature = evt.element;
|
|
2571
3697
|
this._deselectEditFeature(feature);
|
|
3698
|
+
this._syncEditOverlays();
|
|
2572
3699
|
checkIfFeatureIsChanged(feature);
|
|
2573
3700
|
this._editModeOff();
|
|
2574
3701
|
});
|
|
@@ -2589,6 +3716,7 @@ class Wfst extends Control {
|
|
|
2589
3716
|
}
|
|
2590
3717
|
if (this._keySelect) {
|
|
2591
3718
|
unByKey(this._keySelect);
|
|
3719
|
+
this._keySelectBound = false;
|
|
2592
3720
|
}
|
|
2593
3721
|
const layerName = feature.get('_layerName_');
|
|
2594
3722
|
const ll = this.getLayerByName(layerName);
|
|
@@ -2603,28 +3731,80 @@ class Wfst extends Control {
|
|
|
2603
3731
|
});
|
|
2604
3732
|
}
|
|
2605
3733
|
/**
|
|
3734
|
+
* Hide the whole tools toolbar (geometry/draw/select/query buttons and
|
|
3735
|
+
* the layer selector). It is hidden while a geometry/fields edit is in
|
|
3736
|
+
* progress so the only way out is Apply, Discard or Delete.
|
|
2606
3737
|
*
|
|
2607
|
-
* @param feature
|
|
2608
3738
|
* @private
|
|
2609
3739
|
*/
|
|
2610
|
-
|
|
2611
|
-
|
|
3740
|
+
_hideControls() {
|
|
3741
|
+
// While an edit is in progress the tools toolbar and the layers widget
|
|
3742
|
+
// disappear together: the only controls left on screen are Apply,
|
|
3743
|
+
// Discard and Delete.
|
|
3744
|
+
this._controlWidgetToolsDiv.classList.add('ol-wfst--controls--hidden');
|
|
3745
|
+
this._layersWidgetDiv.classList.add('ol-wfst--controls--hidden');
|
|
3746
|
+
}
|
|
3747
|
+
/**
|
|
3748
|
+
* Show the tools toolbar again.
|
|
3749
|
+
*
|
|
3750
|
+
* @private
|
|
3751
|
+
*/
|
|
3752
|
+
_showControls() {
|
|
3753
|
+
this._controlWidgetToolsDiv.classList.remove('ol-wfst--controls--hidden');
|
|
3754
|
+
this._layersWidgetDiv.classList.remove('ol-wfst--controls--hidden');
|
|
3755
|
+
}
|
|
3756
|
+
/**
|
|
3757
|
+
*
|
|
3758
|
+
* @param features
|
|
3759
|
+
* @private
|
|
3760
|
+
*/
|
|
3761
|
+
_editModeOn(features) {
|
|
3762
|
+
features = Array.isArray(features) ? features : [features];
|
|
3763
|
+
features.forEach((feature) => {
|
|
3764
|
+
this._editFeaturesOriginal[String(feature.getId())] =
|
|
3765
|
+
feature.clone();
|
|
3766
|
+
});
|
|
2612
3767
|
activateMode(Modes.Edit);
|
|
3768
|
+
// While a geometry/fields edit is in progress the whole tools toolbar
|
|
3769
|
+
// and the layers widget are hidden together, so the only way out is
|
|
3770
|
+
// Apply, Discard or Delete
|
|
3771
|
+
this._hideControls();
|
|
3772
|
+
// Show the "continue drawing" buttons on the line endpoints
|
|
3773
|
+
this._syncExtendLineOverlays();
|
|
2613
3774
|
// To refresh the style
|
|
2614
3775
|
getEditLayer().getSource().changed();
|
|
2615
|
-
this.
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
3776
|
+
this._refreshSelectionInteractions();
|
|
3777
|
+
const multiOverlay = this._map.getOverlayById(this._multiOverlayId);
|
|
3778
|
+
if (multiOverlay) {
|
|
3779
|
+
this._map.removeOverlay(multiOverlay);
|
|
3780
|
+
}
|
|
3781
|
+
features.forEach((feature) => {
|
|
3782
|
+
this._removeOverlayHelper(feature);
|
|
2621
3783
|
});
|
|
2622
|
-
this._controlApplyDiscardChanges
|
|
3784
|
+
this._controlApplyDiscardChanges = new EditControlChangesEl(features);
|
|
3785
|
+
this._controlApplyDiscardChanges.on('cancel', (evt) => {
|
|
3786
|
+
const list = evt.features;
|
|
3787
|
+
Array.from(list).forEach((feature) => {
|
|
3788
|
+
const original = this._editFeaturesOriginal[String(feature.getId())];
|
|
3789
|
+
if (original) {
|
|
3790
|
+
feature.setGeometry(original.getGeometry());
|
|
3791
|
+
}
|
|
3792
|
+
removeFeatureFromEditList(feature);
|
|
3793
|
+
this._collectionModify.remove(feature);
|
|
3794
|
+
});
|
|
3795
|
+
});
|
|
3796
|
+
this._controlApplyDiscardChanges.on('apply', async (evt) => {
|
|
2623
3797
|
showLoading();
|
|
2624
|
-
|
|
3798
|
+
const list = evt.features;
|
|
3799
|
+
const ok = await this._transactEditList(list);
|
|
3800
|
+
if (ok) {
|
|
3801
|
+
Array.from(list).forEach((feature) => {
|
|
3802
|
+
this._collectionModify.remove(feature);
|
|
3803
|
+
});
|
|
3804
|
+
}
|
|
2625
3805
|
});
|
|
2626
|
-
this._controlApplyDiscardChanges.on('delete', (
|
|
2627
|
-
this._deleteFeature(
|
|
3806
|
+
this._controlApplyDiscardChanges.on('delete', (evt) => {
|
|
3807
|
+
this._deleteFeature(evt.features, true);
|
|
2628
3808
|
});
|
|
2629
3809
|
this._map.addControl(this._controlApplyDiscardChanges);
|
|
2630
3810
|
}
|
|
@@ -2632,33 +3812,343 @@ class Wfst extends Control {
|
|
|
2632
3812
|
* @private
|
|
2633
3813
|
*/
|
|
2634
3814
|
_editModeOff() {
|
|
3815
|
+
// Restore the tools toolbar and the layers widget together
|
|
3816
|
+
this._showControls();
|
|
2635
3817
|
activateMode(null);
|
|
3818
|
+
this._destroyExtendLineOverlays();
|
|
2636
3819
|
this._map.removeControl(this._controlApplyDiscardChanges);
|
|
3820
|
+
this._refreshSelectionInteractions();
|
|
3821
|
+
}
|
|
3822
|
+
/**
|
|
3823
|
+
* Get the first and last coordinate of every line component of a
|
|
3824
|
+
* LineString/MultiLineString geometry, including the line components
|
|
3825
|
+
* nested inside a GeometryCollection. Anything else returns an empty
|
|
3826
|
+
* array.
|
|
3827
|
+
*
|
|
3828
|
+
* @param geometry
|
|
3829
|
+
* @private
|
|
3830
|
+
*/
|
|
3831
|
+
_getLineEndPoints(geometry) {
|
|
3832
|
+
const endPoints = [];
|
|
3833
|
+
const appendLine = (line, collection) => {
|
|
3834
|
+
if (line instanceof LineString) {
|
|
3835
|
+
if (line.getCoordinates().length) {
|
|
3836
|
+
endPoints.push({
|
|
3837
|
+
first: line.getFirstCoordinate(),
|
|
3838
|
+
last: line.getLastCoordinate(),
|
|
3839
|
+
line,
|
|
3840
|
+
lineIndex: 0,
|
|
3841
|
+
collection
|
|
3842
|
+
});
|
|
3843
|
+
}
|
|
3844
|
+
return;
|
|
3845
|
+
}
|
|
3846
|
+
line.getCoordinates().forEach((coordinates, lineIndex) => {
|
|
3847
|
+
if (coordinates.length) {
|
|
3848
|
+
endPoints.push({
|
|
3849
|
+
first: coordinates[0],
|
|
3850
|
+
last: coordinates[coordinates.length - 1],
|
|
3851
|
+
line,
|
|
3852
|
+
lineIndex,
|
|
3853
|
+
collection
|
|
3854
|
+
});
|
|
3855
|
+
}
|
|
3856
|
+
});
|
|
3857
|
+
};
|
|
3858
|
+
// "getGeometries()" returns deep clones (see ol GeometryCollection),
|
|
3859
|
+
// but the extend/merge must work on the actual members.
|
|
3860
|
+
const collect = (geometry, collection) => {
|
|
3861
|
+
if (geometry instanceof LineString ||
|
|
3862
|
+
geometry instanceof MultiLineString$1) {
|
|
3863
|
+
appendLine(geometry, collection);
|
|
3864
|
+
}
|
|
3865
|
+
else if (geometry instanceof GeometryCollection$1) {
|
|
3866
|
+
geometry.getGeometriesArray().forEach((member) => {
|
|
3867
|
+
collect(member, geometry);
|
|
3868
|
+
});
|
|
3869
|
+
}
|
|
3870
|
+
};
|
|
3871
|
+
collect(geometry);
|
|
3872
|
+
return endPoints;
|
|
3873
|
+
}
|
|
3874
|
+
/**
|
|
3875
|
+
* Remove every "continue drawing" overlay and abort any active line
|
|
3876
|
+
* extension draw.
|
|
3877
|
+
*
|
|
3878
|
+
* @private
|
|
3879
|
+
*/
|
|
3880
|
+
_destroyExtendLineOverlays() {
|
|
3881
|
+
if (this._extendDraw) {
|
|
3882
|
+
this._finishExtendDraw();
|
|
3883
|
+
}
|
|
3884
|
+
this._extendOverlays.forEach((overlay) => {
|
|
3885
|
+
this._map.removeOverlay(overlay);
|
|
3886
|
+
});
|
|
3887
|
+
this._extendOverlays = [];
|
|
3888
|
+
}
|
|
3889
|
+
/**
|
|
3890
|
+
* Keep the "continue drawing" overlays in sync with the current edit
|
|
3891
|
+
* selection: they are only shown while editing LineString/MultiLineString
|
|
3892
|
+
* features, and they are repositioned on the endpoints of each line
|
|
3893
|
+
* component.
|
|
3894
|
+
*
|
|
3895
|
+
* @private
|
|
3896
|
+
*/
|
|
3897
|
+
_syncExtendLineOverlays() {
|
|
3898
|
+
if (getMode() !== Modes.Edit || !this._collectionModify) {
|
|
3899
|
+
this._destroyExtendLineOverlays();
|
|
3900
|
+
return;
|
|
3901
|
+
}
|
|
3902
|
+
const items = this._collectionModify.getArray();
|
|
3903
|
+
// Remove overlays whose feature was deselected or lost its line geometry
|
|
3904
|
+
const removed = [];
|
|
3905
|
+
this._extendOverlays.forEach((overlay) => {
|
|
3906
|
+
const stale = !items.includes(overlay.feature) ||
|
|
3907
|
+
overlay.component >=
|
|
3908
|
+
this._getLineEndPoints(overlay.feature.getGeometry())
|
|
3909
|
+
.length;
|
|
3910
|
+
if (stale) {
|
|
3911
|
+
this._map.removeOverlay(overlay);
|
|
3912
|
+
removed.push(overlay);
|
|
3913
|
+
}
|
|
3914
|
+
});
|
|
3915
|
+
this._extendOverlays = this._extendOverlays.filter((overlay) => !removed.includes(overlay));
|
|
3916
|
+
// Add overlays for new LineString/MultiLineString features
|
|
3917
|
+
items.forEach((feature) => {
|
|
3918
|
+
this._getLineEndPoints(feature.getGeometry()).forEach(({ first, last }, component) => {
|
|
3919
|
+
['start', 'end'].forEach((side) => {
|
|
3920
|
+
const exists = this._extendOverlays.some((overlay) => overlay.feature === feature &&
|
|
3921
|
+
overlay.component === component &&
|
|
3922
|
+
overlay.side === side);
|
|
3923
|
+
if (exists) {
|
|
3924
|
+
return;
|
|
3925
|
+
}
|
|
3926
|
+
const overlay = new ExtendLineOverlay(feature, side === 'start' ? first : last, side, component, () => this._startExtendLineDraw(feature, side, component));
|
|
3927
|
+
this._extendOverlays.push(overlay);
|
|
3928
|
+
this._map.addOverlay(overlay);
|
|
3929
|
+
});
|
|
3930
|
+
});
|
|
3931
|
+
});
|
|
3932
|
+
this._updateExtendOverlayPositions();
|
|
3933
|
+
}
|
|
3934
|
+
/**
|
|
3935
|
+
* Move the "continue drawing" overlays to the current position of their
|
|
3936
|
+
* line endpoints, e.g. after a vertex is dragged with the Modify
|
|
3937
|
+
* interaction.
|
|
3938
|
+
*
|
|
3939
|
+
* @private
|
|
3940
|
+
*/
|
|
3941
|
+
_updateExtendOverlayPositions() {
|
|
3942
|
+
this._extendOverlays.forEach((overlay) => {
|
|
3943
|
+
const point = this._getLineEndPoints(overlay.feature.getGeometry())[overlay.component];
|
|
3944
|
+
if (!point) {
|
|
3945
|
+
return;
|
|
3946
|
+
}
|
|
3947
|
+
overlay.setPosition(overlay.side === 'start' ? point.first : point.last);
|
|
3948
|
+
});
|
|
3949
|
+
}
|
|
3950
|
+
/**
|
|
3951
|
+
* Start drawing a continuation of a line from one of its endpoints. The
|
|
3952
|
+
* sketch is anchored on the clicked vertex, so the next clicks append the
|
|
3953
|
+
* new points. A double click (or clicking back on the last point) ends
|
|
3954
|
+
* the drawing and merges the segment into the feature.
|
|
3955
|
+
*
|
|
3956
|
+
* @param feature
|
|
3957
|
+
* @param side 'start' prepends the new segment, 'end' appends it
|
|
3958
|
+
* @param component index of the clicked line component
|
|
3959
|
+
* @private
|
|
3960
|
+
*/
|
|
3961
|
+
_startExtendLineDraw(feature, side, component = 0) {
|
|
3962
|
+
var _a;
|
|
3963
|
+
if (this._extendDraw || getMode() !== Modes.Edit) {
|
|
3964
|
+
return;
|
|
3965
|
+
}
|
|
3966
|
+
const points = this._getLineEndPoints(feature.getGeometry());
|
|
3967
|
+
if (!points.length || component >= points.length) {
|
|
3968
|
+
return;
|
|
3969
|
+
}
|
|
3970
|
+
const anchor = side === 'start' ? points[component].first : points[component].last;
|
|
3971
|
+
// While extending, the Modify/Select interactions must not grab the clicks
|
|
3972
|
+
(_a = this._interactionModify) === null || _a === void 0 ? void 0 : _a.setActive(false);
|
|
3973
|
+
if (this._interactionSelectModify) {
|
|
3974
|
+
this._interactionSelectModify.setActive(false);
|
|
3975
|
+
}
|
|
3976
|
+
if (this._interactionWfsSelect) {
|
|
3977
|
+
this._interactionWfsSelect.setActive(false);
|
|
3978
|
+
}
|
|
3979
|
+
this._extendDraw = new Draw({
|
|
3980
|
+
type: GeometryType.LineString,
|
|
3981
|
+
style: (f) => styleFunction(f),
|
|
3982
|
+
stopClick: true // To prevent firing a map/wms click
|
|
3983
|
+
});
|
|
3984
|
+
this._extendDraw.on('drawend', (evt) => {
|
|
3985
|
+
this._mergeExtendLine(feature, side, evt.feature, component);
|
|
3986
|
+
});
|
|
3987
|
+
this._extendDraw.on('drawabort', () => {
|
|
3988
|
+
this._finishExtendDraw();
|
|
3989
|
+
});
|
|
3990
|
+
this._map.addInteraction(this._extendDraw);
|
|
3991
|
+
// Anchor the sketch on the clicked vertex: the user only has to digitize
|
|
3992
|
+
// the continuation points (this also dispatches the "drawstart" event)
|
|
3993
|
+
this._extendDraw.appendCoordinates([anchor]);
|
|
2637
3994
|
}
|
|
2638
3995
|
/**
|
|
2639
|
-
*
|
|
3996
|
+
* Merge the just-drawn segment into the edited feature's line geometry and
|
|
3997
|
+
* restore the editing interactions.
|
|
2640
3998
|
*
|
|
2641
3999
|
* @param feature
|
|
4000
|
+
* @param side
|
|
4001
|
+
* @param sketch
|
|
4002
|
+
* @param component index of the extended line component
|
|
2642
4003
|
* @private
|
|
2643
4004
|
*/
|
|
2644
|
-
|
|
4005
|
+
_mergeExtendLine(feature, side, sketch, component = 0) {
|
|
4006
|
+
const points = this._getLineEndPoints(feature.getGeometry());
|
|
4007
|
+
const point = points[component];
|
|
4008
|
+
if (!point) {
|
|
4009
|
+
this._finishExtendDraw();
|
|
4010
|
+
return;
|
|
4011
|
+
}
|
|
4012
|
+
const segment = sketch.getGeometry().getCoordinates();
|
|
4013
|
+
const { line, lineIndex } = point;
|
|
4014
|
+
if (line instanceof LineString) {
|
|
4015
|
+
const coords = line.getCoordinates();
|
|
4016
|
+
if (side === 'start') {
|
|
4017
|
+
line.setCoordinates([...segment.slice(1).reverse(), ...coords]);
|
|
4018
|
+
}
|
|
4019
|
+
else {
|
|
4020
|
+
line.setCoordinates([...coords, ...segment.slice(1)]);
|
|
4021
|
+
}
|
|
4022
|
+
}
|
|
4023
|
+
else if (line instanceof MultiLineString$1) {
|
|
4024
|
+
const coords = line.getCoordinates();
|
|
4025
|
+
if (side === 'start') {
|
|
4026
|
+
coords[lineIndex] = [
|
|
4027
|
+
...segment.slice(1).reverse(),
|
|
4028
|
+
...coords[lineIndex]
|
|
4029
|
+
];
|
|
4030
|
+
}
|
|
4031
|
+
else {
|
|
4032
|
+
coords[lineIndex] = [...coords[lineIndex], ...segment.slice(1)];
|
|
4033
|
+
}
|
|
4034
|
+
line.setCoordinates(coords);
|
|
4035
|
+
}
|
|
4036
|
+
// Only mark the feature as edited when the segment added new vertices
|
|
4037
|
+
if (segment.length > 1) {
|
|
4038
|
+
addFeatureToEditedList(feature);
|
|
4039
|
+
}
|
|
4040
|
+
this._finishExtendDraw();
|
|
4041
|
+
this._syncExtendLineOverlays();
|
|
4042
|
+
}
|
|
4043
|
+
/**
|
|
4044
|
+
* Remove the active line extension draw and restore the interactions that
|
|
4045
|
+
* were disabled while drawing.
|
|
4046
|
+
*
|
|
4047
|
+
* @private
|
|
4048
|
+
*/
|
|
4049
|
+
_finishExtendDraw() {
|
|
4050
|
+
var _a;
|
|
4051
|
+
if (this._extendDraw) {
|
|
4052
|
+
this._map.removeInteraction(this._extendDraw);
|
|
4053
|
+
this._extendDraw = null;
|
|
4054
|
+
}
|
|
4055
|
+
if (getMode() !== Modes.Edit) {
|
|
4056
|
+
return;
|
|
4057
|
+
}
|
|
4058
|
+
(_a = this._interactionModify) === null || _a === void 0 ? void 0 : _a.setActive(true);
|
|
4059
|
+
if (this._interactionSelectModify) {
|
|
4060
|
+
this._interactionSelectModify.setActive(true);
|
|
4061
|
+
}
|
|
4062
|
+
if (this._interactionWfsSelect) {
|
|
4063
|
+
this._interactionWfsSelect.setActive(true);
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
4066
|
+
/**
|
|
4067
|
+
* Send all the given features in a single WFS-T Update transaction per
|
|
4068
|
+
* layer, so a multi selection is saved with only one request. The "edited"
|
|
4069
|
+
* flag is cleared before transacting, so a deselect fired while the
|
|
4070
|
+
* request is in flight does not trigger a duplicate transaction.
|
|
4071
|
+
*
|
|
4072
|
+
* @param list
|
|
4073
|
+
* @returns false when there is nothing to transact or the transaction fails
|
|
4074
|
+
* @private
|
|
4075
|
+
*/
|
|
4076
|
+
async _transactEditList(list) {
|
|
4077
|
+
if (!list.length) {
|
|
4078
|
+
return true;
|
|
4079
|
+
}
|
|
4080
|
+
const byLayer = {};
|
|
4081
|
+
list.forEach((feature) => {
|
|
4082
|
+
const layerName = feature.get('_layerName_');
|
|
4083
|
+
if (!byLayer[layerName]) {
|
|
4084
|
+
byLayer[layerName] = [];
|
|
4085
|
+
}
|
|
4086
|
+
byLayer[layerName].push(feature);
|
|
4087
|
+
});
|
|
4088
|
+
for (const layerName of Object.keys(byLayer)) {
|
|
4089
|
+
const layer = this._options.layers.find((layer) => layer.get(BaseLayerProperty.NAME) === layerName);
|
|
4090
|
+
if (!layer) {
|
|
4091
|
+
continue;
|
|
4092
|
+
}
|
|
4093
|
+
byLayer[layerName].forEach((feature) => {
|
|
4094
|
+
removeFeatureFromEditList(feature);
|
|
4095
|
+
});
|
|
4096
|
+
let ok;
|
|
4097
|
+
try {
|
|
4098
|
+
ok = await layer.transactFeatures(TransactionType.Update, byLayer[layerName]);
|
|
4099
|
+
}
|
|
4100
|
+
catch (err) {
|
|
4101
|
+
return false;
|
|
4102
|
+
}
|
|
4103
|
+
if (ok === false) {
|
|
4104
|
+
return false;
|
|
4105
|
+
}
|
|
4106
|
+
}
|
|
4107
|
+
return true;
|
|
4108
|
+
}
|
|
4109
|
+
/**
|
|
4110
|
+
* Remove features from the edit Layer and from the Geoserver
|
|
4111
|
+
*
|
|
4112
|
+
* @param feature
|
|
4113
|
+
* @param askToConfirm
|
|
4114
|
+
* @private
|
|
4115
|
+
*/
|
|
4116
|
+
_deleteFeature(feature, askToConfirm) {
|
|
2645
4117
|
const deleteEl = () => {
|
|
4118
|
+
var _a;
|
|
2646
4119
|
const features = Array.isArray(feature) ? feature : [feature];
|
|
2647
4120
|
features.forEach((feature) => {
|
|
2648
4121
|
feature.set('_delete_', true, true);
|
|
2649
4122
|
getEditLayer().getSource().removeFeature(feature);
|
|
2650
4123
|
});
|
|
2651
4124
|
this._collectionModify.clear();
|
|
2652
|
-
|
|
2653
|
-
|
|
4125
|
+
this._editModeOff();
|
|
4126
|
+
this._syncEditOverlays();
|
|
4127
|
+
const layerName = (_a = features[0]) === null || _a === void 0 ? void 0 : _a.get('_layerName_');
|
|
4128
|
+
const layer = layerName
|
|
4129
|
+
? this._options.layers.find((layer) => layer.get(BaseLayerProperty.NAME) === layerName)
|
|
4130
|
+
: null;
|
|
2654
4131
|
if (layer instanceof WfsLayer) {
|
|
2655
|
-
|
|
4132
|
+
features.forEach((feature) => {
|
|
4133
|
+
this._interactionWfsSelect.getFeatures().remove(feature);
|
|
4134
|
+
});
|
|
2656
4135
|
}
|
|
2657
4136
|
};
|
|
2658
|
-
if (
|
|
2659
|
-
const
|
|
2660
|
-
|
|
2661
|
-
|
|
4137
|
+
if (askToConfirm) {
|
|
4138
|
+
const features = Array.isArray(feature) ? feature : [feature];
|
|
4139
|
+
const message = features.length > 1
|
|
4140
|
+
? I18N_('confirmDeleteElements', features.length)
|
|
4141
|
+
: I18N.labels.confirmDelete;
|
|
4142
|
+
const confirmModal = Modal.confirm(message, Object.assign(Object.assign({}, this._options.modal), { footer: `
|
|
4143
|
+
<button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">
|
|
4144
|
+
${I18N.labels.cancel}
|
|
4145
|
+
</button>
|
|
4146
|
+
<button type="button" class="btn btn-sm btn-danger" data-action="delete" data-dismiss="modal">
|
|
4147
|
+
${I18N.labels.delete}
|
|
4148
|
+
</button>
|
|
4149
|
+
` }));
|
|
4150
|
+
confirmModal.show().once('dismiss', function (modal, event) {
|
|
4151
|
+
if (event.target.dataset.action == 'delete') {
|
|
2662
4152
|
deleteEl();
|
|
2663
4153
|
}
|
|
2664
4154
|
});
|
|
@@ -2676,33 +4166,102 @@ class Wfst extends Control {
|
|
|
2676
4166
|
* @private
|
|
2677
4167
|
*/
|
|
2678
4168
|
_addFeatureToEditMode(feature, coordinate = null, layerName = null) {
|
|
2679
|
-
// For now, only allow one element at time
|
|
2680
|
-
// @TODO: allow edit multiples elements
|
|
2681
|
-
if (this._collectionModify.getLength())
|
|
2682
|
-
return;
|
|
2683
4169
|
if (layerName) {
|
|
2684
4170
|
// Store the layer information inside the feature
|
|
2685
4171
|
feature.set('_layerName_', layerName);
|
|
2686
4172
|
}
|
|
4173
|
+
if (coordinate) {
|
|
4174
|
+
// Keep the click coordinate to position the overlay helper
|
|
4175
|
+
feature.set('_editOverlayCoord_', coordinate);
|
|
4176
|
+
}
|
|
2687
4177
|
const props = feature ? feature.getProperties() : '';
|
|
2688
4178
|
if (props) {
|
|
2689
4179
|
if (feature.getGeometry()) {
|
|
2690
4180
|
getEditLayer().getSource().addFeature(feature);
|
|
2691
4181
|
this._collectionModify.push(feature);
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
4182
|
+
this._syncEditOverlays();
|
|
4183
|
+
}
|
|
4184
|
+
}
|
|
4185
|
+
}
|
|
4186
|
+
/**
|
|
4187
|
+
* Lock the whole current selection with a single LockFeature request per
|
|
4188
|
+
* layer, so the transaction LockId covers all the selected features
|
|
4189
|
+
*
|
|
4190
|
+
* @private
|
|
4191
|
+
*/
|
|
4192
|
+
async _lockSelectedFeatures() {
|
|
4193
|
+
const byLayer = {};
|
|
4194
|
+
this._collectionModify.getArray().forEach((selected) => {
|
|
4195
|
+
const layerName = selected.get('_layerName_');
|
|
4196
|
+
if (!layerName) {
|
|
4197
|
+
return;
|
|
4198
|
+
}
|
|
4199
|
+
(byLayer[layerName] || (byLayer[layerName] = [])).push(selected.getId());
|
|
4200
|
+
});
|
|
4201
|
+
const locks = [];
|
|
4202
|
+
Object.keys(byLayer).forEach((layerName) => {
|
|
4203
|
+
const layer = getStoredLayer(layerName);
|
|
4204
|
+
if (layer) {
|
|
4205
|
+
locks.push(layer.maybeLockFeature(byLayer[layerName]));
|
|
4206
|
+
}
|
|
4207
|
+
});
|
|
4208
|
+
if (locks.length) {
|
|
4209
|
+
await Promise.all(locks);
|
|
4210
|
+
}
|
|
4211
|
+
}
|
|
4212
|
+
/**
|
|
4213
|
+
* Create an EditOverlay helper and attach the edit listeners
|
|
4214
|
+
*
|
|
4215
|
+
* @param feature
|
|
4216
|
+
* @param coordinate
|
|
4217
|
+
* @param id
|
|
4218
|
+
* @private
|
|
4219
|
+
*/
|
|
4220
|
+
_createEditOverlay(feature = null, coordinate = null, id = null) {
|
|
4221
|
+
coordinate =
|
|
4222
|
+
coordinate || (feature ? feature.get('_editOverlayCoord_') : null);
|
|
4223
|
+
const overlay = new EditOverlay(feature, coordinate, id);
|
|
4224
|
+
// @ts-expect-error
|
|
4225
|
+
overlay.on('editFields', () => {
|
|
4226
|
+
this._editFields.show(this._collectionModify.getArray());
|
|
4227
|
+
});
|
|
4228
|
+
// @ts-expect-error
|
|
4229
|
+
overlay.on('editGeom', () => {
|
|
4230
|
+
this._editModeOn(this._collectionModify.getArray());
|
|
4231
|
+
});
|
|
4232
|
+
this._map.addOverlay(overlay);
|
|
4233
|
+
}
|
|
4234
|
+
/**
|
|
4235
|
+
* Sync the edit overlay helpers with the current selection: a single
|
|
4236
|
+
* per-feature overlay while a feature is selected, or a unique overlay
|
|
4237
|
+
* centered between all the selected features when editing several at once
|
|
4238
|
+
*
|
|
4239
|
+
* @private
|
|
4240
|
+
*/
|
|
4241
|
+
_syncEditOverlays() {
|
|
4242
|
+
if (getMode() === Modes.Edit) {
|
|
4243
|
+
return;
|
|
4244
|
+
}
|
|
4245
|
+
const multiOverlay = this._map.getOverlayById(this._multiOverlayId);
|
|
4246
|
+
if (multiOverlay) {
|
|
4247
|
+
this._map.removeOverlay(multiOverlay);
|
|
4248
|
+
}
|
|
4249
|
+
const items = this._collectionModify.getArray();
|
|
4250
|
+
if (items.length > 1) {
|
|
4251
|
+
items.forEach((feature) => {
|
|
4252
|
+
this._removeOverlayHelper(feature);
|
|
4253
|
+
});
|
|
4254
|
+
const extent = createEmpty();
|
|
4255
|
+
items.forEach((feature) => {
|
|
4256
|
+
extend(extent, feature.getGeometry().getExtent());
|
|
4257
|
+
});
|
|
4258
|
+
this._createEditOverlay(null, getCenter(extent), this._multiOverlayId);
|
|
4259
|
+
}
|
|
4260
|
+
else if (items.length === 1) {
|
|
4261
|
+
const feature = items[0];
|
|
4262
|
+
const featureId = feature.getId();
|
|
4263
|
+
if (featureId && !this._map.getOverlayById(featureId)) {
|
|
4264
|
+
this._createEditOverlay(feature);
|
|
2706
4265
|
}
|
|
2707
4266
|
}
|
|
2708
4267
|
}
|
|
@@ -2749,15 +4308,20 @@ class Wfst extends Control {
|
|
|
2749
4308
|
if (!layer.getVisible()) {
|
|
2750
4309
|
return;
|
|
2751
4310
|
}
|
|
4311
|
+
// Clear hover style while drawing
|
|
4312
|
+
this._clearHoverState();
|
|
2752
4313
|
activateDrawButton();
|
|
4314
|
+
deactivateSelectModeButton();
|
|
2753
4315
|
this._viewport.classList.add('draw-mode');
|
|
2754
4316
|
addDrawInteraction(layer);
|
|
2755
4317
|
}
|
|
2756
4318
|
else {
|
|
2757
4319
|
this._map.removeInteraction(this._interactionDraw);
|
|
2758
4320
|
this._viewport.classList.remove('draw-mode');
|
|
4321
|
+
activateSelectModeButton(getSelectionMode());
|
|
2759
4322
|
}
|
|
2760
4323
|
activateMode(layer ? Modes.Draw : null);
|
|
4324
|
+
this._refreshSelectionInteractions();
|
|
2761
4325
|
}
|
|
2762
4326
|
/**
|
|
2763
4327
|
* Activate/desactivate the edit mode
|
|
@@ -2773,6 +4337,8 @@ class Wfst extends Control {
|
|
|
2773
4337
|
else {
|
|
2774
4338
|
// Deselct features
|
|
2775
4339
|
this._collectionModify.clear();
|
|
4340
|
+
// Clear hover state when leaving the edit mode
|
|
4341
|
+
this._clearHoverState();
|
|
2776
4342
|
}
|
|
2777
4343
|
if (this._interactionSelectModify) {
|
|
2778
4344
|
this._interactionSelectModify.setActive(bool);
|
|
@@ -2780,6 +4346,61 @@ class Wfst extends Control {
|
|
|
2780
4346
|
this._interactionModify.setActive(bool);
|
|
2781
4347
|
if (this._interactionWfsSelect)
|
|
2782
4348
|
this._interactionWfsSelect.setActive(bool);
|
|
4349
|
+
this._refreshSelectionInteractions();
|
|
4350
|
+
}
|
|
4351
|
+
/**
|
|
4352
|
+
* Reset the cursor and remove the hover style of the last hovered feature
|
|
4353
|
+
* @private
|
|
4354
|
+
*/
|
|
4355
|
+
_clearHoverState() {
|
|
4356
|
+
if (this._viewport) {
|
|
4357
|
+
this._viewport.style.cursor = '';
|
|
4358
|
+
}
|
|
4359
|
+
if (this._hoveredFeature) {
|
|
4360
|
+
this._hoveredFeature.setStyle(undefined);
|
|
4361
|
+
this._hoveredFeature = null;
|
|
4362
|
+
}
|
|
4363
|
+
}
|
|
4364
|
+
/**
|
|
4365
|
+
* Change the map cursor while in edit mode: `move` when the pointer is
|
|
4366
|
+
* over an editable vertex and `copy` when it is over a segment (where a
|
|
4367
|
+
* new vertex can be inserted), mirroring the Modify interaction hit
|
|
4368
|
+
* tolerance.
|
|
4369
|
+
*
|
|
4370
|
+
* @param evt
|
|
4371
|
+
* @private
|
|
4372
|
+
*/
|
|
4373
|
+
_updateEditCursor(evt) {
|
|
4374
|
+
if (!this._collectionModify) {
|
|
4375
|
+
return;
|
|
4376
|
+
}
|
|
4377
|
+
const tolerance = 12;
|
|
4378
|
+
const toleranceSq = tolerance * tolerance;
|
|
4379
|
+
const pixel = evt.pixel;
|
|
4380
|
+
for (const feature of this._collectionModify.getArray()) {
|
|
4381
|
+
const vertices = getFeatureVertices(feature);
|
|
4382
|
+
const coordinates = vertices ? vertices.getCoordinates() : [];
|
|
4383
|
+
if (!coordinates.length) {
|
|
4384
|
+
continue;
|
|
4385
|
+
}
|
|
4386
|
+
// Hovering an existing vertex (draggable handle)
|
|
4387
|
+
for (const coordinate of coordinates) {
|
|
4388
|
+
const vertexPixel = this._map.getPixelFromCoordinate(coordinate);
|
|
4389
|
+
if (squaredDistance(vertexPixel, pixel) <= toleranceSq) {
|
|
4390
|
+
this._viewport.style.cursor = 'move';
|
|
4391
|
+
return;
|
|
4392
|
+
}
|
|
4393
|
+
}
|
|
4394
|
+
// Hovering a segment (allows inserting a new vertex)
|
|
4395
|
+
for (let i = 0; i < coordinates.length - 1; i++) {
|
|
4396
|
+
const start = this._map.getPixelFromCoordinate(coordinates[i]);
|
|
4397
|
+
const end = this._map.getPixelFromCoordinate(coordinates[i + 1]);
|
|
4398
|
+
if (squaredDistanceToSegment(pixel, [start, end]) <= toleranceSq) {
|
|
4399
|
+
this._viewport.style.cursor = 'copy';
|
|
4400
|
+
return;
|
|
4401
|
+
}
|
|
4402
|
+
}
|
|
4403
|
+
}
|
|
2783
4404
|
}
|
|
2784
4405
|
/**
|
|
2785
4406
|
* Remove the overlay helper atttached to a specify feature
|