umap-project 3.1.2__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 (199) 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 +22 -18
  4. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  5. umap/locale/fr/LC_MESSAGES/django.po +21 -17
  6. umap/management/commands/export_pictogram.py +29 -0
  7. umap/management/commands/migrate_to_S3.py +5 -1
  8. umap/management/commands/purge_old_versions.py +8 -6
  9. umap/settings/__init__.py +21 -0
  10. umap/settings/base.py +3 -0
  11. umap/static/umap/content.css +7 -2
  12. umap/static/umap/css/contextmenu.css +58 -2
  13. umap/static/umap/css/form.css +175 -45
  14. umap/static/umap/css/icon.css +97 -3
  15. umap/static/umap/css/panel.css +31 -1
  16. umap/static/umap/img/16-white.svg +21 -40
  17. umap/static/umap/img/16.svg +1 -1
  18. umap/static/umap/img/24-white.svg +9 -9
  19. umap/static/umap/img/24.svg +23 -10
  20. umap/static/umap/img/source/16-white.svg +23 -41
  21. umap/static/umap/img/source/16.svg +1 -1
  22. umap/static/umap/img/source/24-white.svg +11 -11
  23. umap/static/umap/img/source/24.svg +25 -12
  24. umap/static/umap/js/modules/browser.js +1 -1
  25. umap/static/umap/js/modules/caption.js +8 -0
  26. umap/static/umap/js/modules/data/features.js +331 -202
  27. umap/static/umap/js/modules/data/layer.js +263 -152
  28. umap/static/umap/js/modules/facets.js +2 -2
  29. umap/static/umap/js/modules/form/builder.js +11 -7
  30. umap/static/umap/js/modules/form/fields.js +66 -26
  31. umap/static/umap/js/modules/formatter.js +78 -28
  32. umap/static/umap/js/modules/importer.js +6 -1
  33. umap/static/umap/js/modules/importers/opendata.js +138 -33
  34. umap/static/umap/js/modules/importers/openrouteservice.js +140 -0
  35. umap/static/umap/js/modules/managers.js +67 -0
  36. umap/static/umap/js/modules/printer.js +107 -0
  37. umap/static/umap/js/modules/rendering/controls.js +78 -2
  38. umap/static/umap/js/modules/rendering/icon.js +116 -87
  39. umap/static/umap/js/modules/rendering/layers/classified.js +8 -7
  40. umap/static/umap/js/modules/rendering/layers/cluster.js +199 -63
  41. umap/static/umap/js/modules/rendering/map.js +6 -2
  42. umap/static/umap/js/modules/rendering/template.js +71 -1
  43. umap/static/umap/js/modules/rendering/ui.js +111 -34
  44. umap/static/umap/js/modules/rules.js +76 -23
  45. umap/static/umap/js/modules/schema.js +27 -0
  46. umap/static/umap/js/modules/share.js +19 -12
  47. umap/static/umap/js/modules/slideshow.js +1 -1
  48. umap/static/umap/js/modules/sync/updaters.js +1 -6
  49. umap/static/umap/js/modules/tableeditor.js +13 -37
  50. umap/static/umap/js/modules/templates.js +7 -6
  51. umap/static/umap/js/modules/ui/bar.js +6 -1
  52. umap/static/umap/js/modules/ui/base.js +24 -9
  53. umap/static/umap/js/modules/ui/contextmenu.js +17 -7
  54. umap/static/umap/js/modules/ui/dialog.js +7 -4
  55. umap/static/umap/js/modules/ui/panel.js +7 -0
  56. umap/static/umap/js/modules/umap.js +84 -67
  57. umap/static/umap/js/modules/utils.js +8 -7
  58. umap/static/umap/js/umap.controls.js +22 -57
  59. umap/static/umap/locale/am_ET.js +81 -9
  60. umap/static/umap/locale/am_ET.json +81 -9
  61. umap/static/umap/locale/ar.js +81 -9
  62. umap/static/umap/locale/ar.json +81 -9
  63. umap/static/umap/locale/ast.js +81 -9
  64. umap/static/umap/locale/ast.json +81 -9
  65. umap/static/umap/locale/bg.js +81 -9
  66. umap/static/umap/locale/bg.json +81 -9
  67. umap/static/umap/locale/br.js +68 -29
  68. umap/static/umap/locale/br.json +68 -29
  69. umap/static/umap/locale/ca.js +88 -16
  70. umap/static/umap/locale/ca.json +88 -16
  71. umap/static/umap/locale/cs_CZ.js +81 -9
  72. umap/static/umap/locale/cs_CZ.json +81 -9
  73. umap/static/umap/locale/da.js +48 -9
  74. umap/static/umap/locale/da.json +48 -9
  75. umap/static/umap/locale/de.js +48 -9
  76. umap/static/umap/locale/de.json +48 -9
  77. umap/static/umap/locale/el.js +58 -13
  78. umap/static/umap/locale/el.json +58 -13
  79. umap/static/umap/locale/en.js +48 -9
  80. umap/static/umap/locale/en.json +48 -9
  81. umap/static/umap/locale/en_US.json +81 -9
  82. umap/static/umap/locale/es.js +48 -9
  83. umap/static/umap/locale/es.json +48 -9
  84. umap/static/umap/locale/et.js +81 -9
  85. umap/static/umap/locale/et.json +81 -9
  86. umap/static/umap/locale/eu.js +97 -25
  87. umap/static/umap/locale/eu.json +97 -25
  88. umap/static/umap/locale/fa_IR.js +81 -9
  89. umap/static/umap/locale/fa_IR.json +81 -9
  90. umap/static/umap/locale/fi.js +81 -9
  91. umap/static/umap/locale/fi.json +81 -9
  92. umap/static/umap/locale/fr.js +48 -9
  93. umap/static/umap/locale/fr.json +48 -9
  94. umap/static/umap/locale/gl.js +81 -9
  95. umap/static/umap/locale/gl.json +81 -9
  96. umap/static/umap/locale/he.js +81 -9
  97. umap/static/umap/locale/he.json +81 -9
  98. umap/static/umap/locale/hr.js +81 -9
  99. umap/static/umap/locale/hr.json +81 -9
  100. umap/static/umap/locale/hu.js +72 -27
  101. umap/static/umap/locale/hu.json +72 -27
  102. umap/static/umap/locale/id.js +81 -9
  103. umap/static/umap/locale/id.json +81 -9
  104. umap/static/umap/locale/is.js +81 -9
  105. umap/static/umap/locale/is.json +81 -9
  106. umap/static/umap/locale/it.js +48 -9
  107. umap/static/umap/locale/it.json +48 -9
  108. umap/static/umap/locale/ja.js +81 -9
  109. umap/static/umap/locale/ja.json +81 -9
  110. umap/static/umap/locale/ko.js +81 -9
  111. umap/static/umap/locale/ko.json +81 -9
  112. umap/static/umap/locale/lt.js +81 -9
  113. umap/static/umap/locale/lt.json +81 -9
  114. umap/static/umap/locale/ms.js +81 -9
  115. umap/static/umap/locale/ms.json +81 -9
  116. umap/static/umap/locale/nl.js +48 -9
  117. umap/static/umap/locale/nl.json +48 -9
  118. umap/static/umap/locale/no.js +81 -9
  119. umap/static/umap/locale/no.json +81 -9
  120. umap/static/umap/locale/pl.js +81 -9
  121. umap/static/umap/locale/pl.json +81 -9
  122. umap/static/umap/locale/pl_PL.json +81 -9
  123. umap/static/umap/locale/pt.js +81 -9
  124. umap/static/umap/locale/pt.json +81 -9
  125. umap/static/umap/locale/pt_BR.js +91 -19
  126. umap/static/umap/locale/pt_BR.json +91 -19
  127. umap/static/umap/locale/pt_PT.js +81 -9
  128. umap/static/umap/locale/pt_PT.json +81 -9
  129. umap/static/umap/locale/ro.js +81 -9
  130. umap/static/umap/locale/ro.json +81 -9
  131. umap/static/umap/locale/ru.js +81 -9
  132. umap/static/umap/locale/ru.json +81 -9
  133. umap/static/umap/locale/sk_SK.js +81 -9
  134. umap/static/umap/locale/sk_SK.json +81 -9
  135. umap/static/umap/locale/sl.js +81 -9
  136. umap/static/umap/locale/sl.json +81 -9
  137. umap/static/umap/locale/sr.js +81 -9
  138. umap/static/umap/locale/sr.json +81 -9
  139. umap/static/umap/locale/sv.js +81 -9
  140. umap/static/umap/locale/sv.json +81 -9
  141. umap/static/umap/locale/th_TH.js +81 -9
  142. umap/static/umap/locale/th_TH.json +81 -9
  143. umap/static/umap/locale/tr.js +81 -9
  144. umap/static/umap/locale/tr.json +81 -9
  145. umap/static/umap/locale/uk_UA.js +81 -9
  146. umap/static/umap/locale/uk_UA.json +81 -9
  147. umap/static/umap/locale/vi.js +81 -9
  148. umap/static/umap/locale/vi.json +81 -9
  149. umap/static/umap/locale/vi_VN.json +81 -9
  150. umap/static/umap/locale/zh.js +81 -9
  151. umap/static/umap/locale/zh.json +81 -9
  152. umap/static/umap/locale/zh_CN.json +81 -9
  153. umap/static/umap/locale/zh_TW.Big5.json +81 -9
  154. umap/static/umap/locale/zh_TW.js +98 -26
  155. umap/static/umap/locale/zh_TW.json +98 -26
  156. umap/static/umap/map.css +325 -102
  157. umap/static/umap/vars.css +1 -0
  158. umap/static/umap/vendors/betterknown/betterknown.mjs +287 -0
  159. umap/static/umap/vendors/editable/Leaflet.Editable.js +3 -1
  160. umap/static/umap/vendors/openrouteservice/ors-js-client.js +521 -0
  161. umap/static/umap/vendors/openrouteservice/ors-js-client.js.map +1 -0
  162. umap/static/umap/vendors/simple-elevation-chart/elevation.js +63 -0
  163. umap/static/umap/vendors/simple-elevation-chart/elevation.svg +8 -0
  164. umap/static/umap/vendors/snapdom/snapdom.min.mjs +3 -0
  165. umap/storage/fs.py +3 -2
  166. umap/storage/staticfiles.py +12 -0
  167. umap/templates/base.html +4 -1
  168. umap/templates/umap/css.html +0 -4
  169. umap/templates/umap/js.html +1 -3
  170. umap/tests/base.py +9 -1
  171. umap/tests/integration/test_basics.py +3 -1
  172. umap/tests/integration/test_conditional_rules.py +79 -37
  173. umap/tests/integration/test_datalayer.py +1 -1
  174. umap/tests/integration/test_draw_polygon.py +3 -5
  175. umap/tests/integration/test_draw_polyline.py +4 -6
  176. umap/tests/integration/test_draw_route.py +178 -0
  177. umap/tests/integration/test_edit_datalayer.py +1 -1
  178. umap/tests/integration/test_edit_map.py +1 -1
  179. umap/tests/integration/test_edit_marker.py +8 -8
  180. umap/tests/integration/test_edit_polygon.py +2 -2
  181. umap/tests/integration/test_export_map.py +84 -10
  182. umap/tests/integration/test_import.py +140 -0
  183. umap/tests/integration/test_map_preview.py +1 -1
  184. umap/tests/integration/test_optimistic_merge.py +72 -12
  185. umap/tests/integration/test_share.py +1 -1
  186. umap/tests/integration/test_tableeditor.py +10 -7
  187. umap/tests/integration/test_websocket_sync.py +4 -4
  188. umap/utils.py +37 -0
  189. umap/views.py +18 -2
  190. umap_project-3.3.0.dist-info/METADATA +76 -0
  191. {umap_project-3.1.2.dist-info → umap_project-3.3.0.dist-info}/RECORD +194 -188
  192. umap/static/umap/vendors/markercluster/MarkerCluster.Default.css +0 -60
  193. umap/static/umap/vendors/markercluster/MarkerCluster.css +0 -14
  194. umap/static/umap/vendors/markercluster/leaflet.markercluster.js +0 -2
  195. umap/static/umap/vendors/markercluster/leaflet.markercluster.js.map +0 -1
  196. umap_project-3.1.2.dist-info/METADATA +0 -68
  197. {umap_project-3.1.2.dist-info → umap_project-3.3.0.dist-info}/WHEEL +0 -0
  198. {umap_project-3.1.2.dist-info → umap_project-3.3.0.dist-info}/entry_points.txt +0 -0
  199. {umap_project-3.1.2.dist-info → umap_project-3.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -3,16 +3,21 @@ import {
3
3
  DomUtil,
4
4
  GeoJSON,
5
5
  LineUtil,
6
- stamp,
7
6
  } from '../../../vendors/leaflet/leaflet-src.esm.js'
8
7
  import { uMapAlert as Alert } from '../../components/alerts/alert.js'
9
8
  import { MutatingForm } from '../form/builder.js'
10
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'
11
15
  import loadPopup from '../rendering/popup.js'
12
16
  import {
13
17
  LeafletMarker,
14
18
  LeafletPolygon,
15
19
  LeafletPolyline,
20
+ LeafletRoute,
16
21
  MaskPolygon,
17
22
  } from '../rendering/ui.js'
18
23
  import { SCHEMA } from '../schema.js'
@@ -84,6 +89,19 @@ class Feature {
84
89
  this.pushGeometry()
85
90
  }
86
91
 
92
+ get fields() {
93
+ // Fields are user defined properties
94
+ return [...this.datalayer.fields, ...this._umap.fields]
95
+ }
96
+
97
+ setter(key, value) {
98
+ if (key === 'datalayer') {
99
+ this.changeDataLayer(value)
100
+ } else {
101
+ Utils.setObjectValue(this, key, value)
102
+ }
103
+ }
104
+
87
105
  isOnScreen(bounds) {
88
106
  return this.ui?.isOnScreen(bounds)
89
107
  }
@@ -198,14 +216,15 @@ class Feature {
198
216
  if (this._umap.currentFeature === this) {
199
217
  this.view()
200
218
  }
201
- this.datalayer.indexProperties(this)
202
219
  }
203
220
  this.redraw()
204
221
  }
