umap-project 2.4.1__py3-none-any.whl → 2.5.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.
Files changed (199) 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 +145 -90
  4. umap/locale/en/LC_MESSAGES/django.po +13 -13
  5. umap/locale/eu/LC_MESSAGES/django.mo +0 -0
  6. umap/locale/eu/LC_MESSAGES/django.po +145 -89
  7. umap/locale/hu/LC_MESSAGES/django.mo +0 -0
  8. umap/locale/hu/LC_MESSAGES/django.po +100 -50
  9. umap/static/umap/base.css +5 -2
  10. umap/static/umap/content.css +2 -2
  11. umap/static/umap/css/contextmenu.css +11 -0
  12. umap/static/umap/css/dialog.css +25 -4
  13. umap/static/umap/css/importers.css +2 -0
  14. umap/static/umap/css/panel.css +6 -4
  15. umap/static/umap/css/slideshow.css +69 -0
  16. umap/static/umap/css/tableeditor.css +69 -0
  17. umap/static/umap/css/tooltip.css +3 -3
  18. umap/static/umap/img/16-white.svg +4 -0
  19. umap/static/umap/img/source/16-white.svg +5 -1
  20. umap/static/umap/js/components/alerts/alert.css +11 -11
  21. umap/static/umap/js/components/alerts/alert.js +1 -1
  22. umap/static/umap/js/modules/autocomplete.js +27 -5
  23. umap/static/umap/js/modules/browser.js +20 -14
  24. umap/static/umap/js/modules/caption.js +4 -4
  25. umap/static/umap/js/modules/dompurify.js +2 -3
  26. umap/static/umap/js/modules/facets.js +53 -17
  27. umap/static/umap/js/modules/formatter.js +153 -0
  28. umap/static/umap/js/modules/global.js +25 -16
  29. umap/static/umap/js/modules/help.js +26 -26
  30. umap/static/umap/js/modules/importer.js +10 -10
  31. umap/static/umap/js/modules/importers/communesfr.js +3 -1
  32. umap/static/umap/js/modules/importers/datasets.js +8 -6
  33. umap/static/umap/js/modules/importers/geodatamine.js +14 -14
  34. umap/static/umap/js/modules/importers/overpass.js +19 -15
  35. umap/static/umap/js/modules/orderable.js +2 -2
  36. umap/static/umap/js/modules/request.js +1 -1
  37. umap/static/umap/js/modules/rules.js +26 -11
  38. umap/static/umap/js/modules/schema.js +16 -12
  39. umap/static/umap/js/{umap.share.js → modules/share.js} +58 -103
  40. umap/static/umap/js/modules/slideshow.js +141 -0
  41. umap/static/umap/js/modules/sync/engine.js +3 -3
  42. umap/static/umap/js/modules/sync/updaters.js +10 -11
  43. umap/static/umap/js/modules/sync/websocket.js +1 -1
  44. umap/static/umap/js/modules/tableeditor.js +329 -0
  45. umap/static/umap/js/modules/ui/base.js +93 -0
  46. umap/static/umap/js/modules/ui/contextmenu.js +50 -0
  47. umap/static/umap/js/modules/ui/dialog.js +169 -31
  48. umap/static/umap/js/modules/ui/panel.js +7 -5
  49. umap/static/umap/js/modules/ui/tooltip.js +7 -77
  50. umap/static/umap/js/modules/urls.js +1 -2
  51. umap/static/umap/js/modules/utils.js +36 -16
  52. umap/static/umap/js/umap.controls.js +27 -29
  53. umap/static/umap/js/umap.core.js +19 -15
  54. umap/static/umap/js/umap.datalayer.permissions.js +15 -18
  55. umap/static/umap/js/umap.features.js +113 -131
  56. umap/static/umap/js/umap.forms.js +203 -228
  57. umap/static/umap/js/umap.icon.js +17 -22
  58. umap/static/umap/js/umap.js +117 -107
  59. umap/static/umap/js/umap.layer.js +374 -324
  60. umap/static/umap/js/umap.permissions.js +7 -10
  61. umap/static/umap/js/umap.popup.js +20 -20
  62. umap/static/umap/locale/am_ET.js +22 -5
  63. umap/static/umap/locale/am_ET.json +22 -5
  64. umap/static/umap/locale/ar.js +22 -5
  65. umap/static/umap/locale/ar.json +22 -5
  66. umap/static/umap/locale/ast.js +22 -5
  67. umap/static/umap/locale/ast.json +22 -5
  68. umap/static/umap/locale/bg.js +22 -5
  69. umap/static/umap/locale/bg.json +22 -5
  70. umap/static/umap/locale/br.js +22 -5
  71. umap/static/umap/locale/br.json +22 -5
  72. umap/static/umap/locale/ca.js +56 -39
  73. umap/static/umap/locale/ca.json +56 -39
  74. umap/static/umap/locale/cs_CZ.js +22 -5
  75. umap/static/umap/locale/cs_CZ.json +22 -5
  76. umap/static/umap/locale/da.js +22 -5
  77. umap/static/umap/locale/da.json +22 -5
  78. umap/static/umap/locale/de.js +22 -5
  79. umap/static/umap/locale/de.json +22 -5
  80. umap/static/umap/locale/el.js +27 -10
  81. umap/static/umap/locale/el.json +27 -10
  82. umap/static/umap/locale/en.js +22 -6
  83. umap/static/umap/locale/en.json +22 -6
  84. umap/static/umap/locale/en_US.json +22 -5
  85. umap/static/umap/locale/es.js +22 -6
  86. umap/static/umap/locale/es.json +22 -6
  87. umap/static/umap/locale/et.js +22 -5
  88. umap/static/umap/locale/et.json +22 -5
  89. umap/static/umap/locale/eu.js +167 -150
  90. umap/static/umap/locale/eu.json +167 -150
  91. umap/static/umap/locale/fa_IR.js +22 -5
  92. umap/static/umap/locale/fa_IR.json +22 -5
  93. umap/static/umap/locale/fi.js +22 -5
  94. umap/static/umap/locale/fi.json +22 -5
  95. umap/static/umap/locale/fr.js +22 -6
  96. umap/static/umap/locale/fr.json +22 -6
  97. umap/static/umap/locale/gl.js +22 -5
  98. umap/static/umap/locale/gl.json +22 -5
  99. umap/static/umap/locale/he.js +22 -5
  100. umap/static/umap/locale/he.json +22 -5
  101. umap/static/umap/locale/hr.js +22 -5
  102. umap/static/umap/locale/hr.json +22 -5
  103. umap/static/umap/locale/hu.js +89 -72
  104. umap/static/umap/locale/hu.json +89 -72
  105. umap/static/umap/locale/id.js +22 -5
  106. umap/static/umap/locale/id.json +22 -5
  107. umap/static/umap/locale/is.js +22 -5
  108. umap/static/umap/locale/is.json +22 -5
  109. umap/static/umap/locale/it.js +22 -5
  110. umap/static/umap/locale/it.json +22 -5
  111. umap/static/umap/locale/ja.js +22 -5
  112. umap/static/umap/locale/ja.json +22 -5
  113. umap/static/umap/locale/ko.js +22 -5
  114. umap/static/umap/locale/ko.json +22 -5
  115. umap/static/umap/locale/lt.js +22 -5
  116. umap/static/umap/locale/lt.json +22 -5
  117. umap/static/umap/locale/ms.js +22 -5
  118. umap/static/umap/locale/ms.json +22 -5
  119. umap/static/umap/locale/nl.js +22 -5
  120. umap/static/umap/locale/nl.json +22 -5
  121. umap/static/umap/locale/no.js +22 -5
  122. umap/static/umap/locale/no.json +22 -5
  123. umap/static/umap/locale/pl.js +22 -5
  124. umap/static/umap/locale/pl.json +22 -5
  125. umap/static/umap/locale/pl_PL.json +22 -5
  126. umap/static/umap/locale/pt.js +22 -6
  127. umap/static/umap/locale/pt.json +22 -6
  128. umap/static/umap/locale/pt_BR.js +22 -5
  129. umap/static/umap/locale/pt_BR.json +22 -5
  130. umap/static/umap/locale/pt_PT.js +22 -5
  131. umap/static/umap/locale/pt_PT.json +22 -5
  132. umap/static/umap/locale/ro.js +22 -5
  133. umap/static/umap/locale/ro.json +22 -5
  134. umap/static/umap/locale/ru.js +22 -5
  135. umap/static/umap/locale/ru.json +22 -5
  136. umap/static/umap/locale/sk_SK.js +22 -5
  137. umap/static/umap/locale/sk_SK.json +22 -5
  138. umap/static/umap/locale/sl.js +22 -5
  139. umap/static/umap/locale/sl.json +22 -5
  140. umap/static/umap/locale/sr.js +22 -5
  141. umap/static/umap/locale/sr.json +22 -5
  142. umap/static/umap/locale/sv.js +22 -5
  143. umap/static/umap/locale/sv.json +22 -5
  144. umap/static/umap/locale/th_TH.js +22 -5
  145. umap/static/umap/locale/th_TH.json +22 -5
  146. umap/static/umap/locale/tr.js +22 -5
  147. umap/static/umap/locale/tr.json +22 -5
  148. umap/static/umap/locale/uk_UA.js +22 -5
  149. umap/static/umap/locale/uk_UA.json +22 -5
  150. umap/static/umap/locale/vi.js +22 -5
  151. umap/static/umap/locale/vi.json +22 -5
  152. umap/static/umap/locale/vi_VN.json +22 -5
  153. umap/static/umap/locale/zh.js +22 -5
  154. umap/static/umap/locale/zh.json +22 -5
  155. umap/static/umap/locale/zh_CN.json +22 -5
  156. umap/static/umap/locale/zh_TW.Big5.json +22 -5
  157. umap/static/umap/locale/zh_TW.js +22 -5
  158. umap/static/umap/locale/zh_TW.json +22 -5
  159. umap/static/umap/map.css +9 -153
  160. umap/static/umap/vars.css +15 -0
  161. umap/static/umap/vendors/dompurify/purify.es.js +5 -59
  162. umap/static/umap/vendors/dompurify/purify.es.mjs.map +1 -1
  163. umap/static/umap/vendors/formbuilder/Leaflet.FormBuilder.js +410 -428
  164. umap/static/umap/vendors/geojson-to-gpx/index.js +155 -0
  165. umap/static/umap/vendors/osmtogeojson/osmtogeojson.js +1 -2
  166. umap/static/umap/vendors/togeojson/togeojson.es.js +1109 -0
  167. umap/static/umap/vendors/togeojson/{togeojson.umd.js.map → togeojson.es.mjs.map} +1 -1
  168. umap/static/umap/vendors/tokml/tokml.es.js +895 -0
  169. umap/static/umap/vendors/tokml/tokml.es.mjs.map +1 -0
  170. umap/storage.py +6 -2
  171. umap/templates/umap/components/alerts/alert.html +3 -3
  172. umap/templates/umap/css.html +3 -0
  173. umap/templates/umap/js.html +0 -6
  174. umap/tests/fixtures/categorized_highway.geojson +1 -0
  175. umap/tests/fixtures/test_import_osm_relation.json +130 -0
  176. umap/tests/integration/conftest.py +8 -1
  177. umap/tests/integration/test_browser.py +3 -2
  178. umap/tests/integration/test_categorized_layer.py +141 -0
  179. umap/tests/integration/test_conditional_rules.py +21 -0
  180. umap/tests/integration/test_datalayer.py +9 -4
  181. umap/tests/integration/test_edit_datalayer.py +1 -0
  182. umap/tests/integration/test_edit_polygon.py +1 -1
  183. umap/tests/integration/test_export_map.py +2 -3
  184. umap/tests/integration/test_import.py +22 -0
  185. umap/tests/integration/test_map_preview.py +36 -2
  186. umap/tests/integration/test_tableeditor.py +158 -4
  187. umap/tests/integration/test_websocket_sync.py +2 -2
  188. umap/tests/test_views.py +2 -2
  189. umap/views.py +3 -2
  190. {umap_project-2.4.1.dist-info → umap_project-2.5.0.dist-info}/METADATA +8 -8
  191. {umap_project-2.4.1.dist-info → umap_project-2.5.0.dist-info}/RECORD +194 -184
  192. umap/static/umap/js/umap.slideshow.js +0 -165
  193. umap/static/umap/js/umap.tableeditor.js +0 -118
  194. umap/static/umap/vendors/togeojson/togeojson.umd.js +0 -2
  195. umap/static/umap/vendors/togpx/togpx.js +0 -547
  196. umap/static/umap/vendors/tokml/tokml.js +0 -343
  197. {umap_project-2.4.1.dist-info → umap_project-2.5.0.dist-info}/WHEEL +0 -0
  198. {umap_project-2.4.1.dist-info → umap_project-2.5.0.dist-info}/entry_points.txt +0 -0
  199. {umap_project-2.4.1.dist-info → umap_project-2.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -15,11 +15,9 @@ U.Layer = {
15
15
  return this._layers
16
16
  },
17
17
 
18
- getEditableOptions: function () {
19
- return []
20
- },
18
+ getEditableOptions: () => [],
21
19
 
22
- onEdit: function () {},
20
+ onEdit: () => {},
23
21
 
24
22
  hasDataVisible: function () {
25
23
  return !!Object.keys(this._layers).length
@@ -67,11 +65,9 @@ U.Layer.Cluster = L.MarkerClusterGroup.extend({
67
65
  polygonOptions: {
68
66
  color: this.datalayer.getColor(),
69
67
  },
70
- iconCreateFunction: function (cluster) {
71
- return new U.Icon.Cluster(datalayer, cluster)
72
- },
68
+ iconCreateFunction: (cluster) => new U.Icon.Cluster(datalayer, cluster),
73
69
  }
74
- if (this.datalayer.options.cluster && this.datalayer.options.cluster.radius) {
70
+ if (this.datalayer.options.cluster?.radius) {
75
71
  options.maxClusterRadius = this.datalayer.options.cluster.radius
76
72
  }
77
73
  L.MarkerClusterGroup.prototype.initialize.call(this, options)
@@ -99,26 +95,24 @@ U.Layer.Cluster = L.MarkerClusterGroup.extend({
99
95
  return L.MarkerClusterGroup.prototype.removeLayer.call(this, layer)
100
96
  },
101
97
 
102
- getEditableOptions: function () {
103
- return [
104
- [
105
- 'options.cluster.radius',
106
- {
107
- handler: 'BlurIntInput',
108
- placeholder: L._('Clustering radius'),
109
- helpText: L._('Override clustering radius (default 80)'),
110
- },
111
- ],
112
- [
113
- 'options.cluster.textColor',
114
- {
115
- handler: 'TextColorPicker',
116
- placeholder: L._('Auto'),
117
- helpText: L._('Text color for the cluster label'),
118
- },
119
- ],
120
- ]
121
- },
98
+ getEditableOptions: () => [
99
+ [
100
+ 'options.cluster.radius',
101
+ {
102
+ handler: 'BlurIntInput',
103
+ placeholder: L._('Clustering radius'),
104
+ helpText: L._('Override clustering radius (default 80)'),
105
+ },
106
+ ],
107
+ [
108
+ 'options.cluster.textColor',
109
+ {
110
+ handler: 'TextColorPicker',
111
+ placeholder: L._('Auto'),
112
+ helpText: L._('Text color for the cluster label'),
113
+ },
114
+ ],
115
+ ],
122
116
 
123
117
  onEdit: function (field, builder) {
124
118
  if (field === 'options.cluster.radius') {
@@ -132,7 +126,77 @@ U.Layer.Cluster = L.MarkerClusterGroup.extend({
132
126
  },
133
127
  })
134
128
 
135
- U.Layer.Choropleth = L.FeatureGroup.extend({
129
+ // Layer where each feature color is relative to the others,
130
+ // so we need all features before behing able to set one
131
+ // feature layer
132
+ U.RelativeColorLayer = L.FeatureGroup.extend({
133
+ initialize: function (datalayer) {
134
+ this.datalayer = datalayer
135
+ this.colorSchemes = Object.keys(colorbrewer)
136
+ .filter((k) => k !== 'schemeGroups')
137
+ .sort()
138
+ const key = this.getType().toLowerCase()
139
+ if (!U.Utils.isObject(this.datalayer.options[key])) {
140
+ this.datalayer.options[key] = {}
141
+ }
142
+ L.FeatureGroup.prototype.initialize.call(this, [], this.datalayer.options[key])
143
+ this.datalayer.onceDataLoaded(() => {
144
+ this.redraw()
145
+ this.datalayer.on('datachanged', this.redraw, this)
146
+ })
147
+ },
148
+
149
+ redraw: function () {
150
+ this.compute()
151
+ if (this._map) this.eachLayer(this._map.addLayer, this._map)
152
+ },
153
+
154
+ getOption: function (option, feature) {
155
+ if (feature && option === feature.staticOptions.mainColor) {
156
+ return this.getColor(feature)
157
+ }
158
+ },
159
+
160
+ addLayer: function (layer) {
161
+ // Do not add yet the layer to the map
162
+ // wait for datachanged event, so we can compute breaks only once
163
+ const id = this.getLayerId(layer)
164
+ this._layers[id] = layer
165
+ return this
166
+ },
167
+
168
+ onAdd: function (map) {
169
+ this.compute()
170
+ L.FeatureGroup.prototype.onAdd.call(this, map)
171
+ },
172
+
173
+ getValues: function () {
174
+ const values = []
175
+ this.datalayer.eachLayer((layer) => {
176
+ const value = this._getValue(layer)
177
+ if (value !== undefined) values.push(value)
178
+ })
179
+ return values
180
+ },
181
+
182
+ renderLegend: function (container) {
183
+ const parent = L.DomUtil.create('ul', '', container)
184
+ const items = this.getLegendItems()
185
+ for (const [color, label] of items) {
186
+ const li = L.DomUtil.create('li', '', parent)
187
+ const colorEl = L.DomUtil.create('span', 'datalayer-color', li)
188
+ colorEl.style.backgroundColor = color
189
+ const labelEl = L.DomUtil.create('span', '', li)
190
+ labelEl.textContent = label
191
+ }
192
+ },
193
+
194
+ getColorSchemes: function (classes) {
195
+ return this.colorSchemes.filter((scheme) => Boolean(colorbrewer[scheme][classes]))
196
+ },
197
+ })
198
+
199
+ U.Layer.Choropleth = U.RelativeColorLayer.extend({
136
200
  statics: {
137
201
  NAME: L._('Choropleth'),
138
202
  TYPE: 'Choropleth',
@@ -153,42 +217,13 @@ U.Layer.Choropleth = L.FeatureGroup.extend({
153
217
  manual: L._('Manual'),
154
218
  },
155
219
 
156
- initialize: function (datalayer) {
157
- this.datalayer = datalayer
158
- if (!U.Utils.isObject(this.datalayer.options.choropleth)) {
159
- this.datalayer.options.choropleth = {}
160
- }
161
- L.FeatureGroup.prototype.initialize.call(
162
- this,
163
- [],
164
- this.datalayer.options.choropleth
165
- )
166
- this.datalayer.onceDataLoaded(() => {
167
- this.redraw()
168
- this.datalayer.on('datachanged', this.redraw, this)
169
- })
170
- },
171
-
172
- redraw: function () {
173
- this.computeBreaks()
174
- if (this._map) this.eachLayer(this._map.addLayer, this._map)
175
- },
176
-
177
220
  _getValue: function (feature) {
178
221
  const key = this.datalayer.options.choropleth.property || 'value'
179
- return +feature.properties[key] // TODO: should we catch values non castable to int ?
222
+ const value = +feature.properties[key]
223
+ if (!Number.isNaN(value)) return value
180
224
  },
181
225
 
182
- getValues: function () {
183
- const values = []
184
- this.datalayer.eachLayer((layer) => {
185
- let value = this._getValue(layer)
186
- if (!isNaN(value)) values.push(value)
187
- })
188
- return values
189
- },
190
-
191
- computeBreaks: function () {
226
+ compute: function () {
192
227
  const values = this.getValues()
193
228
 
194
229
  if (!values.length) {
@@ -196,9 +231,9 @@ U.Layer.Choropleth = L.FeatureGroup.extend({
196
231
  this.options.colors = []
197
232
  return
198
233
  }
199
- let mode = this.datalayer.options.choropleth.mode,
200
- classes = +this.datalayer.options.choropleth.classes || 5,
201
- breaks
234
+ const mode = this.datalayer.options.choropleth.mode
235
+ let classes = +this.datalayer.options.choropleth.classes || 5
236
+ let breaks
202
237
  classes = Math.min(classes, values.length)
203
238
  if (mode === 'manual') {
204
239
  const manualBreaks = this.datalayer.options.choropleth.breaks
@@ -206,7 +241,7 @@ U.Layer.Choropleth = L.FeatureGroup.extend({
206
241
  breaks = manualBreaks
207
242
  .split(',')
208
243
  .map((b) => +b)
209
- .filter((b) => !isNaN(b))
244
+ .filter((b) => !Number.isNaN(b))
210
245
  }
211
246
  } else if (mode === 'equidistant') {
212
247
  breaks = ss.equalIntervalBreaks(values, classes)
@@ -230,7 +265,7 @@ U.Layer.Choropleth = L.FeatureGroup.extend({
230
265
  },
231
266
 
232
267
  getColor: function (feature) {
233
- if (!feature) return // FIXME shold not happen
268
+ if (!feature) return // FIXME should not happen
234
269
  const featureValue = this._getValue(feature)
235
270
  // Find the bucket/step/limit that this value is less than and give it that color
236
271
  for (let i = 1; i < this.options.breaks.length; i++) {
@@ -240,25 +275,6 @@ U.Layer.Choropleth = L.FeatureGroup.extend({
240
275
  }
241
276
  },
242
277
 
243
- getOption: function (option, feature) {
244
- if (feature && option === feature.staticOptions.mainColor) {
245
- return this.getColor(feature)
246
- }
247
- },
248
-
249
- addLayer: function (layer) {
250
- // Do not add yet the layer to the map
251
- // wait for datachanged event, so we want compute breaks once
252
- var id = this.getLayerId(layer)
253
- this._layers[id] = layer
254
- return this
255
- },
256
-
257
- onAdd: function (map) {
258
- this.computeBreaks()
259
- L.FeatureGroup.prototype.onAdd.call(this, map)
260
- },
261
-
262
278
  onEdit: function (field, builder) {
263
279
  // Only compute the breaks if we're dealing with choropleth
264
280
  if (!field.startsWith('options.choropleth')) return
@@ -267,7 +283,7 @@ U.Layer.Choropleth = L.FeatureGroup.extend({
267
283
  this.datalayer.options.choropleth.mode = 'manual'
268
284
  if (builder) builder.helpers['options.choropleth.mode'].fetch()
269
285
  }
270
- this.computeBreaks()
286
+ this.compute()
271
287
  // If user changes the mode or the number of classes,
272
288
  // then update the breaks input value
273
289
  if (field === 'options.choropleth.mode' || field === 'options.choropleth.classes') {
@@ -276,10 +292,6 @@ U.Layer.Choropleth = L.FeatureGroup.extend({
276
292
  },
277
293
 
278
294
  getEditableOptions: function () {
279
- const brewerSchemes = Object.keys(colorbrewer)
280
- .filter((k) => k !== 'schemeGroups')
281
- .sort()
282
-
283
295
  return [
284
296
  [
285
297
  'options.choropleth.property',
@@ -294,7 +306,7 @@ U.Layer.Choropleth = L.FeatureGroup.extend({
294
306
  {
295
307
  handler: 'Select',
296
308
  label: L._('Choropleth color palette'),
297
- selectOptions: brewerSchemes,
309
+ selectOptions: this.colorSchemes,
298
310
  },
299
311
  ],
300
312
  [
@@ -330,18 +342,139 @@ U.Layer.Choropleth = L.FeatureGroup.extend({
330
342
  ]
331
343
  },
332
344
 
333
- renderLegend: function (container) {
334
- const parent = L.DomUtil.create('ul', '', container)
335
- let li, color, label
336
-
337
- this.options.breaks.slice(0, -1).forEach((limit, index) => {
338
- li = L.DomUtil.create('li', '', parent)
339
- color = L.DomUtil.create('span', 'datalayer-color', li)
340
- color.style.backgroundColor = this.options.colors[index]
341
- label = L.DomUtil.create('span', '', li)
342
- label.textContent = `${+this.options.breaks[index].toFixed(
343
- 1
344
- )} - ${+this.options.breaks[index + 1].toFixed(1)}`
345
+ getLegendItems: function () {
346
+ return this.options.breaks.slice(0, -1).map((el, index) => {
347
+ const from = +this.options.breaks[index].toFixed(1)
348
+ const to = +this.options.breaks[index + 1].toFixed(1)
349
+ return [this.options.colors[index], `${from} - ${to}`]
350
+ })
351
+ },
352
+ })
353
+
354
+ U.Layer.Categorized = U.RelativeColorLayer.extend({
355
+ statics: {
356
+ NAME: L._('Categorized'),
357
+ TYPE: 'Categorized',
358
+ },
359
+ includes: [U.Layer],
360
+ MODES: {
361
+ manual: L._('Manual'),
362
+ alpha: L._('Alphabetical'),
363
+ },
364
+ defaults: {
365
+ color: 'white',
366
+ fillColor: 'red',
367
+ fillOpacity: 0.7,
368
+ weight: 2,
369
+ },
370
+
371
+ _getValue: function (feature) {
372
+ const key =
373
+ this.datalayer.options.categorized.property || this.datalayer._propertiesIndex[0]
374
+ return feature.properties[key]
375
+ },
376
+
377
+ getColor: function (feature) {
378
+ if (!feature) return // FIXME should not happen
379
+ const featureValue = this._getValue(feature)
380
+ for (let i = 0; i < this.options.categories.length; i++) {
381
+ if (featureValue === this.options.categories[i]) {
382
+ return this.options.colors[i]
383
+ }
384
+ }
385
+ },
386
+
387
+ compute: function () {
388
+ const values = this.getValues()
389
+
390
+ if (!values.length) {
391
+ this.options.categories = []
392
+ this.options.colors = []
393
+ return
394
+ }
395
+ const mode = this.datalayer.options.categorized.mode
396
+ let categories = []
397
+ if (mode === 'manual') {
398
+ const manualCategories = this.datalayer.options.categorized.categories
399
+ if (manualCategories) {
400
+ categories = manualCategories.split(',')
401
+ }
402
+ } else {
403
+ categories = values
404
+ .filter((val, idx, arr) => arr.indexOf(val) === idx)
405
+ .sort(U.Utils.naturalSort)
406
+ }
407
+ this.options.categories = categories
408
+ this.datalayer.options.categorized.categories = this.options.categories.join(',')
409
+ const fillColor = this.datalayer.getOption('fillColor') || this.defaults.fillColor
410
+ const colorScheme = this.datalayer.options.categorized.brewer
411
+ this._classes = this.options.categories.length
412
+ if (colorbrewer[colorScheme]?.[this._classes]) {
413
+ this.options.colors = colorbrewer[colorScheme][this._classes]
414
+ } else {
415
+ this.options.colors = colorbrewer?.Accent[this._classes]
416
+ ? colorbrewer?.Accent[this._classes]
417
+ : U.COLORS
418
+ }
419
+ },
420
+
421
+ getEditableOptions: function () {
422
+ return [
423
+ [
424
+ 'options.categorized.property',
425
+ {
426
+ handler: 'Select',
427
+ selectOptions: this.datalayer._propertiesIndex,
428
+ label: L._('Category property'),
429
+ },
430
+ ],
431
+ [
432
+ 'options.categorized.brewer',
433
+ {
434
+ handler: 'Select',
435
+ label: L._('Color palette'),
436
+ selectOptions: this.getColorSchemes(this._classes),
437
+ },
438
+ ],
439
+ [
440
+ 'options.categorized.categories',
441
+ {
442
+ handler: 'BlurInput',
443
+ label: L._('Categories'),
444
+ helpText: L._('Comma separated list of categories.'),
445
+ },
446
+ ],
447
+ [
448
+ 'options.categorized.mode',
449
+ {
450
+ handler: 'MultiChoice',
451
+ default: 'alpha',
452
+ choices: Object.entries(this.MODES),
453
+ label: L._('Categories mode'),
454
+ },
455
+ ],
456
+ ]
457
+ },
458
+
459
+ onEdit: function (field, builder) {
460
+ // Only compute the categories if we're dealing with categorized
461
+ if (!field.startsWith('options.categorized')) return
462
+ // If user touches the categories, then force manual mode
463
+ if (field === 'options.categorized.categories') {
464
+ this.datalayer.options.categorized.mode = 'manual'
465
+ if (builder) builder.helpers['options.categorized.mode'].fetch()
466
+ }
467
+ this.compute()
468
+ // If user changes the mode
469
+ // then update the categories input value
470
+ if (field === 'options.categorized.mode') {
471
+ if (builder) builder.helpers['options.categorized.categories'].fetch()
472
+ }
473
+ },
474
+
475
+ getLegendItems: function () {
476
+ return this.options.categories.map((limit, index) => {
477
+ return [this.options.colors[index], this.options.categories[index]]
345
478
  })
346
479
  },
347
480
  })
@@ -364,13 +497,10 @@ U.Layer.Heat = L.HeatLayer.extend({
364
497
 
365
498
  addLayer: function (layer) {
366
499
  if (layer instanceof L.Marker) {
367
- let latlng = layer.getLatLng(),
368
- alt
369
- if (
370
- this.datalayer.options.heat &&
371
- this.datalayer.options.heat.intensityProperty
372
- ) {
373
- alt = parseFloat(
500
+ let latlng = layer.getLatLng()
501
+ let alt
502
+ if (this.datalayer.options.heat?.intensityProperty) {
503
+ alt = Number.parseFloat(
374
504
  layer.properties[this.datalayer.options.heat.intensityProperty || 0]
375
505
  )
376
506
  latlng = new L.LatLng(latlng.lat, latlng.lng, alt)
@@ -383,37 +513,33 @@ U.Layer.Heat = L.HeatLayer.extend({
383
513
  this.setLatLngs([])
384
514
  },
385
515
 
386
- getFeatures: function () {
387
- return {}
388
- },
516
+ getFeatures: () => ({}),
389
517
 
390
518
  getBounds: function () {
391
519
  return L.latLngBounds(this._latlngs)
392
520
  },
393
521
 
394
- getEditableOptions: function () {
395
- return [
396
- [
397
- 'options.heat.radius',
398
- {
399
- handler: 'Range',
400
- min: 10,
401
- max: 100,
402
- step: 5,
403
- label: L._('Heatmap radius'),
404
- helpText: L._('Override heatmap radius (default 25)'),
405
- },
406
- ],
407
- [
408
- 'options.heat.intensityProperty',
409
- {
410
- handler: 'BlurInput',
411
- placeholder: L._('Heatmap intensity property'),
412
- helpText: L._('Optional intensity property for heatmap'),
413
- },
414
- ],
415
- ]
416
- },
522
+ getEditableOptions: () => [
523
+ [
524
+ 'options.heat.radius',
525
+ {
526
+ handler: 'Range',
527
+ min: 10,
528
+ max: 100,
529
+ step: 5,
530
+ label: L._('Heatmap radius'),
531
+ helpText: L._('Override heatmap radius (default 25)'),
532
+ },
533
+ ],
534
+ [
535
+ 'options.heat.intensityProperty',
536
+ {
537
+ handler: 'BlurInput',
538
+ placeholder: L._('Heatmap intensity property'),
539
+ helpText: L._('Optional intensity property for heatmap'),
540
+ },
541
+ ],
542
+ ],
417
543
 
418
544
  onEdit: function (field, builder) {
419
545
  if (field === 'options.heat.intensityProperty') {
@@ -440,23 +566,23 @@ U.Layer.Heat = L.HeatLayer.extend({
440
566
  if (!this._map) {
441
567
  return
442
568
  }
443
- var data = [],
444
- r = this._heat._r,
445
- size = this._map.getSize(),
446
- bounds = new L.Bounds(L.point([-r, -r]), size.add([r, r])),
447
- cellSize = r / 2,
448
- grid = [],
449
- panePos = this._map._getMapPanePos(),
450
- offsetX = panePos.x % cellSize,
451
- offsetY = panePos.y % cellSize,
452
- i,
453
- len,
454
- p,
455
- cell,
456
- x,
457
- y,
458
- j,
459
- len2
569
+ const data = []
570
+ const r = this._heat._r
571
+ const size = this._map.getSize()
572
+ const bounds = new L.Bounds(L.point([-r, -r]), size.add([r, r]))
573
+ const cellSize = r / 2
574
+ const grid = []
575
+ const panePos = this._map._getMapPanePos()
576
+ const offsetX = panePos.x % cellSize
577
+ const offsetY = panePos.y % cellSize
578
+ let i
579
+ let len
580
+ let p
581
+ let cell
582
+ let x
583
+ let y
584
+ let j
585
+ let len2
460
586
 
461
587
  this._max = 1
462
588
 
@@ -465,7 +591,7 @@ U.Layer.Heat = L.HeatLayer.extend({
465
591
  x = Math.floor((p.x - offsetX) / cellSize) + 2
466
592
  y = Math.floor((p.y - offsetY) / cellSize) + 2
467
593
 
468
- var alt =
594
+ const alt =
469
595
  this._latlngs[i].alt !== undefined
470
596
  ? this._latlngs[i].alt
471
597
  : this._latlngs[i][2] !== undefined
@@ -538,23 +664,20 @@ U.DataLayer = L.Evented.extend({
538
664
 
539
665
  let isDirty = false
540
666
  let isDeleted = false
541
- const self = this
542
667
  try {
543
668
  Object.defineProperty(this, 'isDirty', {
544
- get: function () {
545
- return isDirty
546
- },
547
- set: function (status) {
548
- if (!isDirty && status) self.fire('dirty')
669
+ get: () => isDirty,
670
+ set: (status) => {
671
+ if (!isDirty && status) this.fire('dirty')
549
672
  isDirty = status
550
673
  if (status) {
551
- self.map.addDirtyDatalayer(self)
674
+ this.map.addDirtyDatalayer(this)
552
675
  // A layer can be made dirty by indirect action (like dragging layers)
553
676
  // we need to have it loaded before saving it.
554
- if (!self.isLoaded()) self.fetchData()
677
+ if (!this.isLoaded()) this.fetchData()
555
678
  } else {
556
- self.map.removeDirtyDatalayer(self)
557
- self.isDeleted = false
679
+ this.map.removeDirtyDatalayer(this)
680
+ this.isDeleted = false
558
681
  }
559
682
  },
560
683
  })
@@ -563,13 +686,11 @@ U.DataLayer = L.Evented.extend({
563
686
  }
564
687
  try {
565
688
  Object.defineProperty(this, 'isDeleted', {
566
- get: function () {
567
- return isDeleted
568
- },
569
- set: function (status) {
570
- if (!isDeleted && status) self.fire('deleted')
689
+ get: () => isDeleted,
690
+ set: (status) => {
691
+ if (!isDeleted && status) this.fire('deleted')
571
692
  isDeleted = status
572
- if (status) self.isDirty = status
693
+ if (status) this.isDirty = status
573
694
  },
574
695
  })
575
696
  } catch (e) {
@@ -582,11 +703,11 @@ U.DataLayer = L.Evented.extend({
582
703
  this.options.remoteData = {}
583
704
  }
584
705
  // Retrocompat
585
- if (this.options.remoteData && this.options.remoteData.from) {
706
+ if (this.options.remoteData?.from) {
586
707
  this.options.fromZoom = this.options.remoteData.from
587
708
  delete this.options.remoteData.from
588
709
  }
589
- if (this.options.remoteData && this.options.remoteData.to) {
710
+ if (this.options.remoteData?.to) {
590
711
  this.options.toZoom = this.options.remoteData.to
591
712
  delete this.options.remoteData.to
592
713
  }
@@ -618,9 +739,9 @@ U.DataLayer = L.Evented.extend({
618
739
  },
619
740
 
620
741
  render: function (fields, builder) {
621
- let impacts = U.Utils.getImpactsFromSchema(fields)
742
+ const impacts = U.Utils.getImpactsFromSchema(fields)
622
743
 
623
- for (let impact of impacts) {
744
+ for (const impact of impacts) {
624
745
  switch (impact) {
625
746
  case 'ui':
626
747
  this.map.onDataLayersChanged()
@@ -630,9 +751,9 @@ U.DataLayer = L.Evented.extend({
630
751
  this.resetLayer()
631
752
  }
632
753
  this.hide()
633
- fields.forEach((field) => {
754
+ for (const field of fields) {
634
755
  this.layer.onEdit(field, builder)
635
- })
756
+ }
636
757
  this.redraw()
637
758
  this.show()
638
759
  break
@@ -745,19 +866,19 @@ U.DataLayer = L.Evented.extend({
745
866
  }
746
867
  },
747
868
 
748
- fromGeoJSON: function (geojson) {
749
- this.addData(geojson)
869
+ fromGeoJSON: function (geojson, sync = true) {
870
+ this.addData(geojson, sync)
750
871
  this._geojson = geojson
751
872
  this._dataloaded = true
752
- this.fire('dataloaded')
753
873
  this.fire('datachanged')
874
+ this.fire('dataloaded')
754
875
  },
755
876
 
756
877
  fromUmapGeoJSON: async function (geojson) {
757
878
  if (geojson._storage) geojson._umap_options = geojson._storage // Retrocompat
758
879
  if (geojson._umap_options) this.setOptions(geojson._umap_options)
759
880
  if (this.isRemoteLayer()) await this.fetchRemoteData()
760
- else this.fromGeoJSON(geojson)
881
+ else this.fromGeoJSON(geojson, false)
761
882
  this._loaded = true
762
883
  },
763
884
 
@@ -787,14 +908,14 @@ U.DataLayer = L.Evented.extend({
787
908
  },
788
909
 
789
910
  showAtZoom: function () {
790
- const from = parseInt(this.options.fromZoom, 10),
791
- to = parseInt(this.options.toZoom, 10),
792
- zoom = this.map.getZoom()
793
- return !((!isNaN(from) && zoom < from) || (!isNaN(to) && zoom > to))
911
+ const from = Number.parseInt(this.options.fromZoom, 10)
912
+ const to = Number.parseInt(this.options.toZoom, 10)
913
+ const zoom = this.map.getZoom()
914
+ return !((!Number.isNaN(from) && zoom < from) || (!Number.isNaN(to) && zoom > to))
794
915
  },
795
916
 
796
917
  hasDynamicData: function () {
797
- return !!(this.options.remoteData && this.options.remoteData.dynamic)
918
+ return !!this.options.remoteData?.dynamic
798
919
  },
799
920
 
800
921
  fetchRemoteData: async function (force) {
@@ -806,13 +927,11 @@ U.DataLayer = L.Evented.extend({
806
927
  url = this.map.proxyUrl(url, this.options.remoteData.ttl)
807
928
  }
808
929
  const response = await this.map.request.get(url)
809
- if (response && response.ok) {
930
+ if (response?.ok) {
810
931
  this.clear()
811
- this.rawToGeoJSON(
812
- await response.text(),
813
- this.options.remoteData.format,
814
- (geojson) => this.fromGeoJSON(geojson)
815
- )
932
+ this.map.formatter
933
+ .parse(await response.text(), this.options.remoteData.format)
934
+ .then((geojson) => this.fromGeoJSON(geojson))
816
935
  }
817
936
  },
818
937
 
@@ -884,11 +1003,7 @@ U.DataLayer = L.Evented.extend({
884
1003
  },
885
1004
 
886
1005
  isRemoteLayer: function () {
887
- return Boolean(
888
- this.options.remoteData &&
889
- this.options.remoteData.url &&
890
- this.options.remoteData.format
891
- )
1006
+ return Boolean(this.options.remoteData?.url && this.options.remoteData.format)
892
1007
  },
893
1008
 
894
1009
  isClustered: function () {
@@ -911,14 +1026,15 @@ U.DataLayer = L.Evented.extend({
911
1026
  if (this.hasDataLoaded()) this.fire('datachanged')
912
1027
  },
913
1028
 
914
- removeLayer: function (feature) {
1029
+ removeLayer: function (feature, sync) {
915
1030
  const id = L.stamp(feature)
1031
+ if (sync !== false) feature.sync.delete()
916
1032
  this.layer.removeLayer(feature)
917
1033
  feature.disconnectFromDataLayer(this)
918
1034
  this._index.splice(this._index.indexOf(id), 1)
919
1035
  delete this._layers[id]
920
1036
  delete this.map.features_index[feature.getSlug()]
921
- if (this.hasDataLoaded()) this.fire('datachanged')
1037
+ if (this.hasDataLoaded() && this.isVisible()) this.fire('datachanged')
922
1038
  },
923
1039
 
924
1040
  indexProperties: function (feature) {
@@ -931,6 +1047,7 @@ U.DataLayer = L.Evented.extend({
931
1047
  if (name.indexOf('_') === 0) return
932
1048
  if (L.Util.indexOf(this._propertiesIndex, name) !== -1) return
933
1049
  this._propertiesIndex.push(name)
1050
+ this._propertiesIndex.sort()
934
1051
  },
935
1052
 
936
1053
  deindexProperty: function (name) {
@@ -938,100 +1055,30 @@ U.DataLayer = L.Evented.extend({
938
1055
  if (idx !== -1) this._propertiesIndex.splice(idx, 1)
939
1056
  },
940
1057
 
941
- addData: function (geojson) {
1058
+ sortedValues: function (property) {
1059
+ return Object.values(this._layers)
1060
+ .map((feature) => feature.properties[property])
1061
+ .filter((val, idx, arr) => arr.indexOf(val) === idx)
1062
+ .sort(U.Utils.naturalSort)
1063
+ },
1064
+
1065
+ addData: function (geojson, sync) {
942
1066
  try {
943
1067
  // Do not fail if remote data is somehow invalid,
944
1068
  // otherwise the layer becomes uneditable.
945
- this.geojsonToFeatures(geojson)
1069
+ this.geojsonToFeatures(geojson, sync)
946
1070
  } catch (err) {
947
1071
  console.log('Error with DataLayer', this.umap_id)
948
1072
  console.error(err)
949
1073
  }
950
1074
  },
951
1075
 
952
- addRawData: function (c, type) {
953
- this.rawToGeoJSON(c, type, (geojson) => this.addData(geojson))
954
- },
955
-
956
- rawToGeoJSON: function (c, type, callback) {
957
- const toDom = (x) => {
958
- const doc = new DOMParser().parseFromString(x, 'text/xml')
959
- const errorNode = doc.querySelector('parsererror')
960
- if (errorNode) {
961
- U.Alert.error(L._('Cannot parse data'))
962
- }
963
- return doc
964
- }
965
-
966
- // TODO add a duck typing guessType
967
- if (type === 'csv') {
968
- csv2geojson.csv2geojson(
969
- c,
970
- {
971
- delimiter: 'auto',
972
- includeLatLon: false,
973
- },
974
- (err, result) => {
975
- // csv2geojson fallback to null geometries when it cannot determine
976
- // lat or lon columns. This is valid geojson, but unwanted from a user
977
- // point of view.
978
- if (result && result.features.length) {
979
- if (result.features[0].geometry === null) {
980
- err = {
981
- type: 'Error',
982
- message: L._('Cannot determine latitude and longitude columns.'),
983
- }
984
- }
985
- }
986
- if (err) {
987
- let message
988
- if (err.type === 'Error') {
989
- message = err.message
990
- } else {
991
- message = L._('{count} errors during import: {message}', {
992
- count: err.length,
993
- message: err[0].message,
994
- })
995
- }
996
- U.Alert.error(message, 10000)
997
- console.error(err)
998
- }
999
- if (result && result.features.length) {
1000
- callback(result)
1001
- }
1002
- }
1003
- )
1004
- } else if (type === 'gpx') {
1005
- callback(toGeoJSON.gpx(toDom(c)))
1006
- } else if (type === 'georss') {
1007
- callback(GeoRSSToGeoJSON(toDom(c)))
1008
- } else if (type === 'kml') {
1009
- callback(toGeoJSON.kml(toDom(c)))
1010
- } else if (type === 'osm') {
1011
- let d
1012
- try {
1013
- d = JSON.parse(c)
1014
- } catch (e) {
1015
- d = toDom(c)
1016
- }
1017
- callback(osmtogeojson(d, { flatProperties: true }))
1018
- } else if (type === 'geojson') {
1019
- try {
1020
- const gj = JSON.parse(c)
1021
- callback(gj)
1022
- } catch (err) {
1023
- U.Alert.error(`Invalid JSON file: ${err}`)
1024
- return
1025
- }
1026
- }
1027
- },
1028
-
1029
1076
  // The choice of the name is not ours, because it is required by Leaflet.
1030
1077
  // It is misleading, as the returned objects are uMap objects, and not
1031
1078
  // GeoJSON features.
1032
- geojsonToFeatures: function (geojson) {
1079
+ geojsonToFeatures: function (geojson, sync) {
1033
1080
  if (!geojson) return
1034
- const features = geojson instanceof Array ? geojson : geojson.features
1081
+ const features = Array.isArray(geojson) ? geojson : geojson.features
1035
1082
  let i
1036
1083
  let len
1037
1084
 
@@ -1045,10 +1092,10 @@ U.DataLayer = L.Evented.extend({
1045
1092
 
1046
1093
  const geometry = geojson.type === 'Feature' ? geojson.geometry : geojson
1047
1094
 
1048
- let feature = this.geoJSONToLeaflet({ geometry, geojson })
1095
+ const feature = this.geoJSONToLeaflet({ geometry, geojson })
1049
1096
  if (feature) {
1050
1097
  this.addLayer(feature)
1051
- feature.onCommit()
1098
+ if (sync) feature.onCommit()
1052
1099
  return feature
1053
1100
  }
1054
1101
  },
@@ -1077,7 +1124,8 @@ U.DataLayer = L.Evented.extend({
1077
1124
  } = {}) {
1078
1125
  if (!geometry) return // null geometry is valid geojson.
1079
1126
  const coords = geometry.coordinates
1080
- let latlng, latlngs
1127
+ let latlng
1128
+ let latlngs
1081
1129
 
1082
1130
  // Create a default geojson if none is provided
1083
1131
  if (geojson === undefined) geojson = { type: 'Feature', geometry: geometry }
@@ -1154,10 +1202,12 @@ U.DataLayer = L.Evented.extend({
1154
1202
  return new U.Polygon(this.map, latlngs, { geojson: geojson, datalayer: this }, id)
1155
1203
  },
1156
1204
 
1157
- importRaw: function (raw, type) {
1158
- this.addRawData(raw, type)
1205
+ importRaw: async function (raw, format) {
1206
+ this.map.formatter
1207
+ .parse(raw, format)
1208
+ .then((geojson) => this.addData(geojson))
1209
+ .then(() => this.zoomTo())
1159
1210
  this.isDirty = true
1160
- this.zoomTo()
1161
1211
  },
1162
1212
 
1163
1213
  importFromFiles: function (files, type) {
@@ -1176,7 +1226,7 @@ U.DataLayer = L.Evented.extend({
1176
1226
  importFromUrl: async function (uri, type) {
1177
1227
  uri = this.map.localizeUrl(uri)
1178
1228
  const response = await this.map.request.get(uri)
1179
- if (response && response.ok) {
1229
+ if (response?.ok) {
1180
1230
  this.importRaw(await response.text(), type)
1181
1231
  }
1182
1232
  },
@@ -1222,8 +1272,8 @@ U.DataLayer = L.Evented.extend({
1222
1272
  const options = U.Utils.CopyJSON(this.options)
1223
1273
  options.name = L._('Clone of {name}', { name: this.options.name })
1224
1274
  delete options.id
1225
- const geojson = U.Utils.CopyJSON(this._geojson),
1226
- datalayer = this.map.createDataLayer(options)
1275
+ const geojson = U.Utils.CopyJSON(this._geojson)
1276
+ const datalayer = this.map.createDataLayer(options)
1227
1277
  datalayer.fromGeoJSON(geojson)
1228
1278
  return datalayer
1229
1279
  },
@@ -1272,28 +1322,28 @@ U.DataLayer = L.Evented.extend({
1272
1322
  if (!this.map.editEnabled || !this.isLoaded()) {
1273
1323
  return
1274
1324
  }
1275
- const container = L.DomUtil.create('div', 'umap-layer-properties-container'),
1276
- metadataFields = [
1277
- 'options.name',
1278
- 'options.description',
1279
- ['options.type', { handler: 'LayerTypeChooser', label: L._('Type of layer') }],
1280
- ['options.displayOnLoad', { label: L._('Display on load'), handler: 'Switch' }],
1281
- [
1282
- 'options.browsable',
1283
- {
1284
- label: L._('Data is browsable'),
1285
- handler: 'Switch',
1286
- helpEntries: 'browsable',
1287
- },
1288
- ],
1289
- [
1290
- 'options.inCaption',
1291
- {
1292
- label: L._('Show this layer in the caption'),
1293
- handler: 'Switch',
1294
- },
1295
- ],
1296
- ]
1325
+ const container = L.DomUtil.create('div', 'umap-layer-properties-container')
1326
+ const metadataFields = [
1327
+ 'options.name',
1328
+ 'options.description',
1329
+ ['options.type', { handler: 'LayerTypeChooser', label: L._('Type of layer') }],
1330
+ ['options.displayOnLoad', { label: L._('Display on load'), handler: 'Switch' }],
1331
+ [
1332
+ 'options.browsable',
1333
+ {
1334
+ label: L._('Data is browsable'),
1335
+ handler: 'Switch',
1336
+ helpEntries: 'browsable',
1337
+ },
1338
+ ],
1339
+ [
1340
+ 'options.inCaption',
1341
+ {
1342
+ label: L._('Show this layer in the caption'),
1343
+ handler: 'Switch',
1344
+ },
1345
+ ],
1346
+ ]
1297
1347
  L.DomUtil.createTitle(container, L._('Layer properties'), 'icon-layers')
1298
1348
  let builder = new U.FormBuilder(this, metadataFields, {
1299
1349
  callback: function (e) {
@@ -1318,7 +1368,7 @@ U.DataLayer = L.Evented.extend({
1318
1368
  layerProperties.appendChild(builder.build())
1319
1369
  }
1320
1370
 
1321
- let shapeOptions = [
1371
+ const shapeOptions = [
1322
1372
  'options.color',
1323
1373
  'options.iconClass',
1324
1374
  'options.iconUrl',
@@ -1337,7 +1387,7 @@ U.DataLayer = L.Evented.extend({
1337
1387
  const shapeProperties = L.DomUtil.createFieldset(container, L._('Shape properties'))
1338
1388
  shapeProperties.appendChild(builder.build())
1339
1389
 
1340
- let optionsFields = [
1390
+ const optionsFields = [
1341
1391
  'options.smoothFactor',
1342
1392
  'options.dashArray',
1343
1393
  'options.zoomTo',
@@ -1484,23 +1534,23 @@ U.DataLayer = L.Evented.extend({
1484
1534
  },
1485
1535
 
1486
1536
  getOption: function (option, feature) {
1487
- if (this.layer && this.layer.getOption) {
1537
+ if (this.layer?.getOption) {
1488
1538
  const value = this.layer.getOption(option, feature)
1489
1539
  if (typeof value !== 'undefined') return value
1490
1540
  }
1491
1541
  if (typeof this.getOwnOption(option) !== 'undefined') {
1492
1542
  return this.getOwnOption(option)
1493
- } else if (this.layer && this.layer.defaults && this.layer.defaults[option]) {
1543
+ }
1544
+ if (this.layer?.defaults?.[option]) {
1494
1545
  return this.layer.defaults[option]
1495
- } else {
1496
- return this.map.getOption(option, feature)
1497
1546
  }
1547
+ return this.map.getOption(option, feature)
1498
1548
  },
1499
1549
 
1500
1550
  buildVersionsFieldset: async function (container) {
1501
1551
  const appendVersion = (data) => {
1502
- const date = new Date(parseInt(data.at, 10))
1503
- const content = `${date.toLocaleString(L.lang)} (${parseInt(data.size) / 1000}Kb)`
1552
+ const date = new Date(Number.parseInt(data.at, 10))
1553
+ const content = `${date.toLocaleString(L.lang)} (${Number.parseInt(data.size) / 1000}Kb)`
1504
1554
  const el = L.DomUtil.create('div', 'umap-datalayer-version', versionsContainer)
1505
1555
  const button = L.DomUtil.createButton(
1506
1556
  '',
@@ -1576,7 +1626,7 @@ U.DataLayer = L.Evented.extend({
1576
1626
 
1577
1627
  // Is this layer type browsable in theorie
1578
1628
  isBrowsable: function () {
1579
- return this.layer && this.layer.browsable
1629
+ return this.layer?.browsable
1580
1630
  },
1581
1631
 
1582
1632
  // Is this layer browsable in theorie
@@ -1753,9 +1803,9 @@ U.DataLayer = L.Evented.extend({
1753
1803
  },
1754
1804
 
1755
1805
  tableEdit: function () {
1756
- if (this.isRemoteLayer() || !this.isVisible()) return
1806
+ if (!this.isVisible()) return
1757
1807
  const editor = new U.TableEditor(this)
1758
- editor.edit()
1808
+ editor.open()
1759
1809
  },
1760
1810
 
1761
1811
  getFilterKeys: function () {
@@ -1763,9 +1813,9 @@ U.DataLayer = L.Evented.extend({
1763
1813
  // By default, it will we use the "name" property, which is also the one used as label in the features list.
1764
1814
  // When map owner has configured another label or sort key, we try to be smart and search in the same keys.
1765
1815
  if (this.map.options.filterKey) return this.map.options.filterKey
1766
- else if (this.options.labelKey) return this.options.labelKey
1767
- else if (this.map.options.sortKey) return this.map.options.sortKey
1768
- else return 'name'
1816
+ if (this.options.labelKey) return this.options.labelKey
1817
+ if (this.map.options.sortKey) return this.map.options.sortKey
1818
+ return 'name'
1769
1819
  },
1770
1820
  })
1771
1821