umap-project 3.2.0__py3-none-any.whl → 3.3.0__py3-none-any.whl

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.

Potentially problematic release.


This version of umap-project might be problematic. Click here for more details.

Files changed (173) hide show
  1. umap/__init__.py +1 -1
  2. umap/locale/en/LC_MESSAGES/django.mo +0 -0
  3. umap/locale/en/LC_MESSAGES/django.po +15 -15
  4. umap/settings/base.py +2 -0
  5. umap/static/umap/css/contextmenu.css +58 -2
  6. umap/static/umap/css/form.css +175 -45
  7. umap/static/umap/css/icon.css +20 -0
  8. umap/static/umap/img/16-white.svg +21 -40
  9. umap/static/umap/img/16.svg +1 -1
  10. umap/static/umap/img/24-white.svg +9 -9
  11. umap/static/umap/img/24.svg +23 -10
  12. umap/static/umap/img/source/16-white.svg +23 -41
  13. umap/static/umap/img/source/16.svg +1 -1
  14. umap/static/umap/img/source/24-white.svg +11 -11
  15. umap/static/umap/img/source/24.svg +25 -12
  16. umap/static/umap/js/modules/caption.js +8 -0
  17. umap/static/umap/js/modules/data/features.js +317 -173
  18. umap/static/umap/js/modules/data/layer.js +17 -18
  19. umap/static/umap/js/modules/form/builder.js +11 -7
  20. umap/static/umap/js/modules/form/fields.js +10 -7
  21. umap/static/umap/js/modules/formatter.js +42 -20
  22. umap/static/umap/js/modules/importer.js +6 -1
  23. umap/static/umap/js/modules/importers/opendata.js +125 -37
  24. umap/static/umap/js/modules/importers/openrouteservice.js +140 -0
  25. umap/static/umap/js/modules/managers.js +12 -4
  26. umap/static/umap/js/modules/printer.js +107 -0
  27. umap/static/umap/js/modules/rendering/controls.js +78 -2
  28. umap/static/umap/js/modules/rendering/icon.js +113 -82
  29. umap/static/umap/js/modules/rendering/layers/cluster.js +199 -63
  30. umap/static/umap/js/modules/rendering/map.js +5 -1
  31. umap/static/umap/js/modules/rendering/template.js +71 -1
  32. umap/static/umap/js/modules/rendering/ui.js +98 -34
  33. umap/static/umap/js/modules/schema.js +24 -0
  34. umap/static/umap/js/modules/share.js +19 -12
  35. umap/static/umap/js/modules/ui/bar.js +6 -1
  36. umap/static/umap/js/modules/ui/base.js +24 -9
  37. umap/static/umap/js/modules/ui/contextmenu.js +17 -7
  38. umap/static/umap/js/modules/ui/dialog.js +7 -4
  39. umap/static/umap/js/modules/umap.js +67 -61
  40. umap/static/umap/js/umap.controls.js +22 -57
  41. umap/static/umap/locale/am_ET.js +39 -4
  42. umap/static/umap/locale/am_ET.json +39 -4
  43. umap/static/umap/locale/ar.js +39 -4
  44. umap/static/umap/locale/ar.json +39 -4
  45. umap/static/umap/locale/ast.js +39 -4
  46. umap/static/umap/locale/ast.json +39 -4
  47. umap/static/umap/locale/bg.js +39 -4
  48. umap/static/umap/locale/bg.json +39 -4
  49. umap/static/umap/locale/br.js +39 -4
  50. umap/static/umap/locale/br.json +39 -4
  51. umap/static/umap/locale/ca.js +39 -4
  52. umap/static/umap/locale/ca.json +39 -4
  53. umap/static/umap/locale/cs_CZ.js +39 -4
  54. umap/static/umap/locale/cs_CZ.json +39 -4
  55. umap/static/umap/locale/da.js +47 -12
  56. umap/static/umap/locale/da.json +47 -12
  57. umap/static/umap/locale/de.js +39 -4
  58. umap/static/umap/locale/de.json +39 -4
  59. umap/static/umap/locale/el.js +39 -4
  60. umap/static/umap/locale/el.json +39 -4
  61. umap/static/umap/locale/en.js +39 -4
  62. umap/static/umap/locale/en.json +39 -4
  63. umap/static/umap/locale/en_US.json +39 -4
  64. umap/static/umap/locale/es.js +47 -12
  65. umap/static/umap/locale/es.json +47 -12
  66. umap/static/umap/locale/et.js +39 -4
  67. umap/static/umap/locale/et.json +39 -4
  68. umap/static/umap/locale/eu.js +79 -44
  69. umap/static/umap/locale/eu.json +79 -44
  70. umap/static/umap/locale/fa_IR.js +39 -4
  71. umap/static/umap/locale/fa_IR.json +39 -4
  72. umap/static/umap/locale/fi.js +39 -4
  73. umap/static/umap/locale/fi.json +39 -4
  74. umap/static/umap/locale/fr.js +39 -4
  75. umap/static/umap/locale/fr.json +39 -4
  76. umap/static/umap/locale/gl.js +39 -4
  77. umap/static/umap/locale/gl.json +39 -4
  78. umap/static/umap/locale/he.js +39 -4
  79. umap/static/umap/locale/he.json +39 -4
  80. umap/static/umap/locale/hr.js +39 -4
  81. umap/static/umap/locale/hr.json +39 -4
  82. umap/static/umap/locale/hu.js +39 -4
  83. umap/static/umap/locale/hu.json +39 -4
  84. umap/static/umap/locale/id.js +39 -4
  85. umap/static/umap/locale/id.json +39 -4
  86. umap/static/umap/locale/is.js +39 -4
  87. umap/static/umap/locale/is.json +39 -4
  88. umap/static/umap/locale/it.js +39 -4
  89. umap/static/umap/locale/it.json +39 -4
  90. umap/static/umap/locale/ja.js +39 -4
  91. umap/static/umap/locale/ja.json +39 -4
  92. umap/static/umap/locale/ko.js +39 -4
  93. umap/static/umap/locale/ko.json +39 -4
  94. umap/static/umap/locale/lt.js +39 -4
  95. umap/static/umap/locale/lt.json +39 -4
  96. umap/static/umap/locale/ms.js +39 -4
  97. umap/static/umap/locale/ms.json +39 -4
  98. umap/static/umap/locale/nl.js +39 -4
  99. umap/static/umap/locale/nl.json +39 -4
  100. umap/static/umap/locale/no.js +39 -4
  101. umap/static/umap/locale/no.json +39 -4
  102. umap/static/umap/locale/pl.js +39 -4
  103. umap/static/umap/locale/pl.json +39 -4
  104. umap/static/umap/locale/pl_PL.json +39 -4
  105. umap/static/umap/locale/pt.js +39 -4
  106. umap/static/umap/locale/pt.json +39 -4
  107. umap/static/umap/locale/pt_BR.js +39 -4
  108. umap/static/umap/locale/pt_BR.json +39 -4
  109. umap/static/umap/locale/pt_PT.js +39 -4
  110. umap/static/umap/locale/pt_PT.json +39 -4
  111. umap/static/umap/locale/ro.js +39 -4
  112. umap/static/umap/locale/ro.json +39 -4
  113. umap/static/umap/locale/ru.js +39 -4
  114. umap/static/umap/locale/ru.json +39 -4
  115. umap/static/umap/locale/sk_SK.js +39 -4
  116. umap/static/umap/locale/sk_SK.json +39 -4
  117. umap/static/umap/locale/sl.js +39 -4
  118. umap/static/umap/locale/sl.json +39 -4
  119. umap/static/umap/locale/sr.js +39 -4
  120. umap/static/umap/locale/sr.json +39 -4
  121. umap/static/umap/locale/sv.js +39 -4
  122. umap/static/umap/locale/sv.json +39 -4
  123. umap/static/umap/locale/th_TH.js +39 -4
  124. umap/static/umap/locale/th_TH.json +39 -4
  125. umap/static/umap/locale/tr.js +39 -4
  126. umap/static/umap/locale/tr.json +39 -4
  127. umap/static/umap/locale/uk_UA.js +39 -4
  128. umap/static/umap/locale/uk_UA.json +39 -4
  129. umap/static/umap/locale/vi.js +39 -4
  130. umap/static/umap/locale/vi.json +39 -4
  131. umap/static/umap/locale/vi_VN.json +39 -4
  132. umap/static/umap/locale/zh.js +39 -4
  133. umap/static/umap/locale/zh.json +39 -4
  134. umap/static/umap/locale/zh_CN.json +39 -4
  135. umap/static/umap/locale/zh_TW.Big5.json +39 -4
  136. umap/static/umap/locale/zh_TW.js +98 -63
  137. umap/static/umap/locale/zh_TW.json +98 -63
  138. umap/static/umap/map.css +90 -41
  139. umap/static/umap/vars.css +1 -0
  140. umap/static/umap/vendors/editable/Leaflet.Editable.js +3 -1
  141. umap/static/umap/vendors/openrouteservice/ors-js-client.js +521 -0
  142. umap/static/umap/vendors/openrouteservice/ors-js-client.js.map +1 -0
  143. umap/static/umap/vendors/simple-elevation-chart/elevation.js +63 -0
  144. umap/static/umap/vendors/simple-elevation-chart/elevation.svg +8 -0
  145. umap/static/umap/vendors/snapdom/snapdom.min.mjs +3 -0
  146. umap/storage/staticfiles.py +12 -0
  147. umap/templates/umap/css.html +0 -4
  148. umap/templates/umap/js.html +1 -3
  149. umap/tests/integration/test_basics.py +2 -0
  150. umap/tests/integration/test_conditional_rules.py +17 -17
  151. umap/tests/integration/test_datalayer.py +1 -1
  152. umap/tests/integration/test_draw_polygon.py +3 -5
  153. umap/tests/integration/test_draw_polyline.py +4 -6
  154. umap/tests/integration/test_draw_route.py +178 -0
  155. umap/tests/integration/test_edit_map.py +1 -1
  156. umap/tests/integration/test_edit_marker.py +7 -7
  157. umap/tests/integration/test_edit_polygon.py +2 -2
  158. umap/tests/integration/test_export_map.py +74 -10
  159. umap/tests/integration/test_map_preview.py +1 -1
  160. umap/tests/integration/test_share.py +1 -1
  161. umap/tests/integration/test_tableeditor.py +4 -4
  162. umap/tests/integration/test_websocket_sync.py +4 -4
  163. umap/utils.py +5 -1
  164. umap/views.py +2 -0
  165. {umap_project-3.2.0.dist-info → umap_project-3.3.0.dist-info}/METADATA +8 -8
  166. {umap_project-3.2.0.dist-info → umap_project-3.3.0.dist-info}/RECORD +169 -165
  167. umap/static/umap/vendors/markercluster/MarkerCluster.Default.css +0 -60
  168. umap/static/umap/vendors/markercluster/MarkerCluster.css +0 -14
  169. umap/static/umap/vendors/markercluster/leaflet.markercluster.js +0 -2
  170. umap/static/umap/vendors/markercluster/leaflet.markercluster.js.map +0 -1
  171. {umap_project-3.2.0.dist-info → umap_project-3.3.0.dist-info}/WHEEL +0 -0
  172. {umap_project-3.2.0.dist-info → umap_project-3.3.0.dist-info}/entry_points.txt +0 -0
  173. {umap_project-3.2.0.dist-info → umap_project-3.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -7,11 +7,17 @@ import {
7
7
  import { uMapAlert as Alert } from '../../components/alerts/alert.js'
8
8
  import { MutatingForm } from '../form/builder.js'
9
9
  import { translate } from '../i18n.js'
10
+ import {
11
+ PREFERENCES as ORS_PREFERENCES,
12
+ PROFILES as ORS_PROFILES,
13
+ Importer as OpenRouteService,
14
+ } from '../importers/openrouteservice.js'
10
15
  import loadPopup from '../rendering/popup.js'
11
16
  import {
12
17
  LeafletMarker,
13
18
  LeafletPolygon,
14
19
  LeafletPolyline,
20
+ LeafletRoute,
15
21
  MaskPolygon,
16
22
  } from '../rendering/ui.js'
17
23
  import { SCHEMA } from '../schema.js'
@@ -88,6 +94,14 @@ class Feature {
88
94
  return [...this.datalayer.fields, ...this._umap.fields]
89
95
  }
90
96
 
97
+ setter(key, value) {
98
+ if (key === 'datalayer') {
99
+ this.changeDataLayer(value)
100
+ } else {
101
+ Utils.setObjectValue(this, key, value)
102
+ }
103
+ }
104
+
91
105
  isOnScreen(bounds) {
92
106
  return this.ui?.isOnScreen(bounds)
93
107
  }
@@ -209,6 +223,8 @@ class Feature {
209
223
  edit(event) {
210
224
  if (!this._umap.editEnabled || this.isReadOnly()) return
211
225
  if (this._umap.editedFeature === this && !event?.force) return
226
+ // If this feature is active (popup open), let's close it.
227
+ this.deactivate()
212
228
  const container = DomUtil.create('div', 'umap-feature-container')
213
229
  DomUtil.createTitle(
214
230
  container,
@@ -244,21 +260,29 @@ class Feature {
244
260
  })
245
261
  form.appendChild(button)
246
262
  this.appendEditFieldsets(container)
247
- const advancedActions = DomUtil.createFieldset(
248
- container,
249
- translate('Advanced actions')
250
- )
251
- this.getAdvancedEditActions(advancedActions)
252
- const onLoad = this._umap.editPanel.open({ content: container })
253
- onLoad.then(() => {
263
+ const [details, { fieldset }] = Utils.loadTemplateWithRefs(`
264
+ <details>
265
+ <summary>${translate('Advanced actions')}</summary>
266
+ <fieldset class="button-bar by2" data-ref=fieldset></fieldset>
267
+ </details>
268
+ `)
269
+ container.appendChild(details)
270
+ this.getAdvancedEditActions(fieldset)
271
+ const onPanelLoaded = this._umap.editPanel.open({ content: container })
272
+ onPanelLoaded.then(() => {
254
273
  builder.form.querySelector('input')?.focus()
255
274
  })
256
275
  this._umap.editedFeature = this
257
276
  if (!this.ui.isOnScreen(this._umap._leafletMap.getBounds())) this.zoomTo(event)
277
+ return onPanelLoaded
258
278
  }
259
279
 
260
280
  toggleEditing() {
261
- this.edit()
281
+ if (this._umap.editedFeature === this) {
282
+ this._umap.editPanel.close()
283
+ } else {
284
+ this.edit()
285
+ }
262
286
  }
263
287
 
264
288
  getAdvancedEditActions(container) {
@@ -407,7 +431,6 @@ class Feature {
407
431
  if (this.datalayer) {
408
432
  this.datalayer.removeFeature(this)
409
433
  }
410
-
411
434
  datalayer.addFeature(this)
412
435
  this.sync.upsert(this.toGeoJSON())
413
436
  this.redraw()
@@ -496,21 +519,6 @@ class Feature {
496
519
  })
497
520
  }
498
521
 
499
- getInplaceEditMenu() {
500
- return [
501
- {
502
- action: () => this.toggleEditing(),
503
- title: translate('Toggle edit mode (⇧+Click)'),
504
- icon: 'icon-edit',
505
- },
506
- {
507
- action: () => this.del(),
508
- title: translate('Delete this feature'),
509
- icon: 'icon-delete',
510
- },
511
- ]
512
- }
513
-
514
522
  isFiltered() {
515
523
  const filterKeys = this.datalayer.getFilterKeys()
516
524
  const filter = this._umap.browser.options.filter
@@ -613,9 +621,14 @@ class Feature {
613
621
  }
614
622
  }
615
623
 
616
- getContextMenuItems(event) {
624
+ getContextMenu(event) {
617
625
  const permalink = this.getPermalink()
618
- let items = []
626
+ const items = []
627
+ if (this._umap.editEnabled && !this.isReadOnly()) {
628
+ items.push({
629
+ items: this.getEditContextMenu(event),
630
+ })
631
+ }
619
632
  if (permalink) {
620
633
  items.push({
621
634
  label: translate('Permalink'),
@@ -637,32 +650,43 @@ class Feature {
637
650
  this._umap.tooltip.open({ content: L._('✅ Copied!') })
638
651
  },
639
652
  })
640
- if (this._umap.editEnabled && !this.isReadOnly()) {
641
- items = items.concat(this.getContextMenuEditItems(event))
642
- }
643
653
  return items
644
654
  }
645
655
 
646
- getContextMenuEditItems() {
647
- let items = ['-']
648
- if (this._umap.editedFeature !== this) {
649
- items.push({
650
- label: `${translate('Edit this feature')} (⇧+Click)`,
651
- action: () => this.edit(),
652
- })
656
+ getDrawingTools(event) {
657
+ return [
658
+ {
659
+ title: translate('Toggle edit mode (⇧+Click)'),
660
+ action: () => this.toggleEditing(),
661
+ icon: 'icon-edit',
662
+ },
663
+ ]
664
+ }
665
+
666
+ getEditContextMenu(event) {
667
+ const items = []
668
+ const vertexClicked = event.vertex
669
+ if (vertexClicked) {
670
+ items.push(...this.getVertexTools(event))
671
+ } else {
672
+ items.push(...this.getDrawingTools(event))
653
673
  }
654
- items = items.concat(
674
+ items.push(
675
+ '-',
655
676
  {
656
- label: this._umap.help.displayLabel('EDIT_FEATURE_LAYER'),
677
+ title: this._umap.help.displayLabel('EDIT_FEATURE_LAYER', false),
678
+ icon: 'icon-layers',
657
679
  action: () => this.datalayer.edit(),
658
680
  },
659
681
  {
660
- label: translate('Delete this feature'),
661
- action: () => this.del(),
682
+ title: translate('Clone this feature'),
683
+ icon: 'icon-copy',
684
+ action: () => this.clone(),
662
685
  },
663
686
  {
664
- label: translate('Clone this feature'),
665
- action: () => this.clone(),
687
+ title: translate('Delete this feature'),
688
+ icon: 'icon-delete',
689
+ action: () => this.del(),
666
690
  }
667
691
  )
668
692
  return items
@@ -677,7 +701,10 @@ class Feature {
677
701
  }
678
702
 
679
703
  deactivate() {
680
- if (this._umap.activeFeature === this) this._umap.activeFeature = undefined
704
+ if (this._umap.activeFeature === this) {
705
+ this._umap.activeFeature = undefined
706
+ this.ui.closePopup()
707
+ }
681
708
  }
682
709
  }
683
710
 
@@ -722,6 +749,7 @@ export class Point extends Feature {
722
749
  return [
723
750
  'properties._umap_options.color',
724
751
  'properties._umap_options.iconClass',
752
+ 'properties._umap_options.iconSize',
725
753
  'properties._umap_options.iconUrl',
726
754
  'properties._umap_options.iconOpacity',
727
755
  ]
@@ -755,11 +783,11 @@ export class Point extends Feature {
755
783
 
756
784
  zoomTo(event) {
757
785
  if (this.datalayer.isClustered() && !this.ui._icon) {
758
- // callback is mandatory for zoomToShowLayer
759
- this.datalayer.layer.zoomToShowLayer(this.ui, event.callback || (() => {}))
760
- } else {
761
- super.zoomTo(event)
786
+ this.ui._cluster.zoomToCoverage().then(() => {
787
+ this.ui._cluster.spiderfy()
788
+ })
762
789
  }
790
+ super.zoomTo(event)
763
791
  }
764
792
  }
765
793
 
@@ -778,8 +806,9 @@ class Path extends Feature {
778
806
 
779
807
  edit(event) {
780
808
  if (this._umap.editEnabled) {
781
- super.edit(event)
809
+ const promise = super.edit(event)
782
810
  if (!this.ui.editEnabled()) this.ui.makeGeometryEditable()
811
+ return promise
783
812
  }
784
813
  }
785
814
 
@@ -837,33 +866,6 @@ class Path extends Feature {
837
866
  return other
838
867
  }
839
868
 
840
- getInplaceEditMenu(event) {
841
- const items = super.getInplaceEditMenu()
842
- if (this.isMulti()) {
843
- items.push({
844
- action: () => this.ui.enableEdit().deleteShapeAt(event.latlng),
845
- title: translate('Delete this shape'),
846
- icon: 'icon-delete-shape',
847
- })
848
- items.push({
849
- action: () => this.ui.isolateShape(event.latlng),
850
- title: translate('Extract shape to separate feature'),
851
- icon: 'icon-extract-shape',
852
- })
853
- }
854
- return items
855
- }
856
-
857
- getInplaceEditVertexMenu(event) {
858
- return [
859
- {
860
- action: () => event.vertex.delete(),
861
- title: translate('Delete this vertex (Alt+Click)'),
862
- icon: 'icon-delete-vertex',
863
- },
864
- ]
865
- }
866
-
867
869
  zoomTo({ easing, callback }) {
868
870
  // Use bounds instead of centroid for paths.
869
871
  easing = easing || this._umap.getProperty('easing')
@@ -878,62 +880,55 @@ class Path extends Feature {
878
880
  if (callback) callback.call(this)
879
881
  }
880
882
 
881
- getContextMenuItems(event) {
882
- const items = super.getContextMenuItems(event)
883
+ getContextMenu(event) {
884
+ const items = super.getContextMenu(event)
883
885
  items.push({
884
886
  label: translate('Display measure'),
885
887
  action: () => Alert.info(this.ui.getMeasure()),
886
888
  })
887
- if (this._umap.editEnabled && !this.isReadOnly() && this.isMulti()) {
888
- items.push(...this.getContextMenuMultiItems(event))
889
- }
890
889
  return items
891
890
  }
892
891
 
893
- getContextMenuMultiItems(event) {
894
- const items = [
895
- '-',
892
+ getVertexTools(event) {
893
+ return [
896
894
  {
897
- label: translate('Remove shape from the multi'),
898
- action: () => {
899
- this.ui.enableEdit().deleteShapeAt(event.latlng)
900
- },
895
+ action: () => event.vertex.delete(),
896
+ title: translate('Delete this vertex (Alt+Click)'),
897
+ icon: 'icon-delete-vertex',
901
898
  },
902
899
  ]
903
- const shape = this.ui.shapeAt(event.latlng)
904
- if (this.ui._latlngs.indexOf(shape) > 0) {
905
- items.push({
906
- label: translate('Make main shape'),
907
- action: () => {
908
- this.ui.enableEdit().deleteShape(shape)
909
- this.ui.editor.prependShape(shape)
910
- },
911
- })
912
- }
913
- return items
914
900
  }
915
901
 
916
- getContextMenuEditItems(event) {
917
- const items = super.getContextMenuEditItems(event)
902
+ getDrawingTools(event) {
903
+ const items = super.getDrawingTools(event)
904
+ if (this.isMulti()) {
905
+ items.push(
906
+ {
907
+ title: translate('Extract shape to separate feature'),
908
+ icon: 'icon-extract-shape',
909
+ action: () => {
910
+ this.ui.isolateShape(event.latlng)
911
+ },
912
+ },
913
+ {
914
+ title: translate('Delete this shape'),
915
+ icon: 'icon-delete-shape',
916
+ action: () => this.ui.enableEdit().deleteShapeAt(event.latlng),
917
+ }
918
+ )
919
+ }
918
920
  if (
919
921
  this._umap?.editedFeature !== this &&
920
922
  this.isSameClass(this._umap.editedFeature)
921
923
  ) {
922
924
  items.push({
923
- label: translate('Transfer shape to edited feature'),
925
+ title: translate('Transfer shape to edited feature'),
926
+ icon: 'icon-transfer-shape',
924
927
  action: () => {
925
928
  this.transferShape(event.latlng, this._umap.editedFeature)
926
929
  },
927
930
  })
928
931
  }
929
- if (this.isMulti()) {
930
- items.push({
931
- label: translate('Extract shape to separate feature'),
932
- action: () => {
933
- this.ui.isolateShape(event.latlng)
934
- },
935
- })
936
- }
937
932
  return items
938
933
  }
939
934
  }
@@ -965,13 +960,63 @@ export class LineString extends Path {
965
960
  }
966
961
 
967
962
  getUIClass() {
968
- return super.getUIClass() || LeafletPolyline
963
+ const klass = super.getUIClass()
964
+ if (klass) return klass
965
+ if (this.isRoute()) {
966
+ return LeafletRoute
967
+ }
968
+ return LeafletPolyline
969
969
  }
970
970
 
971
971
  isSameClass(other) {
972
972
  return other instanceof LineString
973
973
  }
974
974
 
975
+ cancelRoute() {
976
+ const oldRoute = Utils.CopyJSON(this.properties._umap_options.route)
977
+ this.properties._umap_options.route.active = false
978
+ this.redraw()
979
+ this.edit()
980
+ this.sync.update('properties._umap_options.route', null, oldRoute)
981
+ }
982
+
983
+ restoreRoute() {
984
+ const oldRoute = Utils.CopyJSON(this.properties._umap_options.route)
985
+ this._ensureRoute()
986
+ delete this.properties._umap_options.route.active
987
+ this.redraw()
988
+ this.sync.update(
989
+ 'properties._umap_options.route',
990
+ this.properties._umap_options.route,
991
+ oldRoute
992
+ )
993
+ this.edit().then((panel) => {
994
+ panel.scrollTo('details#edit-route')
995
+ })
996
+ }
997
+
998
+ toRoute() {
999
+ this._ensureRoute()
1000
+ this.properties._umap_options.route.coordinates = Utils.CopyJSON(this.coordinates)
1001
+ this.redraw()
1002
+ this.sync.update(
1003
+ 'properties._umap_options.route',
1004
+ this.properties._umap_options.route,
1005
+ null
1006
+ )
1007
+ this.edit().then((panel) => {
1008
+ panel.scrollTo('details#edit-route')
1009
+ })
1010
+ }
1011
+
1012
+ askForRouteSettings() {
1013
+ const container = Utils.loadTemplate(
1014
+ `<div><h3>${translate('Route settings')}</h3></div>`
1015
+ )
1016
+ container.appendChild(this.routeForm())
1017
+ return this._umap.dialog.open({ template: container })
1018
+ }
1019
+
975
1020
  toPolygon() {
976
1021
  const geojson = this.toGeoJSON()
977
1022
  geojson.geometry.type = 'Polygon'
@@ -986,15 +1031,51 @@ export class LineString extends Path {
986
1031
  this.del()
987
1032
  }
988
1033
 
1034
+ isRoute() {
1035
+ return (
1036
+ !!this.properties._umap_options.route &&
1037
+ this.properties._umap_options.route.active !== false
1038
+ )
1039
+ }
1040
+
989
1041
  getAdvancedEditActions(container) {
990
1042
  super.getAdvancedEditActions(container)
991
- DomUtil.createButton(
992
- 'button umap-to-polygon',
993
- container,
994
- translate('Transform to polygon'),
995
- this.toPolygon,
996
- this
997
- )
1043
+ const button = Utils.loadTemplate(`
1044
+ <button class="button" type="button"><i class="icon icon-24 icon-polygon"></i>${translate('Transform to polygon')}</button>
1045
+ `)
1046
+ container.appendChild(button)
1047
+ button.addEventListener('click', () => this.toPolygon())
1048
+ if (this.isRoute()) {
1049
+ const button = Utils.loadTemplate(`
1050
+ <button class="button" type="button"><i class="icon icon-24 icon-polyline"></i>${translate('Transform to regular line')}</button>
1051
+ `)
1052
+ container.appendChild(button)
1053
+ button.addEventListener('click', () => this.cancelRoute())
1054
+ } else if (!this.isMulti() && this.coordinates.length < 10) {
1055
+ const button = Utils.loadTemplate(`
1056
+ <button class="button" type="button"><i class="icon icon-24 icon-route"></i>${translate('Transform to route')}</button>
1057
+ `)
1058
+ container.appendChild(button)
1059
+ button.addEventListener('click', () =>
1060
+ this.askForRouteSettings().then(() => {
1061
+ this.toRoute()
1062
+ this.computeRoute()
1063
+ })
1064
+ )
1065
+ } else if (this.properties._umap_options.route?.coordinates) {
1066
+ const button = Utils.loadTemplate(`
1067
+ <button class="button" type="button"><i class="icon icon-24 icon-route"></i>${translate('Restore route')}</button>
1068
+ `)
1069
+ container.appendChild(button)
1070
+ button.addEventListener('click', () => this.restoreRoute())
1071
+ }
1072
+ if (this._umap.properties.ORSAPIKey) {
1073
+ const button = Utils.loadTemplate(`
1074
+ <button class="button" type="button"><i class="icon icon-24 icon-mountain"></i>${translate('Compute elevations')}</button>
1075
+ `)
1076
+ container.appendChild(button)
1077
+ button.addEventListener('click', () => this.computeElevation())
1078
+ }
998
1079
  }
999
1080
 
1000
1081
  _mergeShapes(from, to) {
@@ -1046,38 +1127,40 @@ export class LineString extends Path {
1046
1127
  return !LineUtil.isFlat(this.coordinates) && this.coordinates.length > 1
1047
1128
  }
1048
1129
 
1049
- getContextMenuEditItems(event) {
1050
- const items = super.getContextMenuEditItems(event)
1051
- const vertexClicked = event.vertex
1052
- if (!this.isMulti()) {
1130
+ getVertexTools(event) {
1131
+ const items = super.getVertexTools(event)
1132
+ const index = event.vertex.getIndex()
1133
+ if (index !== 0 && index !== event.vertex.getLastIndex()) {
1053
1134
  items.push({
1054
- label: translate('Transform to polygon'),
1055
- action: () => this.toPolygon(),
1135
+ title: translate('Split line'),
1136
+ icon: 'icon-split-line',
1137
+ action: () => event.vertex.split(),
1138
+ })
1139
+ } else if (index === 0 || index === event.vertex.getLastIndex()) {
1140
+ items.push({
1141
+ title: this._umap.help.displayLabel('CONTINUE_LINE', false),
1142
+ icon: 'icon-continue-line',
1143
+ action: () => event.vertex.continue(),
1056
1144
  })
1057
- }
1058
- if (vertexClicked) {
1059
- const index = event.vertex.getIndex()
1060
- if (index !== 0 && index !== event.vertex.getLastIndex()) {
1061
- items.push({
1062
- label: translate('Split line'),
1063
- action: () => event.vertex.split(),
1064
- })
1065
- } else if (index === 0 || index === event.vertex.getLastIndex()) {
1066
- items.push({
1067
- label: this._umap.help.displayLabel('CONTINUE_LINE'),
1068
- action: () => event.vertex.continue(),
1069
- })
1070
- }
1071
1145
  }
1072
1146
  return items
1073
1147
  }
1074
1148
 
1075
- getContextMenuMultiItems(event) {
1076
- const items = super.getContextMenuMultiItems(event)
1077
- items.push({
1078
- label: translate('Merge lines'),
1079
- action: () => this.mergeShapes(),
1080
- })
1149
+ getDrawingTools(event) {
1150
+ const items = super.getDrawingTools(event)
1151
+ if (this.isMulti()) {
1152
+ items.push({
1153
+ title: translate('Merge lines'),
1154
+ icon: 'icon-merge',
1155
+ action: () => this.mergeShapes(),
1156
+ })
1157
+ } else {
1158
+ items.push({
1159
+ title: translate('Transform to polygon'),
1160
+ icon: 'icon-polygon',
1161
+ action: () => this.toPolygon(),
1162
+ })
1163
+ }
1081
1164
  return items
1082
1165
  }
1083
1166
 
@@ -1086,23 +1169,64 @@ export class LineString extends Path {
1086
1169
  return Object.assign({ gain, loss }, super.extendedProperties())
1087
1170
  }
1088
1171
 
1089
- getInplaceEditVertexMenu(event) {
1090
- const items = super.getInplaceEditVertexMenu(event)
1091
- const index = event.vertex.getIndex()
1092
- if (index === 0 || index === event.vertex.getLastIndex()) {
1093
- items.push({
1094
- action: () => event.vertex.continue(),
1095
- title: translate('Continue line'),
1096
- icon: 'icon-continue-line',
1097
- })
1098
- } else {
1099
- items.push({
1100
- action: () => event.vertex.split(),
1101
- title: translate('Split line'),
1102
- icon: 'icon-split-line',
1103
- })
1172
+ _ensureRoute() {
1173
+ if (!this.properties._umap_options.route) {
1174
+ this.properties._umap_options.route = {}
1104
1175
  }
1105
- return items
1176
+ this.properties._umap_options.route.profile ??= ORS_PROFILES[0][0]
1177
+ this.properties._umap_options.route.preference ??= ORS_PREFERENCES[0][0]
1178
+ this.properties._umap_options.route.elevation ??= false
1179
+ this.properties._umap_options.route.coordinates ??= []
1180
+ }
1181
+
1182
+ routeForm() {
1183
+ this._ensureRoute()
1184
+ const metadatas = [
1185
+ [
1186
+ 'profile',
1187
+ {
1188
+ handler: 'Select',
1189
+ selectOptions: ORS_PROFILES,
1190
+ label: translate('Profile'),
1191
+ },
1192
+ ],
1193
+ [
1194
+ 'elevation',
1195
+ {
1196
+ handler: 'Switch',
1197
+ label: translate('Compute elevations'),
1198
+ },
1199
+ ],
1200
+ [
1201
+ 'preference',
1202
+ {
1203
+ handler: 'Select',
1204
+ selectOptions: ORS_PREFERENCES,
1205
+ label: translate('Route preference'),
1206
+ },
1207
+ ],
1208
+ ]
1209
+ const form = new MutatingForm(this.properties._umap_options.route, metadatas, {
1210
+ umap: this._umap,
1211
+ })
1212
+ return form.build()
1213
+ }
1214
+
1215
+ _editRoute(container) {
1216
+ const template = `
1217
+ <details id="edit-route">
1218
+ <summary>${translate('Route settings')}</summary>
1219
+ <fieldset data-ref=fieldset></fieldset>
1220
+ </details>
1221
+ `
1222
+ const [details, { fieldset }] = Utils.loadTemplateWithRefs(template)
1223
+ container.appendChild(details)
1224
+ fieldset.appendChild(this.routeForm())
1225
+ const button = Utils.loadTemplate(
1226
+ `<button data-ref=button type="button">${translate('Compute route')}</button>`
1227
+ )
1228
+ fieldset.appendChild(button)
1229
+ button.addEventListener('click', async () => this.computeRoute())
1106
1230
  }
1107
1231
 
1108
1232
  addExtraEditFieldset(container) {
@@ -1120,6 +1244,34 @@ export class LineString extends Path {
1120
1244
  })
1121
1245
  const fieldset = DomUtil.createFieldset(container, translate('Line decoration'))
1122
1246
  fieldset.appendChild(builder.build())
1247
+ if (this._umap.properties.ORSAPIKey && this.isRoute()) {
1248
+ this._editRoute(container)
1249
+ }
1250
+ }
1251
+
1252
+ async computeElevation() {
1253
+ if (!this._umap.properties.ORSAPIKey) return
1254
+ const importer = new OpenRouteService(this._umap)
1255
+ const geometry = await importer.elevation(this.geometry)
1256
+ if (geometry?.type) {
1257
+ const oldGeometry = Utils.CopyJSON(this._geometry)
1258
+ this.geometry = geometry
1259
+ this.ui.resetTooltip()
1260
+ this.sync.update('geometry', this.geometry, oldGeometry)
1261
+ }
1262
+ }
1263
+
1264
+ async computeRoute() {
1265
+ if (!this._umap.properties.ORSAPIKey) return
1266
+ const importer = new OpenRouteService(this._umap)
1267
+ await importer.directions(this.properties._umap_options.route).then((geometry) => {
1268
+ if (geometry?.type) {
1269
+ const oldGeometry = Utils.CopyJSON(this._geometry)
1270
+ this.geometry = geometry
1271
+ this.ui.resetTooltip()
1272
+ this.sync.update('geometry', this.geometry, oldGeometry)
1273
+ }
1274
+ })
1123
1275
  }
1124
1276
  }
1125
1277
 
@@ -1226,29 +1378,21 @@ export class Polygon extends Path {
1226
1378
  )
1227
1379
  }
1228
1380
 
1229
- getInplaceEditMenu(event) {
1230
- const items = super.getInplaceEditMenu()
1231
- items.push({
1232
- action: () => this.ui.startHole(event),
1233
- title: translate('Start a hole here'),
1234
- icon: 'icon-hole',
1235
- })
1236
- return items
1237
- }
1238
-
1239
- getContextMenuEditItems(event) {
1240
- const items = super.getContextMenuEditItems(event)
1381
+ getDrawingTools(event) {
1382
+ const items = super.getDrawingTools(event)
1241
1383
  const shape = this.ui.shapeAt(event.latlng)
1242
1384
  // No multi and no holes.
1243
1385
  if (shape && !this.isMulti() && (LineUtil.isFlat(shape) || shape.length === 1)) {
1244
1386
  items.push({
1245
- label: translate('Transform to lines'),
1387
+ title: translate('Transform to lines'),
1388
+ icon: 'icon-polyline',
1246
1389
  action: () => this.toLineString(),
1247
1390
  })
1248
1391
  }
1249
1392
  items.push({
1250
- label: translate('Start a hole here'),
1393
+ title: translate('Start a hole here'),
1251
1394
  action: () => this.ui.startHole(event),
1395
+ icon: 'icon-hole',
1252
1396
  })
1253
1397
  return items
1254
1398
  }