205
222
 
206
223
  edit(event) {
207
224
  if (!this._umap.editEnabled || this.isReadOnly()) return
208
- if (this._umap.editedFeature === this && !event.force) return
225
+ if (this._umap.editedFeature === this && !event?.force) return
226
+ // If this feature is active (popup open), let's close it.
227
+ this.deactivate()
209
228
  const container = DomUtil.create('div', 'umap-feature-container')
210
229
  DomUtil.createTitle(
211
230
  container,
@@ -221,51 +240,49 @@ class Feature {
221
240
  container.appendChild(builder.build())
222
241
 
223
242
  const properties = []
224
- let labelKeyFound = undefined
225
- for (const property of this.datalayer.allProperties()) {
226
- if (!labelKeyFound && U.LABEL_KEYS.includes(property)) {
227
- labelKeyFound = property
228
- continue
243
+ for (const field of this.fields) {
244
+ let handler = 'Input'
245
+ if (field.key === 'description' || field.type === 'Text') {
246
+ handler = 'Textarea'
229
247
  }
230
- if (property === 'description') {
231
- continue
232
- }
233
- properties.push([`properties.${property}`, { label: property }])
234
- }
235
- // We always want name and description for now (properties management to come)
236
- properties.unshift('properties.description')
237
- if (!labelKeyFound) {
238
- labelKeyFound = U.DEFAULT_LABEL_KEY
248
+ properties.push([`properties.${field.key}`, { label: field.key, handler }])
239
249
  }
240
- properties.unshift([`properties.${labelKeyFound}`, { label: labelKeyFound }])
241
250
  builder = new MutatingForm(this, properties, {
242
251
  id: 'umap-feature-properties',
243
252
  })
244
253
  const form = builder.build()
245
254
  container.appendChild(form)
246
255
  const button = Utils.loadTemplate(
247
- `<button type="button"><i class="icon icon-16 icon-add"></i>${translate('Add a new property')}</button>`
256
+ `<button type="button"><i class="icon icon-16 icon-add"></i>${translate('Add a new field')}</button>`
248
257
  )
249
258
  button.addEventListener('click', () => {
250
259
  this.datalayer.addProperty().then(() => this.edit({ force: true }))
251
260
  })
252
261
  form.appendChild(button)
253
262
  this.appendEditFieldsets(container)
254
- const advancedActions = DomUtil.createFieldset(
255
- container,
256
- translate('Advanced actions')
257
- )
258
- this.getAdvancedEditActions(advancedActions)
259
- const onLoad = this._umap.editPanel.open({ content: container })
260
- onLoad.then(() => {
261
- builder.helpers[`properties.${labelKeyFound}`].input.focus()
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(() => {
273
+ builder.form.querySelector('input')?.focus()
262
274
  })
263
275
  this._umap.editedFeature = this
264
276
  if (!this.ui.isOnScreen(this._umap._leafletMap.getBounds())) this.zoomTo(event)
277
+ return onPanelLoaded
265
278
  }
266
279
 
267
280
  toggleEditing() {
268
- this.edit()
281
+ if (this._umap.editedFeature === this) {
282
+ this._umap.editPanel.close()
283
+ } else {
284
+ this.edit()
285
+ }
269
286
  }
270
287
 
271
288
  getAdvancedEditActions(container) {
@@ -378,8 +395,6 @@ class Feature {
378
395
 
379
396
  connectToDataLayer(datalayer) {
380
397
  this.datalayer = datalayer
381
- // FIXME should be in layer/ui
382
- this.ui.options.renderer = this.datalayer.renderer
383
398
  }
384
399
 
385
400
  disconnectFromDataLayer(datalayer) {
@@ -416,7 +431,6 @@ class Feature {
416
431
  if (this.datalayer) {
417
432
  this.datalayer.removeFeature(this)
418
433
  }
419
-
420
434
  datalayer.addFeature(this)
421
435
  this.sync.upsert(this.toGeoJSON())
422
436
  this.redraw()
@@ -505,21 +519,6 @@ class Feature {
505
519
  })
506
520
  }
507
521
 
508
- getInplaceEditMenu() {
509
- return [
510
- {
511
- action: () => this.toggleEditing(),
512
- title: translate('Toggle edit mode (⇧+Click)'),
513
- icon: 'icon-edit',
514
- },
515
- {
516
- action: () => this.del(),
517
- title: translate('Delete this feature'),
518
- icon: 'icon-delete',
519
- },
520
- ]
521
- }
522
-
523
522
  isFiltered() {
524
523
  const filterKeys = this.datalayer.getFilterKeys()
525
524
  const filter = this._umap.browser.options.filter
@@ -607,7 +606,7 @@ class Feature {
607
606
  }
608
607
 
609
608
  getRank() {
610
- return this.datalayer._index.indexOf(L.stamp(this))
609
+ return this.datalayer.features.getIndex(this)
611
610
  }
612
611
 
613
612
  redraw() {
@@ -622,9 +621,14 @@ class Feature {
622
621
  }
623
622
  }
624
623
 
625
- getContextMenuItems(event) {
624
+ getContextMenu(event) {
626
625
  const permalink = this.getPermalink()
627
- let items = []
626
+ const items = []
627
+ if (this._umap.editEnabled && !this.isReadOnly()) {
628
+ items.push({
629
+ items: this.getEditContextMenu(event),
630
+ })
631
+ }
628
632
  if (permalink) {
629
633
  items.push({
630
634
  label: translate('Permalink'),
@@ -646,32 +650,43 @@ class Feature {
646
650
  this._umap.tooltip.open({ content: L._('✅ Copied!') })
647
651
  },
648
652
  })
649
- if (this._umap.editEnabled && !this.isReadOnly()) {
650
- items = items.concat(this.getContextMenuEditItems(event))
651
- }
652
653
  return items
653
654
  }
654
655
 
655
- getContextMenuEditItems() {
656
- let items = ['-']
657
- if (this._umap.editedFeature !== this) {
658
- items.push({
659
- label: `${translate('Edit this feature')} (⇧+Click)`,
660
- action: () => this.edit(),
661
- })
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))
662
673
  }
663
- items = items.concat(
674
+ items.push(
675
+ '-',
664
676
  {
665
- label: this._umap.help.displayLabel('EDIT_FEATURE_LAYER'),
677
+ title: this._umap.help.displayLabel('EDIT_FEATURE_LAYER', false),
678
+ icon: 'icon-layers',
666
679
  action: () => this.datalayer.edit(),
667
680
  },
668
681
  {
669
- label: translate('Delete this feature'),
670
- action: () => this.del(),
682
+ title: translate('Clone this feature'),
683
+ icon: 'icon-copy',
684
+ action: () => this.clone(),
671
685
  },
672
686
  {
673
- label: translate('Clone this feature'),
674
- action: () => this.clone(),
687
+ title: translate('Delete this feature'),
688
+ icon: 'icon-delete',
689
+ action: () => this.del(),
675
690
  }
676
691
  )
677
692
  return items
@@ -686,7 +701,10 @@ class Feature {
686
701
  }
687
702
 
688
703
  deactivate() {
689
- 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
+ }
690
708
  }
691
709
  }
692
710
 
@@ -731,6 +749,7 @@ export class Point extends Feature {
731
749
  return [
732
750
  'properties._umap_options.color',
733
751
  'properties._umap_options.iconClass',
752
+ 'properties._umap_options.iconSize',
734
753
  'properties._umap_options.iconUrl',
735
754
  'properties._umap_options.iconOpacity',
736
755
  ]
@@ -764,11 +783,11 @@ export class Point extends Feature {
764
783
 
765
784
  zoomTo(event) {
766
785
  if (this.datalayer.isClustered() && !this.ui._icon) {
767
- // callback is mandatory for zoomToShowLayer
768
- this.datalayer.layer.zoomToShowLayer(this.ui, event.callback || (() => {}))
769
- } else {
770
- super.zoomTo(event)
786
+ this.ui._cluster.zoomToCoverage().then(() => {
787
+ this.ui._cluster.spiderfy()
788
+ })
771
789
  }
790
+ super.zoomTo(event)
772
791
  }
773
792
  }
774
793
 
@@ -785,16 +804,11 @@ class Path extends Feature {
785
804
  this.ui.setLatLngs(latlngs)
786
805
  }
787
806
 
788
- connectToDataLayer(datalayer) {
789
- super.connectToDataLayer(datalayer)
790
- // We keep markers on their own layer on top of the paths.
791
- this.ui.options.pane = this.datalayer.pane
792
- }
793
-
794
807
  edit(event) {
795
808
  if (this._umap.editEnabled) {
796
- super.edit(event)
809
+ const promise = super.edit(event)
797
810
  if (!this.ui.editEnabled()) this.ui.makeGeometryEditable()
811
+ return promise
798
812
  }
799
813
  }
800
814
 
@@ -852,33 +866,6 @@ class Path extends Feature {
852
866
  return other
853
867
  }
854
868
 
855
- getInplaceEditMenu(event) {
856
- const items = super.getInplaceEditMenu()
857
- if (this.isMulti()) {
858
- items.push({
859
- action: () => this.ui.enableEdit().deleteShapeAt(event.latlng),
860
- title: translate('Delete this shape'),
861
- icon: 'icon-delete-shape',
862
- })
863
- items.push({
864
- action: () => this.ui.isolateShape(event.latlng),
865
- title: translate('Extract shape to separate feature'),
866
- icon: 'icon-extract-shape',
867
- })
868
- }
869
- return items
870
- }
871
-
872
- getInplaceEditVertexMenu(event) {
873
- return [
874
- {
875
- action: () => event.vertex.delete(),
876
- title: translate('Delete this vertex (Alt+Click)'),
877
- icon: 'icon-delete-vertex',
878
- },
879
- ]
880
- }
881
-
882
869
  zoomTo({ easing, callback }) {
883
870
  // Use bounds instead of centroid for paths.
884
871
  easing = easing || this._umap.getProperty('easing')
@@ -893,62 +880,55 @@ class Path extends Feature {
893
880
  if (callback) callback.call(this)
894
881
  }
895
882
 
896
- getContextMenuItems(event) {
897
- const items = super.getContextMenuItems(event)
883
+ getContextMenu(event) {
884
+ const items = super.getContextMenu(event)
898
885
  items.push({
899
886
  label: translate('Display measure'),
900
887
  action: () => Alert.info(this.ui.getMeasure()),
901
888
  })
902
- if (this._umap.editEnabled && !this.isReadOnly() && this.isMulti()) {
903
- items.push(...this.getContextMenuMultiItems(event))
904
- }
905
889
  return items
906
890
  }
907
891
 
908
- getContextMenuMultiItems(event) {
909
- const items = [
910
- '-',
892
+ getVertexTools(event) {
893
+ return [
911
894
  {
912
- label: translate('Remove shape from the multi'),
913
- action: () => {
914
- this.ui.enableEdit().deleteShapeAt(event.latlng)
915
- },
895
+ action: () => event.vertex.delete(),
896
+ title: translate('Delete this vertex (Alt+Click)'),
897
+ icon: 'icon-delete-vertex',
916
898
  },
917
899
  ]
918
- const shape = this.ui.shapeAt(event.latlng)
919
- if (this.ui._latlngs.indexOf(shape) > 0) {
920
- items.push({
921
- label: translate('Make main shape'),
922
- action: () => {
923
- this.ui.enableEdit().deleteShape(shape)
924
- this.ui.editor.prependShape(shape)
925
- },
926
- })
927
- }
928
- return items
929
900
  }
930
901
 
931
- getContextMenuEditItems(event) {
932
- 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
+ }
933
920
  if (
934
921
  this._umap?.editedFeature !== this &&
935
922
  this.isSameClass(this._umap.editedFeature)
936
923
  ) {
937
924
  items.push({
938
- label: translate('Transfer shape to edited feature'),
925
+ title: translate('Transfer shape to edited feature'),
926
+ icon: 'icon-transfer-shape',
939
927
  action: () => {
940
928
  this.transferShape(event.latlng, this._umap.editedFeature)
941
929
  },
942
930
  })
943
931
  }
944
- if (this.isMulti()) {
945
- items.push({
946
- label: translate('Extract shape to separate feature'),
947
- action: () => {
948
- this.ui.isolateShape(event.latlng)
949
- },
950
- })
951
- }
952
932
  return items
953
933
  }
954
934
  }
@@ -980,13 +960,63 @@ export class LineString extends Path {
980
960
  }
981
961
 
982
962
  getUIClass() {
983
- 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
984
969
  }
985
970
 
986
971
  isSameClass(other) {
987
972
  return other instanceof LineString
988
973
  }
989
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
+
990
1020
  toPolygon() {
991
1021
  const geojson = this.toGeoJSON()
992
1022
  geojson.geometry.type = 'Polygon'
@@ -1001,15 +1031,51 @@ export class LineString extends Path {
1001
1031
  this.del()
1002
1032
  }
1003
1033
 
1034
+ isRoute() {
1035
+ return (
1036
+ !!this.properties._umap_options.route &&
1037
+ this.properties._umap_options.route.active !== false
1038
+ )
1039
+ }
1040
+
1004
1041
  getAdvancedEditActions(container) {
1005
1042
  super.getAdvancedEditActions(container)
1006
- DomUtil.createButton(
1007
- 'button umap-to-polygon',
1008
- container,
1009
- translate('Transform to polygon'),
1010
- this.toPolygon,
1011
- this
1012
- )
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
+ }
1013
1079
  }
1014
1080
 
1015
1081
  _mergeShapes(from, to) {
@@ -1061,38 +1127,40 @@ export class LineString extends Path {
1061
1127
  return !LineUtil.isFlat(this.coordinates) && this.coordinates.length > 1
1062
1128
  }
1063
1129
 
1064
- getContextMenuEditItems(event) {
1065
- const items = super.getContextMenuEditItems(event)
1066
- const vertexClicked = event.vertex
1067
- 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()) {
1068
1134
  items.push({
1069
- label: translate('Transform to polygon'),
1070
- 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(),
1071
1144
  })
1072
- }
1073
- if (vertexClicked) {
1074
- const index = event.vertex.getIndex()
1075
- if (index !== 0 && index !== event.vertex.getLastIndex()) {
1076
- items.push({
1077
- label: translate('Split line'),
1078
- action: () => event.vertex.split(),
1079
- })
1080
- } else if (index === 0 || index === event.vertex.getLastIndex()) {
1081
- items.push({
1082
- label: this._umap.help.displayLabel('CONTINUE_LINE'),
1083
- action: () => event.vertex.continue(),
1084
- })
1085
- }
1086
1145
  }
1087
1146
  return items
1088
1147
  }
1089
1148
 
1090
- getContextMenuMultiItems(event) {
1091
- const items = super.getContextMenuMultiItems(event)
1092
- items.push({
1093
- label: translate('Merge lines'),
1094
- action: () => this.mergeShapes(),
1095
- })
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
+ }
1096
1164
  return items
1097
1165
  }
1098
1166
 
@@ -1101,23 +1169,64 @@ export class LineString extends Path {
1101
1169
  return Object.assign({ gain, loss }, super.extendedProperties())
1102
1170
  }
1103
1171
 
1104
- getInplaceEditVertexMenu(event) {
1105
- const items = super.getInplaceEditVertexMenu(event)
1106
- const index = event.vertex.getIndex()
1107
- if (index === 0 || index === event.vertex.getLastIndex()) {
1108
- items.push({
1109
- action: () => event.vertex.continue(),
1110
- title: translate('Continue line'),
1111
- icon: 'icon-continue-line',
1112
- })
1113
- } else {
1114
- items.push({
1115
- action: () => event.vertex.split(),
1116
- title: translate('Split line'),
1117
- icon: 'icon-split-line',
1118
- })
1172
+ _ensureRoute() {
1173
+ if (!this.properties._umap_options.route) {
1174
+ this.properties._umap_options.route = {}
1119
1175
  }
1120
- 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())
1121
1230
  }
1122
1231
 
1123
1232
  addExtraEditFieldset(container) {
@@ -1135,6 +1244,34 @@ export class LineString extends Path {
1135
1244
  })
1136
1245
  const fieldset = DomUtil.createFieldset(container, translate('Line decoration'))
1137
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
+ })
1138
1275
  }
1139
1276
  }
1140
1277
 
@@ -1241,29 +1378,21 @@ export class Polygon extends Path {
1241
1378
  )
1242
1379
  }
1243
1380
 
1244
- getInplaceEditMenu(event) {
1245
- const items = super.getInplaceEditMenu()
1246
- items.push({
1247
- action: () => this.ui.startHole(event),
1248
- title: translate('Start a hole here'),
1249
- icon: 'icon-hole',
1250
- })
1251
- return items
1252
- }
1253
-
1254
- getContextMenuEditItems(event) {
1255
- const items = super.getContextMenuEditItems(event)
1381
+ getDrawingTools(event) {
1382
+ const items = super.getDrawingTools(event)
1256
1383
  const shape = this.ui.shapeAt(event.latlng)
1257
1384
  // No multi and no holes.
1258
1385
  if (shape && !this.isMulti() && (LineUtil.isFlat(shape) || shape.length === 1)) {
1259
1386
  items.push({
1260
- label: translate('Transform to lines'),
1387
+ title: translate('Transform to lines'),
1388
+ icon: 'icon-polyline',
1261
1389
  action: () => this.toLineString(),
1262
1390
  })
1263
1391
  }
1264
1392
  items.push({
1265
- label: translate('Start a hole here'),
1393
+ title: translate('Start a hole here'),
1266
1394
  action: () => this.ui.startHole(event),
1395
+ icon: 'icon-hole',
1267
1396
  })
1268
1397
  return items
1269
1398
  }