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
@@ -22,6 +22,8 @@ import {
22
22
  PermanentCreditsControl,
23
23
  TileLayerChooser,
24
24
  LoadTemplateControl,
25
+ PrintControl,
26
+ SearchControl,
25
27
  } from './controls.js'
26
28
  import * as Utils from '../utils.js'
27
29
  import * as Icon from './icon.js'
@@ -45,6 +47,7 @@ const ControlsMixin = {
45
47
  'locate',
46
48
  'measure',
47
49
  'editinosm',
50
+ 'print',
48
51
  'tilelayers',
49
52
  ],
50
53
 
@@ -84,8 +87,9 @@ const ControlsMixin = {
84
87
  true: translate('Exit Fullscreen'),
85
88
  },
86
89
  })
87
- this._controls.search = new U.SearchControl()
90
+ this._controls.search = new SearchControl(this._umap)
88
91
  this._controls.embed = new EmbedControl(this._umap)
92
+ this._controls.print = new PrintControl(this._umap)
89
93
  this._controls.tilelayersChooser = new TileLayerChooser(this._umap)
90
94
  this._controls.editinosm = new Control.EditInOSM({
91
95
  position: 'topleft',
@@ -335,7 +339,7 @@ export const LeafletMap = BaseMap.extend({
335
339
  const datalayer = this._umap.datalayers.visible()[0]
336
340
  let feature
337
341
  if (datalayer) {
338
- const feature = datalayer.getFeatureByIndex(-1)
342
+ const feature = datalayer.features.last()
339
343
  if (feature) {
340
344
  feature.zoomTo({ callback: this.options.noControl ? null : feature.view })
341
345
  return
@@ -1,4 +1,8 @@
1
- import { DomEvent, DomUtil } from '../../../vendors/leaflet/leaflet-src.esm.js'
1
+ import {
2
+ DomEvent,
3
+ DomUtil,
4
+ CircleMarker,
5
+ } from '../../../vendors/leaflet/leaflet-src.esm.js'
2
6
  import { getLocale, translate } from '../i18n.js'
3
7
  import { Request } from '../request.js'
4
8
  import * as Utils from '../utils.js'
@@ -22,6 +26,9 @@ export default async function loadTemplate(name, feature, container) {
22
26
  case 'Wikipedia':
23
27
  klass = Wikipedia
24
28
  break
29
+ case 'Route':
30
+ klass = Route
31
+ break
25
32
  }
26
33
  const content = new klass()
27
34
  return await content.render(feature, container)
@@ -282,3 +289,66 @@ class Wikipedia extends PopupTemplate {
282
289
  return body
283
290
  }
284
291
  }
292
+
293
+ class Route extends TitleMixin(PopupTemplate) {
294
+ async renderBody(feature) {
295
+ if (feature.type !== 'LineString' || feature.isMulti()) {
296
+ return super.renderBody(feature)
297
+ }
298
+ let prev
299
+ let dist = 0
300
+ const data = []
301
+ const latlngs = feature.ui.getLatLngs()
302
+ const map = feature._umap._leafletMap
303
+ const properties = feature.extendedProperties()
304
+ for (const latlng of latlngs) {
305
+ if (!latlng.alt) {
306
+ continue
307
+ }
308
+ if (prev) {
309
+ dist = map.distance(latlng, prev)
310
+ }
311
+ data.push([latlng.alt, dist])
312
+ prev = latlng
313
+ }
314
+ const [root, { altitude, chart }] = Utils.loadTemplateWithRefs(`
315
+ <div>
316
+ <p>
317
+ ${translate('Distance:')} ${properties.measure} •
318
+ ${translate('Gain:')} ${properties.gain} m ↗ •
319
+ ${translate('Loss:')} ${properties.loss} m ↘ •
320
+ ${translate('Altitude:')} <span data-ref="altitude">—</span> m
321
+ </p>
322
+ <object width="100%"
323
+ data="${feature._umap.getStaticPathFor('../vendors/simple-elevation-chart/elevation.svg')}"
324
+ data-elevation="${JSON.stringify(data)}"
325
+ data-ref="chart"
326
+ type="image/svg+xml">
327
+ </div>
328
+ `)
329
+ let marker
330
+ function removeMarker() {
331
+ if (marker) {
332
+ marker.remove()
333
+ }
334
+ }
335
+ chart.addEventListener('mouseout', removeMarker)
336
+ map.on('popupclose', removeMarker)
337
+ chart.addEventListener('chart:over', (event) => {
338
+ const dataset = event.detail.element.dataset
339
+ if (dataset.ele) {
340
+ altitude.textContent = dataset.ele
341
+ }
342
+ removeMarker()
343
+ const latlng = latlngs[dataset.index]
344
+ if (!latlng) return
345
+ marker = new CircleMarker(latlng, {
346
+ radius: 8,
347
+ fillColor: 'white',
348
+ fillOpacity: 1,
349
+ color: 'orange',
350
+ }).addTo(map)
351
+ })
352
+ return root
353
+ }
354
+ }
@@ -3,6 +3,7 @@ import {
3
3
  CircleMarker as BaseCircleMarker,
4
4
  DomEvent,
5
5
  DomUtil,
6
+ GeoJSON,
6
7
  LatLng,
7
8
  LatLngBounds,
8
9
  LineUtil,
@@ -45,26 +46,29 @@ const FeatureMixin = {
45
46
  this.on('contextmenu editable:vertex:contextmenu', this.onContextMenu)
46
47
  this.on('click', this.onClick)
47
48
  this.on('editable:edited', this.onCommit)
49
+ this.on('mouseover', this.onMouseOver)
50
+ },
51
+
52
+ onMouseOver: function () {
53
+ if (this._map._umap.editEnabled && !this._map._umap.editedFeature) {
54
+ this._map._umap.tooltip.open({
55
+ content: translate('Right-click to edit'),
56
+ anchor: this,
57
+ })
58
+ }
48
59
  },
49
60
 
50
61
  onClick: function (event) {
51
62
  if (this._map.measureTools?.enabled()) return
52
63
  this._popupHandlersAdded = true // Prevent leaflet from managing event
53
- if (!this._map._umap.editEnabled) {
54
- this.feature.view(event)
55
- } else if (!this.feature.isReadOnly()) {
56
- if (event.originalEvent.shiftKey) {
57
- if (event.originalEvent.ctrlKey || event.originalEvent.metaKey) {
58
- this.feature.datalayer.edit(event)
59
- } else {
60
- this.feature.toggleEditing(event)
61
- }
62
- } else if (!this._map.editTools?.drawing()) {
63
- this._map._umap.editContextmenu.open(
64
- event.originalEvent,
65
- this.feature.getInplaceEditMenu(event)
66
- )
64
+ if (event.originalEvent.shiftKey) {
65
+ if (event.originalEvent.ctrlKey || event.originalEvent.metaKey) {
66
+ this.feature.datalayer.edit(event)
67
+ } else if (!this.feature.isReadOnly()) {
68
+ this.feature.toggleEditing(event)
67
69
  }
70
+ } else if (!this._map.editTools?.drawing()) {
71
+ this.feature.view(event)
68
72
  }
69
73
  DomEvent.stop(event)
70
74
  },
@@ -91,8 +95,8 @@ const FeatureMixin = {
91
95
  onContextMenu: function (event) {
92
96
  DomEvent.stop(event)
93
97
  const items = this.feature
94
- .getContextMenuItems(event)
95
- .concat(this._map._umap.getSharedContextMenuItems(event))
98
+ .getContextMenu(event)
99
+ .concat(this._map._umap.getSharedContextMenu(event))
96
100
  this._map._umap.contextmenu.open(event.originalEvent, items)
97
101
  },
98
102
 
@@ -123,6 +127,7 @@ const PointMixin = {
123
127
  },
124
128
 
125
129
  _enableDragging: function () {
130
+ if (this._cluster) return
126
131
  // TODO: start dragging after 1 second on mouse down
127
132
  if (this._map._umap.editEnabled) {
128
133
  if (!this.editEnabled()) this.enableEdit()
@@ -238,20 +243,33 @@ export const LeafletMarker = Marker.extend({
238
243
  this._redraw()
239
244
  this._resetZIndex()
240
245
  },
246
+
247
+ _resetZIndex() {
248
+ // Override Leaflet default behaviour, which set the zIndex
249
+ // according to feature's y coordinate, and group features
250
+ // zIndex by their datalayer order
251
+ this._zIndex = this.feature.datalayer.getDOMOrder()
252
+ this._updateZIndex(0)
253
+ },
241
254
  })
242
255
 
243
256
  const PathMixin = {
244
- _onMouseOver: function () {
257
+ maxVertex: 100,
258
+ onMouseOver: function () {
245
259
  if (this._map.measureTools?.enabled()) {
246
260
  this._map._umap.tooltip.open({ content: this.getMeasure(), anchor: this })
247
- } else if (this._map._umap.editEnabled && !this._map._umap.editedFeature) {
248
- this._map._umap.tooltip.open({
249
- content: translate('Click to edit'),
250
- anchor: this,
251
- })
261
+ } else {
262
+ FeatureMixin.onMouseOver.call(this)
252
263
  }
253
264
  },
254
265
 
266
+ shouldAllowGeometryEdit: function () {
267
+ const pointsCount = this._parts.reduce((acc, part) => acc + part.length, 0)
268
+ return (
269
+ pointsCount < this.maxVertex || this._map.getZoom() === this._map.getMaxZoom()
270
+ )
271
+ },
272
+
255
273
  makeGeometryEditable: function () {
256
274
  // Feature has been removed since then?
257
275
  if (!this._map) return
@@ -260,20 +278,18 @@ const PathMixin = {
260
278
  return
261
279
  }
262
280
  this._map.once('moveend', this.makeGeometryEditable, this)
263
- const pointsCount = this._parts.reduce((acc, part) => acc + part.length, 0)
264
- if (pointsCount > 100 && this._map.getZoom() < this._map.getMaxZoom()) {
281
+ if (this.shouldAllowGeometryEdit()) {
282
+ this.enableEdit()
283
+ } else {
265
284
  this._map._umap.tooltip.open({
266
285
  content: L._('Please zoom in to edit the geometry'),
267
286
  })
268
287
  this.disableEdit()
269
- } else {
270
- this.enableEdit()
271
288
  }
272
289
  },
273
290
 
274
291
  addInteractions: function () {
275
292
  FeatureMixin.addInteractions.call(this)
276
- this.on('mouseover', this._onMouseOver)
277
293
  this.on('drag editable:drag', this._onDrag)
278
294
  this.on('popupopen', this.highlightPath)
279
295
  this.on('popupclose', this._redraw)
@@ -285,6 +301,7 @@ const PathMixin = {
285
301
  },
286
302
 
287
303
  highlightPath: function () {
304
+ this.feature.activate()
288
305
  this.parentClass.prototype.setStyle.call(this, {
289
306
  fillOpacity: Math.sqrt(this.feature.getDynamicOption('fillOpacity', 1.0)),
290
307
  opacity: 1.0,
@@ -296,6 +313,11 @@ const PathMixin = {
296
313
  if (this._tooltip) this._tooltip.setLatLng(this.getCenter())
297
314
  },
298
315
 
316
+ beforeAdd: function (map) {
317
+ this.options.renderer = this.feature.datalayer.renderer
318
+ this.parentClass.prototype.beforeAdd.call(this, map)
319
+ },
320
+
299
321
  onAdd: function (map) {
300
322
  this._container = null
301
323
  FeatureMixin.onAdd.call(this, map)
@@ -323,17 +345,11 @@ const PathMixin = {
323
345
  },
324
346
 
325
347
  _redraw: function () {
348
+ this.feature.deactivate()
326
349
  this.setStyle()
327
350
  this.resetTooltip()
328
351
  },
329
352
 
330
- onVertexRawClick: function (event) {
331
- this._map._umap.editContextmenu.open(
332
- event.originalEvent,
333
- this.feature.getInplaceEditVertexMenu(event)
334
- )
335
- },
336
-
337
353
  isolateShape: function (atLatLng) {
338
354
  if (!this.feature.isMulti()) return
339
355
  const shape = this.enableEdit().deleteShapeAt(atLatLng)
@@ -444,6 +460,67 @@ export const LeafletPolyline = Polyline.extend({
444
460
  },
445
461
  })
446
462
 
463
+ export const RouteEditor = L.Editable.PolylineEditor.extend({
464
+ options: {
465
+ skipMiddleMarkers: true,
466
+ draggable: false,
467
+ },
468
+
469
+ getLatLngs: function () {
470
+ return this.feature._route
471
+ },
472
+ })
473
+
474
+ export const LeafletRoute = LeafletPolyline.extend({
475
+ initialize: function (feature, latlngs) {
476
+ this._route = GeoJSON.coordsToLatLngs(
477
+ feature.properties._umap_options.route?.coordinates
478
+ )
479
+ FeatureMixin.initialize.call(this, feature, latlngs)
480
+ delete this.dragging
481
+ },
482
+
483
+ addInteractions: function () {
484
+ PathMixin.addInteractions.call(this)
485
+ this.on('editable:drawing:clicked', this.onDrawingClick)
486
+ this.on('editable:vertex:dragend editable:vertex:deleted', this.onDrawingMoved)
487
+ },
488
+
489
+ getEditorClass: (tools) => {
490
+ return RouteEditor
491
+ },
492
+
493
+ getClass: () => LeafletRoute,
494
+
495
+ syncRoute() {
496
+ this.feature.properties._umap_options.route.coordinates = GeoJSON.latLngsToCoords(
497
+ this._route
498
+ )
499
+ },
500
+
501
+ onDrawingMoved: function (event) {
502
+ this.syncRoute()
503
+ if (this._route.length >= 2) {
504
+ this.feature.computeRoute()
505
+ }
506
+ },
507
+
508
+ onDrawingClick: function (event) {
509
+ this._route.push(event.latlng)
510
+ this.syncRoute()
511
+ if (this._route.length >= 2) {
512
+ this.feature.computeRoute()
513
+ }
514
+ },
515
+
516
+ shouldAllowGeometryEdit: function () {
517
+ return (
518
+ this._route.length < this.maxVertex ||
519
+ this._map.getZoom() === this._map.getMaxZoom()
520
+ )
521
+ },
522
+ })
523
+
447
524
  export const LeafletPolygon = Polygon.extend({
448
525
  parentClass: Polygon,
449
526
  includes: [FeatureMixin, PathMixin],
@@ -4,6 +4,8 @@ import { MutatingForm } from './form/builder.js'
4
4
  import { translate } from './i18n.js'
5
5
  import Orderable from './orderable.js'
6
6
  import * as Utils from './utils.js'
7
+ import * as Icon from './rendering/icon.js'
8
+ import { SCHEMA } from './schema.js'
7
9
 
8
10
  const EMPTY_VALUES = ['', undefined, null]
9
11
 
@@ -12,12 +14,16 @@ class Rule {
12
14
  return this._condition
13
15
  }
14
16
 
17
+ get label() {
18
+ return this.name || this.condition
19
+ }
20
+
15
21
  set condition(value) {
16
22
  this._condition = value
17
23
  this.parse()
18
24
  }
19
25
 
20
- constructor(umap, parent, condition = '', options = {}) {
26
+ constructor(umap, parent, condition = '', name = '', properties = {}) {
21
27
  // TODO make this public properties when browser coverage is ok
22
28
  // cf https://caniuse.com/?search=public%20class%20field
23
29
  this._condition = null
@@ -32,8 +38,9 @@ class Rule {
32
38
  this.parent = parent
33
39
  this._umap = umap
34
40
  this.active = true
35
- this.options = options
41
+ this.properties = properties
36
42
  this.condition = condition
43
+ this.name = name
37
44
  }
38
45
 
39
46
  render(fields) {
@@ -95,7 +102,7 @@ class Rule {
95
102
  }
96
103
 
97
104
  getOption(option) {
98
- return this.options[option]
105
+ return this.properties[option]
99
106
  }
100
107
 
101
108
  edit() {
@@ -108,23 +115,24 @@ class Rule {
108
115
  placeholder: translate('key=value or key!=value'),
109
116
  },
110
117
  ],
111
- 'options.color',
112
- 'options.iconClass',
113
- 'options.iconUrl',
114
- 'options.iconOpacity',
115
- 'options.opacity',
116
- 'options.weight',
117
- 'options.fill',
118
- 'options.fillColor',
119
- 'options.fillOpacity',
120
- 'options.smoothFactor',
121
- 'options.dashArray',
118
+ 'name',
119
+ 'properties.color',
120
+ 'properties.iconClass',
121
+ 'properties.iconUrl',
122
+ 'properties.iconOpacity',
123
+ 'properties.opacity',
124
+ 'properties.weight',
125
+ 'properties.fill',
126
+ 'properties.fillColor',
127
+ 'properties.fillOpacity',
128
+ 'properties.smoothFactor',
129
+ 'properties.dashArray',
122
130
  ]
123
131
  const builder = new MutatingForm(this, options)
124
132
  const container = document.createElement('div')
125
133
  container.appendChild(builder.build())
126
134
  const autocomplete = new AutocompleteDatalist(builder.helpers.condition.input)
127
- const properties = this.parent.allProperties()
135
+ const properties = this.parent.fieldKeys
128
136
  autocomplete.suggestions = properties
129
137
  autocomplete.input.addEventListener('input', (event) => {
130
138
  const value = event.target.value
@@ -143,7 +151,7 @@ class Rule {
143
151
  </button>`)
144
152
  backButton.addEventListener('click', () =>
145
153
  this.parent.edit().then((panel) => {
146
- panel.container.querySelector('details#rules').open = true
154
+ panel.scrollTo('details#rules')
147
155
  })
148
156
  )
149
157
 
@@ -160,7 +168,7 @@ class Rule {
160
168
  <button class="icon icon-16 icon-eye" title="${translate('Toggle rule')}" data-ref=toggle></button>
161
169
  <button class="icon icon-16 icon-edit show-on-edit" title="${translate('Edit')}" data-ref=edit></button>
162
170
  <button class="icon icon-16 icon-delete show-on-edit" title="${translate('Delete rule')}" data-ref=remove></button>
163
- <span>${this.condition || translate('empty rule')}</span>
171
+ <span>${this.label || translate('empty rule')}</span>
164
172
  <i class="icon icon-16 icon-drag" title="${translate('Drag to reorder')}"></i>
165
173
  </li>
166
174
  `
@@ -171,7 +179,7 @@ class Rule {
171
179
  remove.addEventListener('click', () => {
172
180
  if (!confirm(translate('Are you sure you want to delete this rule?'))) return
173
181
  this._delete()
174
- this._umap.editPanel.close()
182
+ this.parent.edit().then((panel) => panel.scrollTo('details#rules'))
175
183
  })
176
184
  toggle.addEventListener('click', () => {
177
185
  this.active = !this.active
@@ -181,8 +189,11 @@ class Rule {
181
189
  }
182
190
 
183
191
  _delete() {
192
+ // TODO refactor this call to update
193
+ const oldRules = Utils.CopyJSON(this.parent.properties.rules || {})
184
194
  this.parent.rules.rules = this.parent.rules.rules.filter((rule) => rule !== this)
185
195
  this.parent.rules.commit()
196
+ this.parent.sync.update('properties.rules', this.parent.properties.rules, oldRules)
186
197
  }
187
198
 
188
199
  setter(key, value) {
@@ -191,6 +202,20 @@ class Rule {
191
202
  this.parent.rules.commit()
192
203
  this.parent.sync.update('properties.rules', this.parent.properties.rules, oldRules)
193
204
  }
205
+
206
+ renderLegend(ul) {
207
+ const [li, { colorBox }] = Utils.loadTemplateWithRefs(
208
+ `<li><span class="color-box" data-ref=colorBox></span>${this.label}</li>`
209
+ )
210
+ const bgcolor = this.properties.color || this.parent.getColor()
211
+ const symbol = this.properties.iconUrl
212
+ colorBox.style.backgroundColor = bgcolor
213
+ if (symbol && symbol !== SCHEMA.iconUrl.default) {
214
+ const icon = Icon.makeElement(symbol, colorBox)
215
+ Icon.setContrast(icon, colorBox, symbol, bgcolor)
216
+ }
217
+ ul.appendChild(li)
218
+ }
194
219
  }
195
220
 
196
221
  export default class Rules {
@@ -203,9 +228,17 @@ export default class Rules {
203
228
  load() {
204
229
  this.rules = []
205
230
  if (!this.parent.properties.rules?.length) return
206
- for (const { condition, options } of this.parent.properties.rules) {
231
+ for (const { condition, name, properties, options } of this.parent.properties
232
+ .rules) {
207
233
  if (!condition) continue
208
- this.rules.push(new Rule(this._umap, this.parent, condition, options))
234
+ const rule = new Rule(
235
+ this._umap,
236
+ this.parent,
237
+ condition,
238
+ name,
239
+ properties || options
240
+ )
241
+ this.rules.push(rule)
209
242
  }
210
243
  }
211
244
 
@@ -250,6 +283,19 @@ export default class Rules {
250
283
  container.appendChild(body)
251
284
  }
252
285
 
286
+ count() {
287
+ return this.rules.length
288
+ }
289
+
290
+ renderLegend(container, keys = new Set()) {
291
+ const ul = Utils.loadTemplate('<ul class="rules-caption"></ul>')
292
+ container.appendChild(ul)
293
+ for (const rule of this.rules) {
294
+ if (keys.size && !keys.has(rule.key)) continue
295
+ rule.renderLegend(ul)
296
+ }
297
+ }
298
+
253
299
  addRule() {
254
300
  const rule = new Rule(this._umap, this.parent)
255
301
  this.rules.push(rule)
@@ -259,17 +305,24 @@ export default class Rules {
259
305
  commit() {
260
306
  this.parent.properties.rules = this.rules.map((rule) => {
261
307
  return {
308
+ name: rule.name,
262
309
  condition: rule.condition,
263
- options: rule.options,
310
+ properties: rule.properties,
264
311
  }
265
312
  })
266
313
  }
267
314
 
268
- getOption(option, feature) {
315
+ getOption(name, feature) {
269
316
  for (const rule of this.rules) {
270
317
  if (rule.match(feature.properties)) {
271
- if (Utils.usableOption(rule.options, option)) return rule.options[option]
318
+ if (Utils.usableOption(rule.properties, name)) return rule.properties[name]
272
319
  }
273
320
  }
274
321
  }
322
+
323
+ *[Symbol.iterator]() {
324
+ for (const rule of this.rules) {
325
+ yield rule
326
+ }
327
+ }
275
328
  }
@@ -147,6 +147,9 @@ export const SCHEMA = {
147
147
  placeholder: translate('Example: key1,key2|Label 2,key3|Label 3|checkbox'),
148
148
  label: translate('Filters keys'),
149
149
  },
150
+ fields: {
151
+ type: Object,
152
+ },
150
153
  fill: {
151
154
  type: Boolean,
152
155
  impacts: ['data'],
@@ -215,6 +218,7 @@ export const SCHEMA = {
215
218
  choices: [
216
219
  ['Default', translate('Default')],
217
220
  ['Circle', translate('Circle')],
221
+ ['LargeCircle', translate('Large Circle')],
218
222
  ['Drop', translate('Drop')],
219
223
  ['Ball', translate('Ball')],
220
224
  ['Raw', translate('None')],
@@ -231,6 +235,17 @@ export const SCHEMA = {
231
235
  inheritable: true,
232
236
  default: 1,
233
237
  },
238
+ iconSize: {
239
+ type: Number,
240
+ impacts: ['data'],
241
+ min: 12,
242
+ max: 64,
243
+ step: 4,
244
+ label: translate('Icon size'),
245
+ helpText: translate('Will only affect raw and large circle icons.'),
246
+ inheritable: true,
247
+ default: 24,
248
+ },
234
249
  iconUrl: {
235
250
  type: String,
236
251
  impacts: ['data'],
@@ -435,9 +450,17 @@ export const SCHEMA = {
435
450
  ['GeoRSSLink', translate('GeoRSS (only link)')],
436
451
  ['OSM', translate('OpenStreetMap')],
437
452
  ['Wikipedia', translate('Wikipedia')],
453
+ ['Route', translate('Route')],
438
454
  ],
439
455
  default: 'Default',
440
456
  },
457
+ printControl: {
458
+ type: Boolean,
459
+ impacts: ['ui'],
460
+ nullable: true,
461
+ label: translate('Display the print control'),
462
+ default: null,
463
+ },
441
464
  rank: {
442
465
  type: Number,
443
466
  impacts: ['datalayer-rank'],
@@ -450,6 +473,10 @@ export const SCHEMA = {
450
473
  type: Object,
451
474
  impacts: ['data'],
452
475
  },
476
+ route: {
477
+ type: Object,
478
+ impacts: ['data'],
479
+ },
453
480
  scaleControl: {
454
481
  type: Boolean,
455
482
  impacts: ['ui'],
@@ -43,7 +43,7 @@ export default class Share {
43
43
  const list = document.createElement('ul')
44
44
  list.classList.add('downloads')
45
45
  this.container.appendChild(list)
46
- for (const format of Object.keys(EXPORT_FORMATS)) {
46
+ for (const format of Object.keys(EXPORT_FORMATS).concat('jpg', 'png')) {
47
47
  const button = Utils.loadTemplate(`
48
48
  <li>
49
49
  <button class="flat" type="button">
@@ -142,22 +142,29 @@ export default class Share {
142
142
 
143
143
  async format(mode) {
144
144
  const type = EXPORT_FORMATS[mode]
145
- const content = await type.formatter(this._umap)
145
+ const features = this._umap.datalayers
146
+ .visible()
147
+ .reduce((acc, dl) => acc.concat(dl.features.visible()), [])
148
+ const content = await this._umap.formatter.stringify(features, mode)
146
149
  const filename = Utils.slugify(this._umap.properties.name) + type.ext
147
150
  return { content, filetype: type.filetype, filename }
148
151
  }
149
152
 
150
153
  async download(mode) {
151
- const { content, filetype, filename } = await this.format(mode)
152
- const blob = new Blob([content], { type: filetype })
153
- window.URL = window.URL || window.webkitURL
154
- const el = document.createElement('a')
155
- el.download = filename
156
- el.href = window.URL.createObjectURL(blob)
157
- el.style.display = 'none'
158
- document.body.appendChild(el)
159
- el.click()
160
- document.body.removeChild(el)
154
+ if (!(mode in EXPORT_FORMATS)) {
155
+ this._umap.openPrinter(mode)
156
+ } else {
157
+ const { content, filetype, filename } = await this.format(mode)
158
+ const blob = new Blob([content], { type: filetype })
159
+ window.URL = window.URL || window.webkitURL
160
+ const el = document.createElement('a')
161
+ el.download = filename
162
+ el.href = window.URL.createObjectURL(blob)
163
+ el.style.display = 'none'
164
+ document.body.appendChild(el)
165
+ el.click()
166
+ document.body.removeChild(el)
167
+ }
161
168
  }
162
169
  }
163
170
 
@@ -39,7 +39,7 @@ export default class Slideshow extends WithTemplate {
39
39
  get current() {
40
40
  if (!this._current) {
41
41
  const datalayer = this.defaultDatalayer()
42
- if (datalayer) this._current = datalayer.getFeatureByIndex(0)
42
+ if (datalayer) this._current = datalayer.features.first()
43
43
  }
44
44
  return this._current
45
45
  }