qwc2 2025.10.16 → 2025.10.25

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.
Files changed (69) hide show
  1. package/actions/locate.js +3 -2
  2. package/components/AttributeTableWidget.js +24 -15
  3. package/components/FeatureAttributesWindow.js +178 -0
  4. package/components/IdentifyViewer.js +80 -39
  5. package/components/LocationRecorder.js +138 -0
  6. package/components/NumericInputWindow.js +30 -22
  7. package/components/PickFeature.js +87 -26
  8. package/components/PluginsContainer.js +16 -3
  9. package/components/map/OlLayer.js +2 -1
  10. package/components/map/layers/MVTLayer.js +3 -0
  11. package/components/map/layers/VectorLayer.js +66 -65
  12. package/components/map3d/HeightProfile3D.js +2 -0
  13. package/components/map3d/drawtool/EditTool3D.js +1 -1
  14. package/components/style/App.css +14 -0
  15. package/components/style/AttributeTableWidget.css +1 -1
  16. package/components/style/FeatureAttributesWindow.css +16 -0
  17. package/components/style/IdentifyViewer.css +5 -0
  18. package/components/style/LocationRecorder.css +10 -0
  19. package/components/style/NumericInputWindow.css +22 -0
  20. package/components/style/PluginsContainer.css +16 -0
  21. package/components/widgets/LayerCatalogWidget.js +40 -16
  22. package/components/widgets/NavBar.js +10 -3
  23. package/components/widgets/TextInput.js +4 -2
  24. package/icons/circle_full.svg +75 -0
  25. package/package.json +1 -1
  26. package/plugins/AttributeTable.js +4 -0
  27. package/plugins/GeometryDigitizer.js +3 -0
  28. package/plugins/HeightProfile.js +5 -1
  29. package/plugins/Identify.js +7 -3
  30. package/plugins/ObjectList.js +116 -0
  31. package/plugins/Redlining.js +21 -56
  32. package/plugins/map/EditingSupport.js +22 -1
  33. package/plugins/map/LocateSupport.js +8 -1
  34. package/plugins/map/RedliningSupport.js +374 -224
  35. package/plugins/map/SnappingSupport.js +11 -6
  36. package/plugins/map/style/SnappingSupport.css +2 -13
  37. package/reducers/locate.js +4 -2
  38. package/reducers/redlining.js +7 -4
  39. package/scripts/updateTranslations.js +1 -1
  40. package/static/translations/bg-BG.json +15 -2
  41. package/static/translations/ca-ES.json +15 -2
  42. package/static/translations/cs-CZ.json +15 -2
  43. package/static/translations/de-CH.json +15 -2
  44. package/static/translations/de-DE.json +15 -2
  45. package/static/translations/en-US.json +15 -2
  46. package/static/translations/es-ES.json +15 -2
  47. package/static/translations/fi-FI.json +15 -2
  48. package/static/translations/fr-FR.json +15 -2
  49. package/static/translations/hu-HU.json +15 -2
  50. package/static/translations/it-IT.json +15 -2
  51. package/static/translations/ja-JP.json +15 -2
  52. package/static/translations/nl-NL.json +15 -2
  53. package/static/translations/no-NO.json +15 -2
  54. package/static/translations/pl-PL.json +15 -2
  55. package/static/translations/pt-BR.json +15 -2
  56. package/static/translations/pt-PT.json +15 -2
  57. package/static/translations/ro-RO.json +15 -2
  58. package/static/translations/ru-RU.json +15 -2
  59. package/static/translations/sv-SE.json +15 -2
  60. package/static/translations/tr-TR.json +15 -2
  61. package/static/translations/tsconfig.json +12 -2
  62. package/static/translations/uk-UA.json +15 -2
  63. package/utils/EditingInterface.js +18 -4
  64. package/utils/EditingUtils.js +3 -1
  65. package/utils/LayerUtils.js +16 -6
  66. package/utils/MiscUtils.js +65 -0
  67. package/utils/expr_grammar/grammar.js +104 -22
  68. package/utils/expr_grammar/grammar.ne +2 -0
  69. package/utils/expr_grammar/test.js +21 -4
@@ -30,12 +30,16 @@ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e
30
30
 
31
31
  import React from 'react';
32
32
  import { connect } from 'react-redux';
33
+ import FileSaver from 'file-saver';
34
+ import isEmpty from 'lodash.isempty';
33
35
  import Mousetrap from 'mousetrap';
34
36
  import ol from 'openlayers';
35
37
  import PropTypes from 'prop-types';
36
38
  import { v4 as uuidv4 } from 'uuid';
37
39
  import { LayerRole, addLayerFeatures, removeLayerFeatures } from '../../actions/layers';
38
40
  import { changeRedliningState } from '../../actions/redlining';
41
+ import FeatureAttributesWindow from '../../components/FeatureAttributesWindow';
42
+ import LocationRecorder from '../../components/LocationRecorder';
39
43
  import NumericInputWindow from '../../components/NumericInputWindow';
40
44
  import { OlLayerAdded, OlLayerUpdated } from '../../components/map/OlLayer';
41
45
  import FeatureStyles from '../../utils/FeatureStyles';
@@ -59,7 +63,8 @@ var GeomTypeConfig = {
59
63
  }));
60
64
  },
61
65
  editTool: 'Pick',
62
- drawNodes: true
66
+ drawNodes: true,
67
+ showRecordLocation: true
63
68
  },
64
69
  LineString: {
65
70
  drawInteraction: function drawInteraction(opts) {
@@ -68,7 +73,8 @@ var GeomTypeConfig = {
68
73
  }));
69
74
  },
70
75
  editTool: 'Pick',
71
- drawNodes: true
76
+ drawNodes: true,
77
+ showRecordLocation: true
72
78
  },
