umap-project 3.2.0__py3-none-any.whl → 3.3.1__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 (179) hide show
  1. umap/__init__.py +1 -1
  2. umap/locale/el/LC_MESSAGES/django.mo +0 -0
  3. umap/locale/el/LC_MESSAGES/django.po +42 -38
  4. umap/locale/en/LC_MESSAGES/django.mo +0 -0
  5. umap/locale/en/LC_MESSAGES/django.po +15 -15
  6. umap/locale/hu/LC_MESSAGES/django.mo +0 -0
  7. umap/locale/hu/LC_MESSAGES/django.po +39 -35
  8. umap/locale/nl/LC_MESSAGES/django.mo +0 -0
  9. umap/locale/nl/LC_MESSAGES/django.po +31 -27
  10. umap/settings/base.py +2 -0
  11. umap/static/umap/css/contextmenu.css +58 -2
  12. umap/static/umap/css/form.css +175 -45
  13. umap/static/umap/css/icon.css +20 -0
  14. umap/static/umap/img/16-white.svg +21 -40
  15. umap/static/umap/img/16.svg +1 -1
  16. umap/static/umap/img/24-white.svg +9 -9
  17. umap/static/umap/img/24.svg +23 -10
  18. umap/static/umap/img/source/16-white.svg +23 -41
  19. umap/static/umap/img/source/16.svg +1 -1
  20. umap/static/umap/img/source/24-white.svg +11 -11
  21. umap/static/umap/img/source/24.svg +25 -12
  22. umap/static/umap/js/modules/caption.js +8 -0
  23. umap/static/umap/js/modules/data/features.js +318 -174
  24. umap/static/umap/js/modules/data/layer.js +27 -20
  25. umap/static/umap/js/modules/form/builder.js +11 -7
  26. umap/static/umap/js/modules/form/fields.js +10 -7
  27. umap/static/umap/js/modules/formatter.js +42 -20
  28. umap/static/umap/js/modules/importer.js +6 -1
  29. umap/static/umap/js/modules/importers/opendata.js +125 -37
  30. umap/static/umap/js/modules/importers/openrouteservice.js +140 -0
  31. umap/static/umap/js/modules/managers.js +12 -4
  32. umap/static/umap/js/modules/printer.js +107 -0
  33. umap/static/umap/js/modules/rendering/controls.js +78 -2
  34. umap/static/umap/js/modules/rendering/icon.js +113 -82
  35. umap/static/umap/js/modules/rendering/layers/cluster.js +220 -64
  36. umap/static/umap/js/modules/rendering/map.js +5 -1
  37. umap/static/umap/js/modules/rendering/template.js +71 -1
  38. umap/static/umap/js/modules/rendering/ui.js +101 -34
  39. umap/static/umap/js/modules/schema.js +24 -0
  40. umap/static/umap/js/modules/share.js +19 -12
  41. umap/static/umap/js/modules/ui/bar.js +6 -1
  42. umap/static/umap/js/modules/ui/base.js +24 -9
  43. umap/static/umap/js/modules/ui/contextmenu.js +17 -7
  44. umap/static/umap/js/modules/ui/dialog.js +7 -4
  45. umap/static/umap/js/modules/umap.js +68 -62
  46. umap/static/umap/js/umap.controls.js +22 -57
  47. umap/static/umap/locale/am_ET.js +39 -4
  48. umap/static/umap/locale/am_ET.json +39 -4
  49. umap/static/umap/locale/ar.js +39 -4
  50. umap/static/umap/locale/ar.json +39 -4
  51. umap/static/umap/locale/ast.js +39 -4
  52. umap/static/umap/locale/ast.json +39 -4
  53. umap/static/umap/locale/bg.js +39 -4
  54. umap/static/umap/locale/bg.json +39 -4
  55. umap/static/umap/locale/br.js +39 -4
  56. umap/static/umap/locale/br.json +39 -4
  57. umap/static/umap/locale/ca.js +39 -4
  58. umap/static/umap/locale/ca.json +39 -4
  59. umap/static/umap/locale/cs_CZ.js +39 -4
  60. umap/static/umap/locale/cs_CZ.json +39 -4
  61. umap/static/umap/locale/da.js +47 -12
  62. umap/static/umap/locale/da.json +47 -12
  63. umap/static/umap/locale/de.js +39 -4
  64. umap/static/umap/locale/de.json +39 -4
  65. umap/static/umap/locale/el.js +81 -46
  66. umap/static/umap/locale/el.json +81 -46
  67. umap/static/umap/locale/en.js +38 -4
  68. umap/static/umap/locale/en.json +38 -4
  69. umap/static/umap/locale/en_US.json +39 -4
  70. umap/static/umap/locale/es.js +47 -12
  71. umap/static/umap/locale/es.json +47 -12
  72. umap/static/umap/locale/et.js +39 -4
  73. umap/static/umap/locale/et.json +39 -4
  74. umap/static/umap/locale/eu.js +80 -45
  75. umap/static/umap/locale/eu.json +80 -45
  76. umap/static/umap/locale/fa_IR.js +39 -4
  77. umap/static/umap/locale/fa_IR.json +39 -4
  78. umap/static/umap/locale/fi.js +39 -4
  79. umap/static/umap/locale/fi.json +39 -4
  80. umap/static/umap/locale/fr.js +39 -4
  81. umap/static/umap/locale/fr.json +39 -4
  82. umap/static/umap/locale/gl.js +39 -4
  83. umap/static/umap/locale/gl.json +39 -4
  84. umap/static/umap/locale/he.js +39 -4
  85. umap/static/umap/locale/he.json +39 -4
  86. umap/static/umap/locale/hr.js +39 -4
  87. umap/static/umap/locale/hr.json +39 -4
  88. umap/static/umap/locale/hu.js +55 -20
  89. umap/static/umap/locale/hu.json +55 -20
  90. umap/static/umap/locale/id.js +39 -4
  91. umap/static/umap/locale/id.json +39 -4
  92. umap/static/umap/locale/is.js +39 -4
  93. umap/static/umap/locale/is.json +39 -4
  94. umap/static/umap/locale/it.js +39 -4
  95. umap/static/umap/locale/it.json +39 -4
  96. umap/static/umap/locale/ja.js +39 -4
  97. umap/static/umap/locale/ja.json +39 -4
  98. umap/static/umap/locale/ko.js +39 -4
  99. umap/static/umap/locale/ko.json +39 -4
  100. umap/static/umap/locale/lt.js +39 -4
  101. umap/static/umap/locale/lt.json +39 -4
  102. umap/static/umap/locale/ms.js +52 -17
  103. umap/static/umap/locale/ms.json +52 -17
  104. umap/static/umap/locale/nl.js +58 -23
  105. umap/static/umap/locale/nl.json +58 -23
  106. umap/static/umap/locale/no.js +39 -4
  107. umap/static/umap/locale/no.json +39 -4
  108. umap/static/umap/locale/pl.js +39 -4
  109. umap/static/umap/locale/pl.json +39 -4
  110. umap/static/umap/locale/pl_PL.json +39 -4
  111. umap/static/umap/locale/pt.js +39 -4
  112. umap/static/umap/locale/pt.json +39 -4
  113. umap/static/umap/locale/pt_BR.js +39 -4
  114. umap/static/umap/locale/pt_BR.json +39 -4
  115. umap/static/umap/locale/pt_PT.js +39 -4
  116. umap/static/umap/locale/pt_PT.json +39 -4
  117. umap/static/umap/locale/ro.js +39 -4
  118. umap/static/umap/locale/ro.json +39 -4
  119. umap/static/umap/locale/ru.js +39 -4
  120. umap/static/umap/locale/ru.json +39 -4
  121. umap/static/umap/locale/sk_SK.js +39 -4
  122. umap/static/umap/locale/sk_SK.json +39 -4
  123. umap/static/umap/locale/sl.js +39 -4
  124. umap/static/umap/locale/sl.json +39 -4
  125. umap/static/umap/locale/sr.js +39 -4
  126. umap/static/umap/locale/sr.json +39 -4
  127. umap/static/umap/locale/sv.js +39 -4
  128. umap/static/umap/locale/sv.json +39 -4
  129. umap/static/umap/locale/th_TH.js +39 -4
  130. umap/static/umap/locale/th_TH.json +39 -4
  131. umap/static/umap/locale/tr.js +39 -4
  132. umap/static/umap/locale/tr.json +39 -4
  133. umap/static/umap/locale/uk_UA.js +39 -4
  134. umap/static/umap/locale/uk_UA.json +39 -4
  135. umap/static/umap/locale/vi.js +39 -4
  136. umap/static/umap/locale/vi.json +39 -4
  137. umap/static/umap/locale/vi_VN.json +39 -4
  138. umap/static/umap/locale/zh.js +39 -4
  139. umap/static/umap/locale/zh.json +39 -4
  140. umap/static/umap/locale/zh_CN.json +39 -4
  141. umap/static/umap/locale/zh_TW.Big5.json +39 -4
  142. umap/static/umap/locale/zh_TW.js +98 -63
  143. umap/static/umap/locale/zh_TW.json +98 -63
  144. umap/static/umap/map.css +90 -41
  145. umap/static/umap/vars.css +1 -0
  146. umap/static/umap/vendors/editable/Leaflet.Editable.js +3 -1
  147. umap/static/umap/vendors/openrouteservice/ors-js-client.js +521 -0
  148. umap/static/umap/vendors/openrouteservice/ors-js-client.js.map +1 -0
  149. umap/static/umap/vendors/simple-elevation-chart/elevation.js +63 -0
  150. umap/static/umap/vendors/simple-elevation-chart/elevation.svg +8 -0
  151. umap/static/umap/vendors/snapdom/snapdom.min.mjs +3 -0
  152. umap/storage/staticfiles.py +12 -0
  153. umap/templates/umap/css.html +0 -4
  154. umap/templates/umap/js.html +1 -3
  155. umap/tests/integration/test_basics.py +2 -0
  156. umap/tests/integration/test_conditional_rules.py +17 -17
  157. umap/tests/integration/test_datalayer.py +1 -1
  158. umap/tests/integration/test_draw_polygon.py +3 -5
  159. umap/tests/integration/test_draw_polyline.py +4 -6
  160. umap/tests/integration/test_draw_route.py +178 -0
  161. umap/tests/integration/test_edit_map.py +1 -1
  162. umap/tests/integration/test_edit_marker.py +7 -7
  163. umap/tests/integration/test_edit_polygon.py +2 -2
  164. umap/tests/integration/test_export_map.py +74 -10
  165. umap/tests/integration/test_map_preview.py +1 -1
  166. umap/tests/integration/test_share.py +1 -1
  167. umap/tests/integration/test_tableeditor.py +4 -4
  168. umap/tests/integration/test_websocket_sync.py +4 -4
  169. umap/utils.py +5 -1
  170. umap/views.py +2 -0
  171. {umap_project-3.2.0.dist-info → umap_project-3.3.1.dist-info}/METADATA +9 -9
  172. {umap_project-3.2.0.dist-info → umap_project-3.3.1.dist-info}/RECORD +175 -171
  173. umap/static/umap/vendors/markercluster/MarkerCluster.Default.css +0 -60
  174. umap/static/umap/vendors/markercluster/MarkerCluster.css +0 -14
  175. umap/static/umap/vendors/markercluster/leaflet.markercluster.js +0 -2
  176. umap/static/umap/vendors/markercluster/leaflet.markercluster.js.map +0 -1
  177. {umap_project-3.2.0.dist-info → umap_project-3.3.1.dist-info}/WHEEL +0 -0
  178. {umap_project-3.2.0.dist-info → umap_project-3.3.1.dist-info}/entry_points.txt +0 -0
  179. {umap_project-3.2.0.dist-info → umap_project-3.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,24 +1,100 @@
1
- import { Evented } from '../../../../vendors/leaflet/leaflet-src.esm.js'
2
- // WARNING must be loaded dynamically, or at least after leaflet.markercluster
3
- // Uses global L.MarkerCluster and L.MarkerClusterGroup, not exposed as ESM
1
+ import {
2
+ FeatureGroup,
3
+ LayerGroup,
4
+ Point,
5
+ Marker,
6
+ Rectangle,
7
+ Polyline,
8
+ DomUtil,
9
+ latLngBounds,
10
+ } from '../../../../vendors/leaflet/leaflet-src.esm.js'
4
11
  import { translate } from '../../i18n.js'
5
12
  import * as Utils from '../../utils.js'
6
13
  import { Cluster as ClusterIcon } from '../icon.js'
7
14
  import { LayerMixin } from './base.js'
8
15
 
9
- const MarkerCluster = L.MarkerCluster.extend({
10
- // Custom class so we can call computeTextColor
11
- // when element is already on the DOM.
12
-
16
+ const MarkerCluster = Marker.extend({
13
17
  _initIcon: function () {
14
- L.MarkerCluster.prototype._initIcon.call(this)
15
- const div = this._icon.querySelector('div')
18
+ Marker.prototype._initIcon.call(this)
19
+ const counter = this._icon.querySelector('span')
16
20
  // Compute text color only when icon is added to the DOM.
17
- div.style.color = this._iconObj.computeTextColor(div)
21
+ const bgColor = this.options.icon.options.color
22
+ const textColor = this.options.icon.options.textColor
23
+ counter.style.color =
24
+ textColor || DomUtil.TextColorFromBackgroundColor(counter, bgColor)
25
+ },
26
+
27
+ computeCoverage() {
28
+ if (this._layers.length < 2) return
29
+ if (!this._coverage) {
30
+ const latlngs = this._layers.map((layer) => layer._latlng)
31
+ const bounds = latLngBounds(latlngs)
32
+ this._coverage = new Rectangle(latlngs, {
33
+ color: this.options.icon.options.color,
34
+ stroke: false,
35
+ })
36
+ this._latlng = bounds.getCenter()
37
+ }
38
+ },
39
+
40
+ async zoomToCoverage() {
41
+ let resolve = undefined
42
+ const promise = new Promise((r) => {
43
+ resolve = r
44
+ })
45
+ if (this._map && this._coverage) {
46
+ this._map.once('moveend', () => resolve())
47
+ this._map.fitBounds(this._coverage.getBounds())
48
+ }
49
+ return promise
50
+ },
51
+
52
+ _spiderfyLatLng: function (center, index) {
53
+ const step = 20
54
+ const maxRadius = 150
55
+ const zoom = this._map.getZoom()
56
+ const angle = (index * step * Math.PI) / 180
57
+ const progress = index / this._layers.length
58
+ const radius = maxRadius * (1 - progress) ** 0.4
59
+ const x = radius * Math.cos(angle)
60
+ const y = radius * Math.sin(angle)
61
+ const point = this._map.project([center.lat, center.lng], zoom)
62
+ const latlng = this._map.unproject(new Point(point.x + x, point.y + y), zoom)
63
+ return latlng
64
+ },
65
+
66
+ spiderfy() {
67
+ if (!this._map) return
68
+ const crs = this._map.options.crs
69
+ if (this._spider && this._map.hasLayer(this._spider)) this.unspiderfy()
70
+ this._spider = new LayerGroup()
71
+ let i = 1
72
+ const center = this.getLatLng()
73
+ for (const layer of this._layers) {
74
+ const latlng = this._spiderfyLatLng(center, i++)
75
+ layer._originalLatLng = layer._latlng
76
+ layer.setLatLng(latlng)
77
+ this._spider.addLayer(layer)
78
+ const line = new Polyline([center, latlng], { color: 'black', weight: 1 })
79
+ this._spider.addLayer(line)
80
+ }
81
+ this._map.addLayer(this._spider)
82
+ this._icon.hidden = true
83
+ this._map.once('click zoomstart', this.unspiderfy, this)
84
+ this.once('remove', this.unspiderfy, this)
85
+ },
86
+
87
+ unspiderfy() {
88
+ if (this._icon) this._icon.hidden = false
89
+ if (this._spider) this._spider.remove()
90
+ for (const layer of this._layers) {
91
+ if (layer._originalLatLng) layer.setLatLng(layer._originalLatLng)
92
+ delete layer._originalLatLng
93
+ }
18
94
  },
19
95
  })
20
96
 
21
- export const Cluster = L.MarkerClusterGroup.extend({
97
+ export const Cluster = FeatureGroup.extend({
22
98
  statics: {
23
99
  NAME: translate('Clustered'),
24
100
  TYPE: 'Cluster',
@@ -27,65 +103,159 @@ export const Cluster = L.MarkerClusterGroup.extend({
27
103
 
28
104
  initialize: function (datalayer) {
29
105
  this.datalayer = datalayer
106
+ this._bucket = []
107
+ this._group = new LayerGroup()
30
108
  if (!Utils.isObject(this.datalayer.properties.cluster)) {
31
109
  this.datalayer.properties.cluster = {}
32
110
  }
33
- const options = {
34
- polygonOptions: {
35
- color: this.datalayer.getColor(),
36
- },
37
- iconCreateFunction: (cluster) => new ClusterIcon(datalayer, cluster),
111
+ FeatureGroup.prototype.initialize.call(this)
112
+ LayerMixin.onInit.call(this, this.datalayer._leafletMap)
113
+ },
114
+
115
+ dataChanged: function () {
116
+ this.redraw()
117
+ },
118
+
119
+ removeClusters() {
120
+ this.hideCoverage()
121
+ for (const layer of this._bucket) {
122
+ delete layer._cluster
38
123
  }
39
- if (this.datalayer.properties.cluster?.radius) {
40
- options.maxClusterRadius = this.datalayer.properties.cluster.radius
124
+ if (this._map) {
125
+ this._group.clearLayers()
126
+ }
127
+ },
128
+
129
+ addClusters() {
130
+ if (this._map) {
131
+ for (const cluster of this._clusters) {
132
+ const layer = cluster._layers.length === 1 ? cluster._layers[0] : cluster
133
+ this._group.addLayer(layer)
134
+ }
41
135
  }
42
- L.MarkerClusterGroup.prototype.initialize.call(this, options)
43
- LayerMixin.onInit.call(this, this.datalayer._leafletMap)
44
- this._markerCluster = MarkerCluster
45
- this._layers = []
46
136
  },
47
137
 
48
- onAdd: function (map) {
49
- LayerMixin.onAdd.call(this, map)
50
- return L.MarkerClusterGroup.prototype.onAdd.call(this, map)
138
+ redraw: function () {
139
+ this.removeClusters()
140
+ this.compute()
141
+ this.addClusters()
51
142
  },
52
143
 
53
- onRemove: function (map) {
54
- // In some situation, the onRemove is called before the layer is really
55
- // added to the map: basically when combining a defaultView=data + max/minZoom
56
- // and loading the map at a zoom outside of that zoom range.
57
- // FIXME: move this upstream (_unbindEvents should accept a map parameter
58
- // instead of relying on this._map)
59
- this._map = map
60
- LayerMixin.onRemove.call(this, map)
61
- return L.MarkerClusterGroup.prototype.onRemove.call(this, map)
144
+ compute() {
145
+ const radius = this.datalayer.properties.cluster?.radius || 80
146
+ this._clusters = []
147
+ const map = this.datalayer._umap._leafletMap
148
+ this._bounds = map.getBounds().pad(0.1)
149
+ const CRS = map.options.crs
150
+ for (const layer of this._bucket) {
151
+ if (layer._cluster) continue
152
+ if (!this._bounds.contains(layer._latlng)) continue
153
+ layer._xy = CRS.latLngToPoint(layer._latlng, map.getZoom())
154
+ let cluster = null
155
+ for (const candidate of this._clusters) {
156
+ if (candidate._xy.distanceTo(layer._xy) <= radius) {
157
+ cluster = candidate
158
+ break
159
+ }
160
+ }
161
+ if (!cluster) {
162
+ const icon = new ClusterIcon({
163
+ color: this.datalayer.getColor(),
164
+ textColor: this.datalayer.properties.cluster?.textColor,
165
+ getCounter: () => cluster._layers.length,
166
+ })
167
+ cluster = new MarkerCluster(layer._latlng, { icon })
168
+ cluster.addEventParent(this)
169
+ cluster._xy ??= layer._xy
170
+ cluster._layers = []
171
+ this._clusters.push(cluster)
172
+ }
173
+ cluster._layers.push(layer)
174
+ layer._cluster = cluster
175
+ }
176
+ for (const cluster of this._clusters) {
177
+ cluster.computeCoverage()
178
+ }
62
179
  },
63
180
 
64
181
  addLayer: function (layer) {
65
- this._layers.push(layer)
66
- try {
67
- return L.MarkerClusterGroup.prototype.addLayer.call(this, layer)
68
- } catch (error) {
69
- console.debug(error)
70
- // Certainly a race condition when loading a clustered layer
71
- // while zooming (this for example can happen at load, when the
72
- // initial zoom is changed by uMap).
73
- // FIXME: remove when this is merged:
74
- // https://github.com/Leaflet/Leaflet.markercluster/pull/1048/files
75
- return this
182
+ if (!layer.getLatLng) return FeatureGroup.prototype.addLayer.call(this, layer)
183
+ // Do not add yet the layer to the map
184
+ // wait for datachanged event, so we can compute breaks only once
185
+ this._bucket.push(layer)
186
+ return this
187
+ },
188
+
189
+ onAdd: function (leafletMap) {
190
+ this.on('click', this.onClick)
191
+ this.on('mouseover', this.onMouseOver)
192
+ this.on('mouseout', this.onMouseOut)
193
+ this.compute()
194
+ LayerMixin.onAdd.call(this, leafletMap)
195
+ leafletMap.on('moveend', this.onMoveEnd, this)
196
+ leafletMap.on('zoomend', this.onZoomEnd, this)
197
+ this.addClusters()
198
+ leafletMap.addLayer(this._group)
199
+ return FeatureGroup.prototype.onAdd.call(this, leafletMap)
200
+ },
201
+
202
+ onRemove: function (leafletMap) {
203
+ leafletMap.off('zoomend', this.onZoomEnd, this)
204
+ leafletMap.off('moveend', this.onMoveEnd, this)
205
+ this.off('click', this.onClick)
206
+ this.off('mouseover', this.onMouseOver)
207
+ this.off('mouseout', this.onMouseOut)
208
+ LayerMixin.onRemove.call(this, leafletMap)
209
+ this.removeClusters()
210
+ leafletMap.removeLayer(this._group)
211
+ return FeatureGroup.prototype.onRemove.call(this, leafletMap)
212
+ },
213
+
214
+ onZoomEnd: function () {
215
+ this.removeClusters()
216
+ },
217
+
218
+ onMoveEnd: function () {
219
+ this.compute()
220
+ this.addClusters()
221
+ },
222
+
223
+ showCoverage(cluster) {
224
+ if (cluster._coverage) {
225
+ this._shownCoverage = cluster._coverage
226
+ this._map.addLayer(this._shownCoverage)
76
227
  }
77
228
  },
78
229
 
79
- removeLayer: function (layer) {
80
- this._layers.splice(this._layers.indexOf(layer), 1)
81
- return L.MarkerClusterGroup.prototype.removeLayer.call(this, layer)
230
+ hideCoverage() {
231
+ if (this._shownCoverage) this._map.removeLayer(this._shownCoverage)
232
+ },
233
+
234
+ onMouseOver(event) {
235
+ event.layer?.computeCoverage?.()
236
+ this.showCoverage(event.layer)
237
+ },
238
+
239
+ onMouseOut(event) {
240
+ this.hideCoverage()
241
+ },
242
+
243
+ onClick(event) {
244
+ if (this._map.getZoom() === this._map.getMaxZoom()) {
245
+ event.layer.spiderfy?.()
246
+ } else {
247
+ event.layer.zoomToCoverage?.()
248
+ }
82
249
  },
83
250
 
84
251
  getEditableProperties: () => [
85
252
  [
86
253
  'properties.cluster.radius',
87
254
  {
88
- handler: 'BlurIntInput',
255
+ handler: 'Range',
256
+ min: 40,
257
+ max: 200,
258
+ step: 10,
89
259
  placeholder: translate('Clustering radius'),
90
260
  helpText: translate('Override clustering radius (default 80)'),
91
261
  },
@@ -101,20 +271,6 @@ export const Cluster = L.MarkerClusterGroup.extend({
101
271
  ],
102
272
 
103
273
  onEdit: function (field, builder) {
104
- if (field === 'properties.cluster.radius') {
105
- // No way to reset radius of an already instanciated MarkerClusterGroup...
106
- this.datalayer.resetLayer(true)
107
- return
108
- }
109
- if (field === 'properties.color') {
110
- this.options.polygonOptions.color = this.datalayer.getColor()
111
- }
112
- },
113
-
114
- _moveChild: (layer, from, to) => {
115
- // Extend parent method, so to remove remove/addLayer,
116
- // to let our own dragend event listener be called
117
- // cf https://github.com/umap-project/umap/issues/2749
118
- layer._latlng = to
274
+ if (field === 'properties.cluster.radius') this.redraw()
119
275
  },
120
276
  })
@@ -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',
@@ -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
 
@@ -110,6 +114,10 @@ const PointMixin = {
110
114
  FeatureMixin.addInteractions.call(this)
111
115
  this.on('dragend', (event) => {
112
116
  this.feature.edit(event)
117
+ if (this._cluster) {
118
+ delete this._originalLatLng
119
+ this.feature.datalayer.dataChanged()
120
+ }
113
121
  })
114
122
  if (!this.feature.isReadOnly()) this.on('mouseover', this._enableDragging)
115
123
  this.on('mouseout', this._onMouseOut)
@@ -249,17 +257,22 @@ export const LeafletMarker = Marker.extend({
249
257
  })
250
258
 
251
259
  const PathMixin = {
252
- _onMouseOver: function () {
260
+ maxVertex: 100,
261
+ onMouseOver: function () {
253
262
  if (this._map.measureTools?.enabled()) {
254
263
  this._map._umap.tooltip.open({ content: this.getMeasure(), anchor: this })
255
- } else if (this._map._umap.editEnabled && !this._map._umap.editedFeature) {
256
- this._map._umap.tooltip.open({
257
- content: translate('Click to edit'),
258
- anchor: this,
259
- })
264
+ } else {
265
+ FeatureMixin.onMouseOver.call(this)
260
266
  }
261
267
  },
262
268
 
269
+ shouldAllowGeometryEdit: function () {
270
+ const pointsCount = this._parts.reduce((acc, part) => acc + part.length, 0)
271
+ return (
272
+ pointsCount < this.maxVertex || this._map.getZoom() === this._map.getMaxZoom()
273
+ )
274
+ },
275
+
263
276
  makeGeometryEditable: function () {
264
277
  // Feature has been removed since then?
265
278
  if (!this._map) return
@@ -268,20 +281,18 @@ const PathMixin = {
268
281
  return
269
282
  }
270
283
  this._map.once('moveend', this.makeGeometryEditable, this)
271
- const pointsCount = this._parts.reduce((acc, part) => acc + part.length, 0)
272
- if (pointsCount > 100 && this._map.getZoom() < this._map.getMaxZoom()) {
284
+ if (this.shouldAllowGeometryEdit()) {
285
+ this.enableEdit()
286
+ } else {
273
287
  this._map._umap.tooltip.open({
274
288
  content: L._('Please zoom in to edit the geometry'),
275
289
  })
276
290
  this.disableEdit()
277
- } else {
278
- this.enableEdit()
279
291
  }
280
292
  },
281
293
 
282
294
  addInteractions: function () {
283
295
  FeatureMixin.addInteractions.call(this)
284
- this.on('mouseover', this._onMouseOver)
285
296
  this.on('drag editable:drag', this._onDrag)
286
297
  this.on('popupopen', this.highlightPath)
287
298
  this.on('popupclose', this._redraw)
@@ -293,6 +304,7 @@ const PathMixin = {
293
304
  },
294
305
 
295
306
  highlightPath: function () {
307
+ this.feature.activate()
296
308
  this.parentClass.prototype.setStyle.call(this, {
297
309
  fillOpacity: Math.sqrt(this.feature.getDynamicOption('fillOpacity', 1.0)),
298
310
  opacity: 1.0,
@@ -336,17 +348,11 @@ const PathMixin = {
336
348
  },
337
349
 
338
350
  _redraw: function () {
351
+ this.feature.deactivate()
339
352
  this.setStyle()
340
353
  this.resetTooltip()
341
354
  },
342
355
 
343
- onVertexRawClick: function (event) {
344
- this._map._umap.editContextmenu.open(
345
- event.originalEvent,
346
- this.feature.getInplaceEditVertexMenu(event)
347
- )
348
- },
349
-
350
356
  isolateShape: function (atLatLng) {
351
357
  if (!this.feature.isMulti()) return
352
358
  const shape = this.enableEdit().deleteShapeAt(atLatLng)
@@ -457,6 +463,67 @@ export const LeafletPolyline = Polyline.extend({
457
463
  },
458
464
  })
459
465
 
466
+ export const RouteEditor = L.Editable.PolylineEditor.extend({
467
+ options: {
468
+ skipMiddleMarkers: true,
469
+ draggable: false,
470
+ },
471
+
472
+ getLatLngs: function () {
473
+ return this.feature._route
474
+ },
475
+ })
476
+
477
+ export const LeafletRoute = LeafletPolyline.extend({
478
+ initialize: function (feature, latlngs) {
479
+ this._route = GeoJSON.coordsToLatLngs(
480
+ feature.properties._umap_options.route?.coordinates
481
+ )
482
+ FeatureMixin.initialize.call(this, feature, latlngs)
483
+ delete this.dragging
484
+ },
485
+
486
+ addInteractions: function () {
487
+ PathMixin.addInteractions.call(this)
488
+ this.on('editable:drawing:clicked', this.onDrawingClick)
489
+ this.on('editable:vertex:dragend editable:vertex:deleted', this.onDrawingMoved)
490
+ },
491
+
492
+ getEditorClass: (tools) => {
493
+ return RouteEditor
494
+ },
495
+
496
+ getClass: () => LeafletRoute,
497
+
498
+ syncRoute() {
499
+ this.feature.properties._umap_options.route.coordinates = GeoJSON.latLngsToCoords(
500
+ this._route
501
+ )
502
+ },
503
+
504
+ onDrawingMoved: function (event) {
505
+ this.syncRoute()
506
+ if (this._route.length >= 2) {
507
+ this.feature.computeRoute()
508
+ }
509
+ },
510
+
511
+ onDrawingClick: function (event) {
512
+ this._route.push(event.latlng)
513
+ this.syncRoute()
514
+ if (this._route.length >= 2) {
515
+ this.feature.computeRoute()
516
+ }
517
+ },
518
+
519
+ shouldAllowGeometryEdit: function () {
520
+ return (
521
+ this._route.length < this.maxVertex ||
522
+ this._map.getZoom() === this._map.getMaxZoom()
523
+ )
524
+ },
525
+ })
526
+
460
527
  export const LeafletPolygon = Polygon.extend({
461
528
  parentClass: Polygon,
462
529
  includes: [FeatureMixin, PathMixin],