73
79
  Polygon: {
74
80
  drawInteraction: function drawInteraction(opts) {
@@ -130,14 +136,22 @@ var RedliningSupport = /*#__PURE__*/function (_React$Component) {
130
136
  var _this;
131
137
  _classCallCheck(this, RedliningSupport);
132
138
  _this = _callSuper(this, RedliningSupport, [props]);
139
+ _defineProperty(_this, "state", {
140
+ showRecordLocation: false
141
+ });
133
142
  _defineProperty(_this, "updateCurrentFeature", function (feature) {
134
- if (_this.currentFeature && _this.props.redlining.selectedFeature) {
143
+ var deletedKeys = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
144
+ if (_this.selectedFeatures.length === 1 && _this.props.redlining.selectedFeature) {
135
145
  if (feature.circleParams) {
136
146
  var circleParams = feature.circleParams;
137
- _this.currentFeature.setGeometry(new ol.geom.Circle(circleParams.center, circleParams.radius));
147
+ _this.selectedFeatures[0].setGeometry(new ol.geom.Circle(circleParams.center, circleParams.radius));
138
148
  } else {
139
- _this.currentFeature.getGeometry().setCoordinates(feature.geometry.coordinates);
149
+ _this.selectedFeatures[0].getGeometry().setCoordinates(feature.geometry.coordinates);
140
150
  }
151
+ _this.selectedFeatures[0].setProperties(feature.properties, true);
152
+ deletedKeys.forEach(function (key) {
153
+ _this.selectedFeatures[0].unset(key);
154
+ });
141
155
  _this.props.changeRedliningState({
142
156
  selectedFeature: feature,
143
157
  geomType: feature.shape
@@ -161,16 +175,47 @@ var RedliningSupport = /*#__PURE__*/function (_React$Component) {
161
175
  var isText = feature.get("shape") === "Text";
162
176
  return _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty({}, isText ? "textOutlineColor" : "borderColor", styleOptions.strokeColor), "strokeDash", styleOptions.strokeDash), "size", (styleOptions.strokeWidth - 1) * 2), isText ? "textFillColor" : "fillColor", styleOptions.fillColor), "text", label), "headmarker", styleOptions.headmarker), "tailmarker", styleOptions.tailmarker);
163
177
  });
164
- _defineProperty(_this, "updateFeatureStyle", function (styleProps) {
165
- var isText = _this.currentFeature.get("shape") === "Text";
178
+ _defineProperty(_this, "updateFeatureStyle", function (feature) {
179
+ _this.blockOnChange = true;
180
+ var styleProps = _this.props.redlining.style;
181
+ var isText = feature.get("shape") === "Text";
166
182
  var styleName = isText ? "text" : "default";
167
183
  var opts = _this.styleOptions(styleProps, isText);
168
- _this.blockOnChange = true;
169
- _this.currentFeature.set('label', styleProps.text);
170
- _this.currentFeature.set('styleName', styleName);
171
- _this.currentFeature.set('styleOptions', opts);
184
+ if (!feature.get('measurements')) {
185
+ feature.set('label', styleProps.text);
186
+ }
187
+ feature.set('styleName', styleName);
188
+ feature.set('styleOptions', opts);
172
189
  _this.blockOnChange = false;
173
190
  });
191
+ _defineProperty(_this, "toggleFeatureMeasurements", function (feature) {
192
+ if (_this.props.redlining.measurements) {
193
+ var settings = {
194
+ displayCrs: _this.props.displayCrs,
195
+ lenUnit: _this.props.redlining.lenUnit,
196
+ areaUnit: _this.props.redlining.areaUnit
197
+ };
198
+ MeasureUtils.updateFeatureMeasurements(feature, feature.get('shape'), _this.props.mapCrs, settings);
199
+ } else {
200
+ feature.set('measurements', undefined);
201
+ feature.set('segment_labels', undefined);
202
+ feature.set('label', '');
203
+ }
204
+ });
205
+ _defineProperty(_this, "updateMeasurements", function (ev) {
206
+ if (_this.blockOnChange) {
207
+ return;
208
+ }
209
+ var feature = ev.target;
210
+ if (feature.get('measurements')) {
211
+ var settings = {
212
+ displayCrs: _this.props.displayCrs,
213
+ lenUnit: _this.props.redlining.lenUnit,
214
+ areaUnit: _this.props.redlining.areaUnit
215
+ };
216
+ MeasureUtils.updateFeatureMeasurements(feature, feature.get('shape'), _this.props.mapCrs, settings);
217
+ }
218
+ });
174
219
  _defineProperty(_this, "styleFunction", function (feature) {
175
220
  var styleOptions = feature.get("styleOptions");
176
221
  var styleName = feature.get("styleName");
@@ -186,83 +231,11 @@ var RedliningSupport = /*#__PURE__*/function (_React$Component) {
186
231
  }
187
232
  return styles;
188
233
  });
189
- _defineProperty(_this, "setCurrentFeature", function (feature) {
190
- var _featureObj$shape;
191
- _this.currentFeature = feature;
192
- _this.currentFeature.setStyle(_this.styleFunction);
193
- var circleParams = _this.currentFeature.get('circleParams');
194
- if (circleParams) {
195
- _this.currentFeature.setGeometry(new ol.geom.Circle(circleParams.center, circleParams.radius));
196
- }
197
- var measurements = _this.currentFeature.get('measurements');
198
- var featureObj = _this.currentFeatureObject();
199
- var newRedliningState = {
200
- style: _this.styleProps(_this.currentFeature),
201
- measurements: !!_this.currentFeature.get('measurements'),
202
- selectedFeature: featureObj,
203
- geomType: (_featureObj$shape = featureObj === null || featureObj === void 0 ? void 0 : featureObj.shape) !== null && _featureObj$shape !== void 0 ? _featureObj$shape : _this.props.redlining.geomType
204
- };
205
- if (measurements) {
206
- newRedliningState.lenUnit = measurements.lenUnit;
207
- newRedliningState.areaUnit = measurements.areaUnit;
208
- }
209
- _this.props.changeRedliningState(newRedliningState);
210
- _this.currentFeature.on('change', _this.updateMeasurements);
211
- });
212
- _defineProperty(_this, "addDrawInteraction", function () {
213
- var geomTypeConfig = GeomTypeConfig[_this.props.redlining.geomType];
214
- if (!geomTypeConfig) {
215
- return;
216
- }
217
- var isFreeHand = _this.props.redlining.freehand;
218
- var drawInteraction = geomTypeConfig.drawInteraction({
219
- stopClick: true,
220
- condition: function condition(event) {
221
- return event.originalEvent.buttons === 1;
222
- },
223
- style: function style() {
224
- return _this.picking ? [] : FeatureStyles.sketchInteraction();
225
- },
226
- freehand: isFreeHand
227
- });
228
- drawInteraction.on('drawstart', function (evt) {
229
- if (_this.picking && _this.props.redlining.drawMultiple === false) {
230
- return;
231
- }
232
- _this.leaveTemporaryEditMode();
233
- _this.currentFeature = evt.feature;
234
- _this.currentFeature.setId(uuidv4());
235
- _this.currentFeature.set('shape', _this.props.redlining.geomType);
236
- _this.currentFeature.setStyle(_this.styleFunction);
237
- _this.updateFeatureStyle(_this.props.redlining.style);
238
- _this.currentFeature.on('change', _this.updateMeasurements);
239
- }, _this);
240
- drawInteraction.on('drawend', function () {
241
- var featureId = _this.currentFeature.getId();
242
- _this.commitCurrentFeature(_this.props.redlining, true);
243
- _this.enterTemporaryEditMode(featureId, _this.props.redlining.layer, geomTypeConfig.editTool);
244
- }, _this);
245
- _this.props.map.addInteraction(drawInteraction);
246
- _this.interactions.push(drawInteraction);
247
- });
248
- _defineProperty(_this, "updateMeasurements", function () {
249
- if (_this.blockOnChange || !_this.currentFeature) {
250
- return;
251
- }
252
- var feature = _this.currentFeature;
253
- var hadMeasurements = !!feature.get('measurements');
254
- if (_this.props.redlining.measurements) {
255
- var settings = {
256
- displayCrs: _this.props.displayCrs,
257
- lenUnit: _this.props.redlining.lenUnit,
258
- areaUnit: _this.props.redlining.areaUnit
259
- };
260
- MeasureUtils.updateFeatureMeasurements(feature, feature.get('shape'), _this.props.mapCrs, settings);
261
- } else if (hadMeasurements) {
262
- feature.set('measurements', undefined);
263
- feature.set('segment_labels', undefined);
264
- feature.set('label', '');
265
- }
234
+ _defineProperty(_this, "searchRedliningLayer", function (layerId) {
235
+ var _this$props$map$getLa;
236
+ return (_this$props$map$getLa = _this.props.map.getLayers().getArray().find(function (l) {
237
+ return l.get('id') === layerId;
238
+ })) !== null && _this$props$map$getLa !== void 0 ? _this$props$map$getLa : null;
266
239
  });
267
240
  _defineProperty(_this, "waitForFeatureAndLayer", function (layerId, featureId, callback) {
268
241
  var redliningLayer = _this.searchRedliningLayer(layerId);
@@ -295,24 +268,59 @@ var RedliningSupport = /*#__PURE__*/function (_React$Component) {
295
268
  callback(redliningLayer, null);
296
269
  }
297
270
  });
271
+ _defineProperty(_this, "addDrawInteraction", function () {
272
+ var geomTypeConfig = GeomTypeConfig[_this.props.redlining.geomType];
273
+ if (!geomTypeConfig) {
274
+ return;
275
+ }
276
+ var isFreeHand = _this.props.redlining.freehand;
277
+ var drawInteraction = geomTypeConfig.drawInteraction({
278
+ stopClick: true,
279
+ condition: function condition(event) {
280
+ return event.originalEvent.buttons === 1;
281
+ },
282
+ style: function style() {
283
+ return _this.picking ? [] : FeatureStyles.sketchInteraction();
284
+ },
285
+ freehand: isFreeHand
286
+ });
287
+ drawInteraction.on('drawstart', function (ev) {
288
+ if (_this.picking && _this.props.redlining.drawMultiple === false) {
289
+ return;
290
+ }
291
+ _this.leaveTemporaryEditMode();
292
+ ev.feature.setId(uuidv4());
293
+ ev.feature.set('shape', _this.props.redlining.geomType);
294
+ _this.updateFeatureStyle(ev.feature);
295
+ _this.toggleFeatureMeasurements(ev.feature);
296
+ _this.selectFeatures([ev.feature]);
297
+ }, _this);
298
+ drawInteraction.on('drawend', function (ev) {
299
+ _this.commitFeatures([ev.feature], _this.props.redlining, true);
300
+ _this.enterTemporaryEditMode(ev.feature.getId(), _this.props.redlining.layer, geomTypeConfig.editTool);
301
+ }, _this);
302
+ _this.props.map.addInteraction(drawInteraction);
303
+ _this.interactions.push(drawInteraction);
304
+ _this.setState({
305
+ showRecordLocation: geomTypeConfig.showRecordLocation
306
+ });
307
+ });
298
308
  _defineProperty(_this, "enterTemporaryEditMode", function (featureId, layerId, editTool) {
299
309
  _this.waitForFeatureAndLayer(layerId, featureId, function (redliningLayer, feature) {
300
310
  if (!feature) {
301
311
  return;
302
312
  }
303
- _this.setCurrentFeature(feature);
313
+ _this.selectFeatures([feature]);
304
314
  if (editTool === 'Transform') {
305
- _this.setupTransformInteraction([_this.currentFeature]);
315
+ _this.setupTransformInteraction(redliningLayer, _this.selectedFeatures);
306
316
  } else {
307
- _this.setupModifyInteraction([_this.currentFeature]);
317
+ _this.setupModifyInteraction(_this.selectedFeatures);
308
318
  }
309
319
  _this.picking = true;
310
320
  });
311
321
  });
312
322
  _defineProperty(_this, "leaveTemporaryEditMode", function () {
313
- if (_this.currentFeature) {
314
- _this.commitCurrentFeature(_this.props.redlining);
315
- }
323
+ _this.commitFeatures(_this.selectedFeatures, _this.props.redlining);
316
324
  if (_this.picking) {
317
325
  // Remove modify interactions
318
326
  _this.props.map.removeInteraction(_this.interactions.pop());
@@ -330,12 +338,10 @@ var RedliningSupport = /*#__PURE__*/function (_React$Component) {
330
338
  });
331
339
  var currentEditInteraction = null;
332
340
  selectInteraction.on('select', function (evt) {
333
- if (evt.selected.length === 1 && evt.selected[0] === _this.currentFeature) {
341
+ if (evt.selected.length === 1 && _this.selectedFeatures.includes(evt.selected[0])) {
334
342
  return;
335
343
  }
336
- if (_this.currentFeature) {
337
- _this.commitCurrentFeature(_this.props.redlining);
338
- }
344
+ _this.commitFeatures(_this.selectedFeatures, _this.props.redlining);
339
345
  if (currentEditInteraction) {
340
346
  _this.props.map.removeInteraction(currentEditInteraction);
341
347
  _this.interactions = _this.interactions.filter(function (i) {
@@ -344,14 +350,14 @@ var RedliningSupport = /*#__PURE__*/function (_React$Component) {
344
350
  currentEditInteraction = null;
345
351
  }
346
352
  if (evt.selected.length === 1) {
347
- _this.setCurrentFeature(evt.selected[0]);
348
- var geomTypeConfig = GeomTypeConfig[_this.currentFeature.get('shape')];
353
+ _this.selectFeatures(evt.selected);
354
+ var geomTypeConfig = GeomTypeConfig[evt.selected[0].get('shape')];
349
355
  if (geomTypeConfig && geomTypeConfig.editTool === 'Transform') {
350
- currentEditInteraction = _this.setupTransformInteraction([_this.currentFeature]);
356
+ currentEditInteraction = _this.setupTransformInteraction(redliningLayer, [evt.selected[0]]);
351
357
  currentEditInteraction.on('select', function (ev) {
352
358
  // Clear selection when selecting a different feature, and let the parent select interaction deal with the new feature
353
- if (_this.currentFeature && ev.feature !== _this.currentFeature) {
354
- _this.commitCurrentFeature(_this.props.redlining);
359
+ if (!isEmpty(_this.selectedFeatures) && !_this.selectedFeatures.includes(ev.feature)) {
360
+ _this.commitFeatures(_this.selectedFeatures, _this.props.redlining);
355
361
  currentEditInteraction.setSelection(new ol.Collection());
356
362
  }
357
363
  });
@@ -372,28 +378,22 @@ var RedliningSupport = /*#__PURE__*/function (_React$Component) {
372
378
  if (!redliningLayer) {
373
379
  return;
374
380
  }
375
- var transformInteraction = _this.setupTransformInteraction();
381
+ var transformInteraction = _this.setupTransformInteraction(redliningLayer, [], true);
376
382
  transformInteraction.on('select', function (evt) {
377
- if (evt.feature === _this.currentFeature) {
378
- return;
379
- }
380
- if (_this.currentFeature) {
381
- _this.commitCurrentFeature(_this.props.redlining);
382
- }
383
- if (evt.feature) {
384
- _this.setCurrentFeature(evt.feature);
385
- }
383
+ var added = evt.features.getArray().filter(function (x) {
384
+ return !_this.selectedFeatures.includes(x);
385
+ });
386
+ var removed = _this.selectedFeatures.filter(function (x) {
387
+ return !evt.features.getArray().includes(x);
388
+ });
389
+ _this.selectFeatures(added);
390
+ _this.commitFeatures(removed, _this.props.redlining);
386
391
  });
387
392
  _this.picking = true;
388
393
  });
389
- _defineProperty(_this, "addPickDrawInteraction", function () {
390
- _this.waitForFeatureAndLayer(_this.props.redlining.layer, null, function () {
391
- return _this.addPickInteraction();
392
- });
393
- });
394
394
  _defineProperty(_this, "maybeEnterTemporaryDrawMode", function (ev) {
395
395
  var redliningLayer = _this.searchRedliningLayer(_this.props.redlining.layer);
396
- if (_this.currentFeature || !_this.props.redlining.drawMultiple && redliningLayer.getSource().getFeatures().length > 0) {
396
+ if (_this.selectedFeatures.length || !_this.props.redlining.drawMultiple && redliningLayer.getSource().getFeatures().length > 0) {
397
397
  return;
398
398
  }
399
399
  var featureHit = false;
@@ -423,16 +423,15 @@ var RedliningSupport = /*#__PURE__*/function (_React$Component) {
423
423
  freehand: isFreeHand
424
424
  });
425
425
  drawInteraction.on('drawstart', function (evt) {
426
- _this.currentFeature = evt.feature;
427
- _this.currentFeature.setId(uuidv4());
428
- _this.currentFeature.set('shape', _this.props.redlining.geomType);
429
- _this.currentFeature.setStyle(_this.styleFunction);
430
- _this.updateFeatureStyle(_this.props.redlining.style);
431
- _this.currentFeature.on('change', _this.updateMeasurements);
426
+ evt.feature.setId(uuidv4());
427
+ evt.feature.set('shape', _this.props.redlining.geomType);
428
+ _this.updateFeatureStyle(evt.feature);
429
+ _this.toggleFeatureMeasurements(ev.feature);
430
+ _this.selectFeatures([evt.feature]);
432
431
  }, _this);
433
432
  drawInteraction.on('drawend', function () {
434
433
  // Draw end
435
- _this.commitCurrentFeature(_this.props.redlining, true);
434
+ _this.commitFeatures(_this.selectFeatures, _this.props.redlining, true);
436
435
  _this.reset(_this.props.redlining);
437
436
  // Ughh... Apparently we need to wait 250ms for the 'singleclick' event processing to finish to avoid pick interactions picking up the current event
438
437
  setTimeout(function () {
@@ -468,18 +467,38 @@ var RedliningSupport = /*#__PURE__*/function (_React$Component) {
468
467
  _this.interactions.push(modifyInteraction);
469
468
  return modifyInteraction;
470
469
  });
471
- _defineProperty(_this, "setupTransformInteraction", function () {
472
- var selectedFeatures = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
470
+ _defineProperty(_this, "setupTransformInteraction", function (redliningLayer) {
471
+ var selectedFeatures = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
472
+ var multi = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
473
473
  var transformInteraction = new ol.interaction.Transform({
474
474
  stretch: false,
475
+ addCondition: multi ? function (ev) {
476
+ return ev.originalEvent.ctrlKey;
477
+ } : null,
475
478
  keepAspectRatio: function keepAspectRatio(ev) {
476
- return _this.currentFeature ? GeomTypeConfig[_this.currentFeature.get('shape')].regular || ol.events.condition.shiftKeyOnly(ev) : false;
477
- }
479
+ return _this.selectedFeatures.find(function (f) {
480
+ return GeomTypeConfig[f.get('shape')].regular;
481
+ }) || ol.events.condition.shiftKeyOnly(ev);
482
+ },
483
+ layers: [redliningLayer],
484
+ translateFeature: true
478
485
  });
479
- transformInteraction.on('rotating', function (e) {
480
- if (_this.currentFeature.get('shape') === 'Text') {
481
- _this.currentFeature.set('rotation', -e.angle);
486
+ // Hacky workaround translateFeature interfering with ctrl-click to deselect selected features
487
+ var origHandleDownEvent = transformInteraction.handleDownEvent;
488
+ transformInteraction.handleDownEvent = function (evt) {
489
+ if (evt.originalEvent.ctrlKey) {
490
+ transformInteraction.set('translateFeature', false);
482
491
  }
492
+ var ret = origHandleDownEvent.call(transformInteraction, evt);
493
+ transformInteraction.set('translateFeature', true);
494
+ return ret;
495
+ };
496
+ transformInteraction.on('rotating', function (ev) {
497
+ ev.features.forEach(function (feature) {
498
+ if (feature.get('shape') === 'Text') {
499
+ feature.set('rotation', -ev.angle);
500
+ }
501
+ });
483
502
  });
484
503
  transformInteraction.on('rotateend', _this.updateSelectedFeature);
485
504
  transformInteraction.on('translateend', _this.updateSelectedFeature);
@@ -490,11 +509,19 @@ var RedliningSupport = /*#__PURE__*/function (_React$Component) {
490
509
  return transformInteraction;
491
510
  });
492
511
  _defineProperty(_this, "updateSelectedFeature", function () {
493
- var _featureObj$shape2;
494
- var featureObj = _this.currentFeatureObject();
512
+ var _featureObj$shape, _featureObj;
513
+ var featureObj = null;
514
+ if (_this.selectedFeatures.length === 1) {
515
+ featureObj = _this.serializeFeature(_this.selectedFeatures[0]);
516
+ } else if (_this.selectedFeatures.length > 1) {
517
+ featureObj = {
518
+ type: "FeatureCollection",
519
+ features: []
520
+ };
521
+ }
495
522
  _this.props.changeRedliningState({
496
523
  selectedFeature: featureObj,
497
- geomType: (_featureObj$shape2 = featureObj === null || featureObj === void 0 ? void 0 : featureObj.shape) !== null && _featureObj$shape2 !== void 0 ? _featureObj$shape2 : _this.props.redlining.geomType
524
+ geomType: (_featureObj$shape = (_featureObj = featureObj) === null || _featureObj === void 0 ? void 0 : _featureObj.shape) !== null && _featureObj$shape !== void 0 ? _featureObj$shape : _this.props.redlining.geomType
498
525
  });
499
526
  });
500
527
  _defineProperty(_this, "triggerDelete", function () {
@@ -502,111 +529,193 @@ var RedliningSupport = /*#__PURE__*/function (_React$Component) {
502
529
  action: "Delete"
503
530
  });
504
531
  });
505
- _defineProperty(_this, "deleteCurrentFeature", function () {
506
- if (_this.currentFeature) {
507
- _this.props.removeLayerFeatures(_this.props.redlining.layer, [_this.currentFeature.getId()], true);
508
- _this.currentFeature = null;
532
+ _defineProperty(_this, "deleteCurrentFeatures", function () {
533
+ if (_this.selectedFeatures.length) {
534
+ _this.props.removeLayerFeatures(_this.props.redlining.layer, _this.selectedFeatures.map(function (f) {
535
+ return f.getId();
536
+ }), true);
537
+ _this.selectedFeatures = [];
509
538
  }
510
539
  });
511
- _defineProperty(_this, "commitCurrentFeature", function (redliningProps) {
512
- var _feature$geometry;
513
- var newFeature = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
514
- var feature = _this.currentFeatureObject();
515
- if (!feature) {
516
- return;
517
- }
518
- // Don't commit empty/invalid features
519
- if (feature.shape === "Text" && !feature.properties.label || feature.shape === "Circle" && feature.circleParams.radius === 0 || ((_feature$geometry = feature.geometry) === null || _feature$geometry === void 0 ? void 0 : _feature$geometry.type) === "Polygon" && _this.currentFeature.getGeometry().getArea() === 0) {
520
- if (!newFeature) {
521
- _this.props.removeLayerFeatures(redliningProps.layer, [feature.id]);
540
+ _defineProperty(_this, "updateRedliningState", function (firstSelection) {
541
+ if (_this.selectedFeatures.length > 0) {
542
+ var features = _this.selectedFeatures;
543
+ var featureObj = features.length === 1 ? _this.serializeFeature(features[0]) : {
544
+ type: "FeatureCollection",
545
+ features: []
546
+ };
547
+ var newRedliningState = {
548
+ selectedFeature: featureObj
549
+ };
550
+ if (firstSelection || _this.selectedFeatures.length === 1) {
551
+ var _featureObj$shape2;
552
+ newRedliningState.style = _this.styleProps(features[0]);
553
+ newRedliningState.measurements = !!features[0].get('measurements');
554
+ newRedliningState.geomType = (_featureObj$shape2 = featureObj === null || featureObj === void 0 ? void 0 : featureObj.shape) !== null && _featureObj$shape2 !== void 0 ? _featureObj$shape2 : _this.props.redlining.geomType;
555
+ var measurements = features[0].get('measurements');
556
+ if (measurements) {
557
+ newRedliningState.lenUnit = measurements.lenUnit;
558
+ newRedliningState.areaUnit = measurements.areaUnit;
559
+ }
522
560
  }
523
- _this.resetSelectedFeature();
524
- return;
525
- }
526
- if (feature.shape === "Circle") {
527
- var _feature$circleParams = feature.circleParams,
528
- center = _feature$circleParams.center,
529
- radius = _feature$circleParams.radius;
530
- var deg2rad = Math.PI / 180;
531
- feature.geometry.type = "Polygon";
532
- feature.geometry.coordinates = [Array.apply(null, Array(91)).map(function (item, index) {
533
- return [center[0] + radius * Math.cos(4 * index * deg2rad), center[1] + radius * Math.sin(4 * index * deg2rad)];
534
- })];
561
+ _this.props.changeRedliningState(newRedliningState);
562
+ } else {
563
+ _this.props.changeRedliningState({
564
+ selectedFeature: null
565
+ });
535
566
  }
536
- if (feature.geometry.type === "LineString" || feature.geometry.type === "Polygon") {
537
- feature.geometry.coordinates = VectorLayerUtils.removeDuplicateNodes(feature.geometry.coordinates);
567
+ });
568
+ _defineProperty(_this, "selectFeatures", function (features) {
569
+ var firstSelection = isEmpty(_this.selectedFeatures);
570
+ features.forEach(function (feature) {
571
+ feature.setStyle(_this.styleFunction);
572
+ feature.on('change', _this.updateMeasurements);
573
+ _this.selectedFeatures.push(feature);
574
+ });
575
+ _this.updateRedliningState(firstSelection);
576
+ });
577
+ _defineProperty(_this, "commitFeatures", function (features, redliningProps) {
578
+ var newFeature = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
579
+ var featureObjects = features.map(function (feature) {
580
+ var _featureObj$geometry;
581
+ var styleName = feature.get("shape") === "Text" ? "text" : "default";
582
+ var style = FeatureStyles[styleName](feature, feature.get('styleOptions'));
583
+ feature.setStyle(style);
584
+ feature.un('change', _this.updateMeasurements);
585
+ _this.selectedFeatures = _this.selectedFeatures.filter(function (f) {
586
+ return f !== feature;
587
+ });
588
+ var featureObj = _this.serializeFeature(feature);
589
+ // Don't commit empty/invalid features
590
+ if (featureObj.shape === "Text" && !featureObj.properties.label || featureObj.shape === "Circle" && featureObj.circleParams.radius === 0 || ((_featureObj$geometry = featureObj.geometry) === null || _featureObj$geometry === void 0 ? void 0 : _featureObj$geometry.type) === "Polygon" && feature.getGeometry().getArea() === 0) {
591
+ if (!newFeature) {
592
+ _this.props.removeLayerFeatures(redliningProps.layer, [featureObj.id]);
593
+ }
594
+ return null;
595
+ }
596
+ if (featureObj.shape === "Circle") {
597
+ var _featureObj$circlePar = featureObj.circleParams,
598
+ center = _featureObj$circlePar.center,
599
+ radius = _featureObj$circlePar.radius;
600
+ var deg2rad = Math.PI / 180;
601
+ featureObj.geometry.type = "Polygon";
602
+ featureObj.geometry.coordinates = [Array.apply(null, Array(91)).map(function (item, index) {
603
+ return [center[0] + radius * Math.cos(4 * index * deg2rad), center[1] + radius * Math.sin(4 * index * deg2rad)];
604
+ })];
605
+ }
606
+ if (featureObj.geometry.type === "LineString" || featureObj.geometry.type === "Polygon") {
607
+ featureObj.geometry.coordinates = VectorLayerUtils.removeDuplicateNodes(featureObj.geometry.coordinates);
608
+ }
609
+ return featureObj;
610
+ }).filter(Boolean);
611
+ if (isEmpty(featureObjects)) {
612
+ return null;
538
613
  }
539
614
  var layer = {
540
615
  id: redliningProps.layer,
541
616
  title: redliningProps.layerTitle,
542
617
  role: LayerRole.USERLAYER
543
618
  };
544
- _this.props.addLayerFeatures(layer, [feature]);
545
- _this.resetSelectedFeature();
546
- });
547
- _defineProperty(_this, "resetSelectedFeature", function () {
548
- if (_this.currentFeature) {
549
- // Reset selection style
550
- var styleName = _this.currentFeature.get("shape") === "Text" ? "text" : "default";
551
- var style = FeatureStyles[styleName](_this.currentFeature, _this.currentFeature.get('styleOptions'));
552
- _this.currentFeature.setStyle(style);
553
- _this.currentFeature.un('change', _this.updateMeasurements);
554
- _this.currentFeature = null;
555
- _this.props.changeRedliningState({
556
- selectedFeature: null
557
- });
558
- }
619
+ _this.props.addLayerFeatures(layer, featureObjects);
620
+ _this.updateRedliningState();
621
+ return featureObjects;
559
622
  });
560
623
  _defineProperty(_this, "reset", function (redliningProps) {
624
+ _this.setState({
625
+ showRecordLocation: false
626
+ });
561
627
  while (_this.interactions.length > 0) {
562
628
  _this.props.map.removeInteraction(_this.interactions.shift());
563
629
  }
564
630
  if (_this.picking) {
565
- _this.commitCurrentFeature(redliningProps, false);
631
+ _this.commitFeatures(_this.selectedFeatures, redliningProps, false);
566
632
  } else {
567
- _this.resetSelectedFeature();
633
+ _this.selectedFeatures.forEach(function (feature) {
634
+ var styleName = feature.get("shape") === "Text" ? "text" : "default";
635
+ var style = FeatureStyles[styleName](feature, feature.get('styleOptions'));
636
+ feature.setStyle(style);
637
+ feature.un('change', _this.updateMeasurements);
638
+ });
639
+ _this.selectedFeatures = [];
568
640
  }
569
641
  _this.props.map.un('click', _this.maybeEnterTemporaryDrawMode);
570
642
  _this.picking = false;
571
643
  });
572
- _defineProperty(_this, "searchRedliningLayer", function (layerId) {
573
- var _this$props$map$getLa;
574
- return (_this$props$map$getLa = _this.props.map.getLayers().getArray().find(function (l) {
575
- return l.get('id') === layerId;
576
- })) !== null && _this$props$map$getLa !== void 0 ? _this$props$map$getLa : null;
577
- });
578
- _defineProperty(_this, "currentFeatureObject", function () {
579
- if (!_this.currentFeature) {
580
- return null;
581
- }
644
+ _defineProperty(_this, "serializeFeature", function (feature) {
582
645
  var format = new ol.format.GeoJSON();
583
- var feature = format.writeFeatureObject(_this.currentFeature);
584
- if (_this.currentFeature.get("shape") === "Circle") {
585
- feature.circleParams = {
586
- center: _this.currentFeature.getGeometry().getCenter(),
587
- radius: _this.currentFeature.getGeometry().getRadius()
646
+ var featureObject = format.writeFeatureObject(feature);
647
+ if (feature.get("shape") === "Circle") {
648
+ featureObject.circleParams = {
649
+ center: feature.getGeometry().getCenter(),
650
+ radius: feature.getGeometry().getRadius()
588
651
  };
589
652
  }
590
- feature.styleName = _this.currentFeature.get('styleName');
591
- feature.styleOptions = _this.currentFeature.get('styleOptions');
592
- feature.shape = _this.currentFeature.get('shape');
593
- feature.measurements = _this.currentFeature.get('measurements');
594
- feature.crs = _this.props.mapCrs;
653
+ featureObject.styleName = feature.get('styleName');
654
+ featureObject.styleOptions = feature.get('styleOptions');
655
+ featureObject.shape = feature.get('shape');
656
+ featureObject.measurements = feature.get('measurements');
657
+ featureObject.crs = _this.props.mapCrs;
595
658
  // Don't pollute GeoJSON object properties with internal props
596
- delete feature.properties.styleName;
597
- delete feature.properties.styleOptions;
598
- delete feature.properties.shape;
599
- delete feature.properties.measurements;
600
- delete feature.properties.circleParams;
659
+ delete featureObject.properties.styleName;
660
+ delete featureObject.properties.styleOptions;
661
+ delete featureObject.properties.shape;
662
+ delete featureObject.properties.measurements;
663
+ delete featureObject.properties.circleParams;
601
664
  // Don't store empty label prop
602
- if (feature.properties.label === "") {
603
- delete feature.properties.label;
665
+ if (featureObject.properties.label === "") {
666
+ delete featureObject.properties.label;
667
+ }
668
+ return featureObject;
669
+ });
670
+ _defineProperty(_this, "export", function () {
671
+ var committedFeatures = _this.commitFeatures(_this.selectedFeatures, _this.props.redlining);
672
+ var layer = _this.props.layers.find(function (l) {
673
+ return l.id === _this.props.redlining.layer;
674
+ });
675
+ if (!layer) {
676
+ return;
677
+ }
678
+ if (_this.props.redlining.format === "geojson") {
679
+ var geojson = JSON.stringify({
680
+ type: "FeatureCollection",
681
+ features: layer.features.map(function (feature) {
682
+ var _committedFeatures$fi;
683
+ var newFeature = _objectSpread({}, (_committedFeatures$fi = committedFeatures.find(function (f) {
684
+ return f.id === feature.id;
685
+ })) !== null && _committedFeatures$fi !== void 0 ? _committedFeatures$fi : feature);
686
+ newFeature.geometry = VectorLayerUtils.reprojectGeometry(feature.geometry, feature.crs || _this.props.mapCrs, 'EPSG:4326');
687
+ delete newFeature.crs;
688
+ return newFeature;
689
+ })
690
+ }, null, ' ');
691
+ FileSaver.saveAs(new Blob([geojson], {
692
+ type: "text/plain;charset=utf-8"
693
+ }), layer.title + ".json");
694
+ } else if (_this.props.redlining.format === "kml") {
695
+ var nativeLayer = _this.searchRedliningLayer(_this.props.redlining.layer);
696
+ if (!nativeLayer) {
697
+ return;
698
+ }
699
+ var kmlFormat = new ol.format.KML();
700
+ var features = nativeLayer.getSource().getFeatures().map(function (feature) {
701
+ // Circle is not supported by kml format
702
+ if (feature.getGeometry() instanceof ol.geom.Circle) {
703
+ feature = feature.clone();
704
+ feature.setGeometry(ol.geom.polygonFromCircle(feature.getGeometry()));
705
+ }
706
+ return feature;
707
+ });
708
+ var data = kmlFormat.writeFeatures(features, {
709
+ featureProjection: _this.props.mapCrs
710
+ });
711
+ FileSaver.saveAs(new Blob([data], {
712
+ type: "application/vnd.google-earth.kml+xml"
713
+ }), layer.title + ".kml");
604
714
  }
605
- return feature;
606
715
  });
607
716
  _this.interactions = [];
608
717
  _this.picking = false;
609
- _this.currentFeature = null;
718
+ _this.selectedFeatures = [];
610
719
  _this.blockOnChange = false;
611
720
  _this.selectedTextStyle = function (feature, opts) {
612
721
  return new ol.style.Style({
@@ -647,6 +756,7 @@ var RedliningSupport = /*#__PURE__*/function (_React$Component) {
647
756
  return _createClass(RedliningSupport, [{
648
757
  key: "componentDidUpdate",
649
758
  value: function componentDidUpdate(prevProps) {
759
+ var _this2 = this;
650
760
  // Bind keyboard shortcuts to delete features
651
761
  if (this.props.redlining.action && !prevProps.redlining.action) {
652
762
  Mousetrap.bind('del', this.triggerDelete);
@@ -657,7 +767,14 @@ var RedliningSupport = /*#__PURE__*/function (_React$Component) {
657
767
  }
658
768
  // Handle delete action immediately and reset the redlining state to the previous action
659
769
  if (this.props.redlining.action === 'Delete') {
660
- this.deleteCurrentFeature();
770
+ this.deleteCurrentFeatures();
771
+ this.props.changeRedliningState(_objectSpread(_objectSpread({}, prevProps.redlining), {}, {
772
+ selectedFeature: null
773
+ }));
774
+ return;
775
+ }
776
+ if (this.props.redlining.action === 'Export') {
777
+ this["export"]();
661
778
  this.props.changeRedliningState(_objectSpread(_objectSpread({}, prevProps.redlining), {}, {
662
779
  selectedFeature: null
663
780
  }));
@@ -674,36 +791,67 @@ var RedliningSupport = /*#__PURE__*/function (_React$Component) {
674
791
  } else if (this.props.redlining.action === 'Pick' || this.props.redlining.action === 'Buffer') {
675
792
  this.addPickInteraction();
676
793
  } else if (this.props.redlining.action === 'PickDraw') {
677
- this.addPickDrawInteraction();
794
+ this.waitForFeatureAndLayer(this.props.redlining.layer, null, function () {
795
+ return _this2.addPickInteraction();
796
+ });
678
797
  }
679
798
  }
680
- if (this.currentFeature) {
799
+ if (this.selectedFeatures) {
681
800
  // Update feature style
682
801
  if (this.props.redlining.style !== prevProps.redlining.style) {
683
- this.updateFeatureStyle(this.props.redlining.style);
802
+ this.selectedFeatures.forEach(this.updateFeatureStyle);
684
803
  }
685
804
  // Update current feature measurements
686
- if (this.props.map.displayCrs !== prevProps.map.displayCrs || this.props.redlining.measurements !== prevProps.redlining.measurements || this.props.redlining.lenUnit !== prevProps.redlining.lenUnit || this.props.redlining.areaUnit !== prevProps.redlining.areaUnit) {
687
- this.currentFeature.changed();
805
+ if (this.props.redlining.measurements !== prevProps.redlining.measurements) {
806
+ this.selectedFeatures.forEach(this.toggleFeatureMeasurements);
807
+ } else if (this.props.map.displayCrs !== prevProps.map.displayCrs || this.props.redlining.lenUnit !== prevProps.redlining.lenUnit || this.props.redlining.areaUnit !== prevProps.redlining.areaUnit) {
808
+ this.selectedFeatures.forEach(function (feature) {
809
+ return feature.changed();
810
+ });
688
811
  }
689
812
  }
690
813
  }
691
814
  }, {
692
815
  key: "render",
693
816
  value: function render() {
694
- var _this2 = this;
695
- if (this.props.redlining.numericInput) {
696
- return /*#__PURE__*/React.createElement(NumericInputWindow, {
817
+ var _this3 = this;
818
+ var widgets = [];
819
+ if (this.props.redlining.extraAction === "NumericInput") {
820
+ widgets.push(/*#__PURE__*/React.createElement(NumericInputWindow, {
821
+ feature: this.props.redlining.selectedFeature,
822
+ key: "NumericInputWindow",
823
+ onClose: function onClose() {
824
+ return _this3.props.changeRedliningState({
825
+ extraAction: null
826
+ });
827
+ },
828
+ onFeatureChanged: this.updateCurrentFeature
829
+ }));
830
+ } else if (this.props.redlining.extraAction === "FeatureAttributes") {
831
+ widgets.push(/*#__PURE__*/React.createElement(FeatureAttributesWindow, {
697
832
  feature: this.props.redlining.selectedFeature,
833
+ key: "FeatureAttributesWindow",
834
+ layerid: this.props.redlining.layer,
698
835
  onClose: function onClose() {
699
- return _this2.props.changeRedliningState({
700
- numericInput: false
836
+ return _this3.props.changeRedliningState({
837
+ extraAction: null
701
838
  });
702
839
  },
703
840
  onFeatureChanged: this.updateCurrentFeature
841
+ }));
842
+ }
843
+ if (this.state.showRecordLocation) {
844
+ var drawInteraction = this.interactions.find(function (interaction) {
845
+ return interaction instanceof ol.interaction.Draw;
704
846
  });
847
+ widgets.push(/*#__PURE__*/React.createElement(LocationRecorder, {
848
+ drawInteraction: drawInteraction,
849
+ geomType: this.props.redlining.geomType,
850
+ key: "LocationRecorder",
851
+ map: this.props.map
852
+ }));
705
853
  }
706
- return null;
854
+ return widgets;
707
855
  }
708
856
  }]);
709
857
  }(React.Component);
@@ -711,6 +859,7 @@ _defineProperty(RedliningSupport, "propTypes", {
711
859
  addLayerFeatures: PropTypes.func,
712
860
  changeRedliningState: PropTypes.func,
713
861
  displayCrs: PropTypes.string,
862
+ layers: PropTypes.array,
714
863
  map: PropTypes.object,
715
864
  mapCrs: PropTypes.string,
716
865
  redlining: PropTypes.object,
@@ -723,7 +872,8 @@ export default connect(function (state) {
723
872
  return {
724
873
  displayCrs: state.map.displayCrs,
725
874
  mapCrs: state.map.projection,
726
- redlining: state.redlining
875
+ redlining: state.redlining,
876
+ layers: state.layers.flat
727
877
  };
728
878
  }, {
729
879
  changeRedliningState: changeRedliningState,