umap-project 2.0.4__py3-none-any.whl → 2.1.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 (106) hide show
  1. umap/__init__.py +1 -1
  2. umap/fields.py +3 -1
  3. umap/locale/br/LC_MESSAGES/django.po +76 -71
  4. umap/locale/en/LC_MESSAGES/django.po +41 -41
  5. umap/locale/hu/LC_MESSAGES/django.po +42 -42
  6. umap/locale/it/LC_MESSAGES/django.po +64 -58
  7. umap/locale/ms/LC_MESSAGES/django.po +62 -57
  8. umap/migrations/0018_datalayer_uuid.py +62 -0
  9. umap/migrations/0019_migrate_internal_remote_datalayers.py +52 -0
  10. umap/models.py +20 -3
  11. umap/settings/base.py +1 -0
  12. umap/settings/dev.py +1 -0
  13. umap/static/umap/js/modules/browser.js +2 -2
  14. umap/static/umap/js/modules/global.js +14 -4
  15. umap/static/umap/js/modules/i18n.js +35 -0
  16. umap/static/umap/js/modules/leaflet-configure.js +7 -0
  17. umap/static/umap/js/modules/schema.js +388 -0
  18. umap/static/umap/js/modules/urls.js +17 -2
  19. umap/static/umap/js/modules/utils.js +24 -0
  20. umap/static/umap/js/umap.controls.js +9 -10
  21. umap/static/umap/js/umap.core.js +5 -5
  22. umap/static/umap/js/umap.features.js +23 -9
  23. umap/static/umap/js/umap.forms.js +49 -299
  24. umap/static/umap/js/umap.icon.js +2 -2
  25. umap/static/umap/js/umap.js +26 -129
  26. umap/static/umap/js/umap.layer.js +9 -9
  27. umap/static/umap/js/umap.popup.js +3 -0
  28. umap/static/umap/js/umap.share.js +1 -1
  29. umap/static/umap/locale/am_ET.json +229 -225
  30. umap/static/umap/locale/ar.json +229 -225
  31. umap/static/umap/locale/ast.json +229 -225
  32. umap/static/umap/locale/bg.json +229 -225
  33. umap/static/umap/locale/br.json +237 -233
  34. umap/static/umap/locale/ca.json +229 -225
  35. umap/static/umap/locale/cs_CZ.json +229 -225
  36. umap/static/umap/locale/da.json +229 -225
  37. umap/static/umap/locale/de.json +229 -225
  38. umap/static/umap/locale/el.json +229 -225
  39. umap/static/umap/locale/en.json +230 -233
  40. umap/static/umap/locale/en_US.json +229 -225
  41. umap/static/umap/locale/es.json +229 -225
  42. umap/static/umap/locale/et.json +229 -225
  43. umap/static/umap/locale/eu.json +226 -198
  44. umap/static/umap/locale/fa_IR.json +229 -225
  45. umap/static/umap/locale/fi.json +229 -225
  46. umap/static/umap/locale/fr.json +229 -232
  47. umap/static/umap/locale/gl.json +229 -225
  48. umap/static/umap/locale/he.json +229 -225
  49. umap/static/umap/locale/hr.json +229 -225
  50. umap/static/umap/locale/hu.json +229 -232
  51. umap/static/umap/locale/id.json +229 -225
  52. umap/static/umap/locale/is.json +229 -225
  53. umap/static/umap/locale/it.json +229 -232
  54. umap/static/umap/locale/ja.json +229 -225
  55. umap/static/umap/locale/ko.json +229 -225
  56. umap/static/umap/locale/lt.json +229 -225
  57. umap/static/umap/locale/ms.json +229 -232
  58. umap/static/umap/locale/nl.json +232 -228
  59. umap/static/umap/locale/no.json +229 -225
  60. umap/static/umap/locale/pl.json +229 -225
  61. umap/static/umap/locale/pl_PL.json +229 -225
  62. umap/static/umap/locale/pt.json +229 -225
  63. umap/static/umap/locale/pt_BR.json +229 -225
  64. umap/static/umap/locale/pt_PT.json +229 -225
  65. umap/static/umap/locale/ro.json +229 -225
  66. umap/static/umap/locale/ru.json +229 -225
  67. umap/static/umap/locale/sk_SK.json +229 -225
  68. umap/static/umap/locale/sl.json +229 -225
  69. umap/static/umap/locale/sr.json +229 -225
  70. umap/static/umap/locale/sv.json +229 -225
  71. umap/static/umap/locale/th_TH.json +229 -225
  72. umap/static/umap/locale/tr.json +229 -225
  73. umap/static/umap/locale/uk_UA.json +229 -225
  74. umap/static/umap/locale/vi.json +229 -225
  75. umap/static/umap/locale/vi_VN.json +229 -225
  76. umap/static/umap/locale/zh.json +229 -225
  77. umap/static/umap/locale/zh_CN.json +229 -225
  78. umap/static/umap/locale/zh_TW.Big5.json +229 -225
  79. umap/static/umap/locale/zh_TW.json +229 -232
  80. umap/static/umap/test/index.html +0 -2
  81. umap/static/umap/{test → unittests}/URLs.js +5 -0
  82. umap/static/umap/vendors/leaflet/leaflet-src.esm.js +7064 -7064
  83. umap/static/umap/vendors/photon/leaflet.photon.js +3 -0
  84. umap/templates/umap/js.html +8 -6
  85. umap/templatetags/umap_tags.py +3 -2
  86. umap/tests/integration/test_browser.py +40 -0
  87. umap/tests/integration/test_collaborative_editing.py +72 -3
  88. umap/tests/integration/test_export_map.py +226 -9
  89. umap/tests/integration/test_features_id_generation.py +51 -0
  90. umap/tests/integration/test_owned_map.py +14 -1
  91. umap/tests/integration/test_statics.py +3 -3
  92. umap/tests/integration/test_tilelayer.py +3 -3
  93. umap/tests/settings.py +3 -3
  94. umap/tests/test_datalayer_views.py +67 -20
  95. umap/tests/test_map_views.py +20 -0
  96. umap/tests/test_merge_features.py +25 -5
  97. umap/urls.py +12 -12
  98. umap/utils.py +7 -0
  99. umap/views.py +56 -47
  100. umap/wsgi.py +1 -0
  101. {umap_project-2.0.4.dist-info → umap_project-2.1.0.dist-info}/METADATA +9 -9
  102. {umap_project-2.0.4.dist-info → umap_project-2.1.0.dist-info}/RECORD +105 -99
  103. umap/static/umap/test/Map.Export.js +0 -106
  104. {umap_project-2.0.4.dist-info → umap_project-2.1.0.dist-info}/WHEEL +0 -0
  105. {umap_project-2.0.4.dist-info → umap_project-2.1.0.dist-info}/entry_points.txt +0 -0
  106. {umap_project-2.0.4.dist-info → umap_project-2.1.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,388 @@
1
+ import { translate } from './i18n.js'
2
+
3
+ export const SCHEMA = {
4
+ zoom: {
5
+ type: Number,
6
+ },
7
+ scrollWheelZoom: {
8
+ type: Boolean,
9
+ label: translate('Allow scroll wheel zoom?'),
10
+ },
11
+ scaleControl: {
12
+ type: Boolean,
13
+ label: translate('Do you want to display the scale control?'),
14
+ default: true,
15
+ },
16
+ moreControl: {
17
+ type: Boolean,
18
+ label: translate('Do you want to display the «more» control?'),
19
+ default: true,
20
+ },
21
+ miniMap: {
22
+ type: Boolean,
23
+ label: translate('Do you want to display a minimap?'),
24
+ default: false,
25
+ },
26
+ displayPopupFooter: {
27
+ type: Boolean,
28
+ label: translate('Do you want to display popup footer?'),
29
+ default: false,
30
+ },
31
+ onLoadPanel: {
32
+ type: String,
33
+ label: translate('Do you want to display a panel on load?'),
34
+ choices: [
35
+ ['none', translate('None')],
36
+ ['caption', translate('Caption')],
37
+ ['databrowser', translate('Data browser')],
38
+ ['facet', translate('Facet search')],
39
+ ],
40
+ default: 'none',
41
+ },
42
+ defaultView: {
43
+ type: String,
44
+ label: translate('Default view'),
45
+ choices: [
46
+ ['center', translate('Saved center and zoom')],
47
+ ['data', translate('Fit all data')],
48
+ ['latest', translate('Latest feature')],
49
+ ['locate', translate('User location')],
50
+ ],
51
+ default: 'center',
52
+ },
53
+ name: {
54
+ type: String,
55
+ label: translate('name'),
56
+ },
57
+ description: {
58
+ label: translate('description'),
59
+ type: 'Text',
60
+ helpEntries: 'textFormatting',
61
+ },
62
+ licence: {
63
+ type: String,
64
+ label: translate('licence'),
65
+ },
66
+ tilelayer: {
67
+ type: Object,
68
+ },
69
+ overlay: {
70
+ type: Object,
71
+ },
72
+ limitBounds: {
73
+ type: Object,
74
+ },
75
+ color: {
76
+ type: String,
77
+ handler: 'ColorPicker',
78
+ label: translate('color'),
79
+ helpEntries: 'colorValue',
80
+ inheritable: true,
81
+ default: 'DarkBlue',
82
+ },
83
+ iconClass: {
84
+ type: String,
85
+ label: translate('Icon shape'),
86
+ inheritable: true,
87
+ choices: [
88
+ ['Default', translate('Default')],
89
+ ['Circle', translate('Circle')],
90
+ ['Drop', translate('Drop')],
91
+ ['Ball', translate('Ball')],
92
+ ],
93
+ default: 'Default',
94
+ },
95
+ iconUrl: {
96
+ type: String,
97
+ handler: 'IconUrl',
98
+ label: translate('Icon symbol'),
99
+ inheritable: true,
100
+ // helpText: translate(
101
+ // 'Symbol can be either a unicode character or an URL. You can use feature properties as variables: ex.: with "http://myserver.org/images/{name}.png", the {name} variable will be replaced by the "name" value of each marker.'
102
+ // ),
103
+ },
104
+ smoothFactor: {
105
+ type: Number,
106
+ min: 0,
107
+ max: 10,
108
+ step: 0.5,
109
+ label: translate('Simplify'),
110
+ helpEntries: 'smoothFactor',
111
+ inheritable: true,
112
+ default: 1.0,
113
+ },
114
+ iconOpacity: {
115
+ type: Number,
116
+ min: 0.1,
117
+ max: 1,
118
+ step: 0.1,
119
+ label: translate('icon opacity'),
120
+ inheritable: true,
121
+ default: 1,
122
+ },
123
+ opacity: {
124
+ type: Number,
125
+ min: 0.1,
126
+ max: 1,
127
+ step: 0.1,
128
+ label: translate('opacity'),
129
+ inheritable: true,
130
+ default: 0.5,
131
+ },
132
+ weight: {
133
+ type: Number,
134
+ min: 1,
135
+ max: 20,
136
+ step: 1,
137
+ label: translate('weight'),
138
+ inheritable: true,
139
+ default: 3,
140
+ },
141
+ fill: {
142
+ type: Boolean,
143
+ label: translate('fill'),
144
+ helpEntries: 'fill',
145
+ inheritable: true,
146
+ default: true,
147
+ },
148
+ fillColor: {
149
+ type: String,
150
+ handler: 'ColorPicker',
151
+ label: translate('fill color'),
152
+ helpEntries: 'fillColor',
153
+ inheritable: true,
154
+ },
155
+ fillOpacity: {
156
+ type: Number,
157
+ min: 0.1,
158
+ max: 1,
159
+ step: 0.1,
160
+ label: translate('fill opacity'),
161
+ inheritable: true,
162
+ default: 0.3,
163
+ },
164
+ dashArray: {
165
+ type: String,
166
+ label: translate('dash array'),
167
+ helpEntries: 'dashArray',
168
+ inheritable: true,
169
+ },
170
+ popupShape: {
171
+ type: String,
172
+ label: translate('Popup shape'),
173
+ inheritable: true,
174
+ choices: [
175
+ ['Default', translate('Popup')],
176
+ ['Large', translate('Popup (large)')],
177
+ ['Panel', translate('Side panel')],
178
+ ],
179
+ default: 'Default',
180
+ },
181
+ popupTemplate: {
182
+ type: String,
183
+ label: translate('Popup content style'),
184
+ inheritable: true,
185
+ choices: [
186
+ ['Default', translate('Default')],
187
+ ['Table', translate('Table')],
188
+ ['GeoRSSImage', translate('GeoRSS (title + image)')],
189
+ ['GeoRSSLink', translate('GeoRSS (only link)')],
190
+ ['OSM', translate('OpenStreetMap')],
191
+ ],
192
+ default: 'Default',
193
+ },
194
+ popupContentTemplate: {
195
+ type: 'Text',
196
+ label: translate('Popup content template'),
197
+ helpEntries: ['dynamicProperties', 'textFormatting'],
198
+ placeholder: '# {name}',
199
+ inheritable: true,
200
+ default: '# {name}\n{description}',
201
+ },
202
+ zoomTo: {
203
+ type: Number,
204
+ placeholder: translate('Inherit'),
205
+ helpEntries: 'zoomTo',
206
+ label: translate('Default zoom level'),
207
+ inheritable: true,
208
+ },
209
+ captionBar: {
210
+ type: Boolean,
211
+ label: translate('Do you want to display a caption bar?'),
212
+ default: false,
213
+ },
214
+ captionMenus: {
215
+ type: Boolean,
216
+ label: translate('Do you want to display caption menus?'),
217
+ default: true,
218
+ },
219
+ slideshow: {
220
+ type: Object,
221
+ },
222
+ sortKey: {
223
+ type: String,
224
+ },
225
+ labelKey: {
226
+ type: String,
227
+ helpEntries: 'labelKey',
228
+ placeholder: translate('Default: name'),
229
+ label: translate('Label key'),
230
+ inheritable: true,
231
+ },
232
+ filterKey: {
233
+ type: String,
234
+ },
235
+ facetKey: {
236
+ type: String,
237
+ },
238
+ slugKey: {
239
+ type: String,
240
+ },
241
+ showLabel: {
242
+ type: Boolean,
243
+ nullable: true,
244
+ label: translate('Display label'),
245
+ inheritable: true,
246
+ default: false,
247
+ },
248
+ labelDirection: {
249
+ type: String,
250
+ label: translate('Label direction'),
251
+ inheritable: true,
252
+ choices: [
253
+ ['auto', translate('Automatic')],
254
+ ['left', translate('On the left')],
255
+ ['right', translate('On the right')],
256
+ ['top', translate('On the top')],
257
+ ['bottom', translate('On the bottom')],
258
+ ],
259
+ default: 'auto',
260
+ },
261
+ labelInteractive: {
262
+ type: Boolean,
263
+ label: translate('Labels are clickable'),
264
+ inheritable: true,
265
+ },
266
+ outlinkTarget: {
267
+ type: String,
268
+ label: translate('Open link in…'),
269
+ inheritable: true,
270
+ default: 'blank',
271
+ choices: [
272
+ ['blank', translate('new window')],
273
+ ['self', translate('iframe')],
274
+ ['parent', translate('parent window')],
275
+ ],
276
+ },
277
+ shortCredit: {
278
+ type: String,
279
+ label: translate('Short credits'),
280
+ helpEntries: ['shortCredit', 'textFormatting'],
281
+ },
282
+ longCredit: {
283
+ type: 'Text',
284
+ label: translate('Long credits'),
285
+ helpEntries: ['longCredit', 'textFormatting'],
286
+ },
287
+ permanentCredit: {
288
+ type: 'Text',
289
+ label: translate('Permanent credits'),
290
+ helpEntries: ['permanentCredit', 'textFormatting'],
291
+ },
292
+ permanentCreditBackground: {
293
+ type: Boolean,
294
+ label: translate('Permanent credits background'),
295
+ default: true,
296
+ },
297
+ zoomControl: {
298
+ type: Boolean,
299
+ nullable: true,
300
+ label: translate('Display the zoom control'),
301
+ default: true,
302
+ },
303
+ datalayersControl: {
304
+ type: Boolean,
305
+ nullable: true,
306
+ handler: 'DataLayersControl',
307
+ label: translate('Display the data layers control'),
308
+ default: true,
309
+ },
310
+ searchControl: {
311
+ type: Boolean,
312
+ nullable: true,
313
+ label: translate('Display the search control'),
314
+ default: true,
315
+ },
316
+ locateControl: {
317
+ type: Boolean,
318
+ nullable: true,
319
+ label: translate('Display the locate control'),
320
+ },
321
+ fullscreenControl: {
322
+ type: Boolean,
323
+ nullable: true,
324
+ label: translate('Display the fullscreen control'),
325
+ default: true,
326
+ },
327
+ editinosmControl: {
328
+ type: Boolean,
329
+ nullable: true,
330
+ label: translate('Display the control to open OpenStreetMap editor'),
331
+ default: null,
332
+ },
333
+ embedControl: {
334
+ type: Boolean,
335
+ nullable: true,
336
+ label: translate('Display the embed control'),
337
+ default: true,
338
+ },
339
+ measureControl: {
340
+ type: Boolean,
341
+ nullable: true,
342
+ label: translate('Display the measure control'),
343
+ },
344
+ tilelayersControl: {
345
+ type: Boolean,
346
+ nullable: true,
347
+ label: translate('Display the tile layers control'),
348
+ },
349
+ starControl: {
350
+ type: Boolean,
351
+ nullable: true,
352
+ label: translate('Display the star map button'),
353
+ },
354
+ easing: {
355
+ type: Boolean,
356
+ default: false,
357
+ },
358
+ interactive: {
359
+ type: Boolean,
360
+ label: translate('Allow interactions'),
361
+ helpEntries: 'interactive',
362
+ inheritable: true,
363
+ default: true,
364
+ },
365
+ fromZoom: {
366
+ type: Number,
367
+ label: translate('From zoom'),
368
+ helpText: translate('Optional.'),
369
+ },
370
+ toZoom: {
371
+ type: Number,
372
+ label: translate('To zoom'),
373
+ helpText: translate('Optional.'),
374
+ },
375
+ stroke: {
376
+ type: Boolean,
377
+ label: translate('stroke'),
378
+ helpEntries: 'stroke',
379
+ inheritable: true,
380
+ default: true,
381
+ },
382
+ outlink: {
383
+ label: translate('Link to…'),
384
+ helpEntries: 'outlink',
385
+ placeholder: 'http://...',
386
+ inheritable: true,
387
+ },
388
+ }
@@ -1,4 +1,19 @@
1
- import { Util } from '../../vendors/leaflet/leaflet-src.esm.js'
1
+ // Vendorized from leaflet.utils
2
+ // https://github.com/Leaflet/Leaflet/blob/108c6717b70f57c63645498f9bd66b6677758786/src/core/Util.js#L132-L151
3
+ var templateRe = /\{ *([\w_ -]+) *\}/g
4
+
5
+ function template(str, data) {
6
+ return str.replace(templateRe, function (str, key) {
7
+ var value = data[key]
8
+
9
+ if (value === undefined) {
10
+ throw new Error('No value provided for variable ' + str)
11
+ } else if (typeof value === 'function') {
12
+ value = value(data)
13
+ }
14
+ return value
15
+ })
16
+ }
2
17
 
3
18
  export default class URLs {
4
19
  constructor(serverUrls) {
@@ -9,7 +24,7 @@ export default class URLs {
9
24
  if (typeof this[urlName] === 'function') return this[urlName](params)
10
25
 
11
26
  if (this.urls.hasOwnProperty(urlName)) {
12
- return Util.template(this.urls[urlName], params)
27
+ return template(this.urls[urlName], params)
13
28
  } else {
14
29
  throw `Unable to find a URL for route ${urlName}`
15
30
  }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Generate a pseudo-unique identifier (5 chars long, mixed-case alphanumeric)
3
+ *
4
+ * Here's the collision risk:
5
+ * - for 6 chars, 1 in 100 000
6
+ * - for 5 chars, 5 in 100 000
7
+ * - for 4 chars, 500 in 100 000
8
+ *
9
+ * @returns string
10
+ */
11
+ export function generateId() {
12
+ return btoa(Math.random().toString()).substring(10, 15)
13
+ }
14
+
15
+ /**
16
+ * Ensure the ID matches the expected format.
17
+ *
18
+ * @param {string} string
19
+ * @returns {boolean}
20
+ */
21
+ export function checkId(string) {
22
+ if (typeof string !== 'string') return false
23
+ return /^[A-Za-z0-9]{5}$/.test(string)
24
+ }
@@ -1184,25 +1184,22 @@ U.AttributionControl = L.Control.Attribution.extend({
1184
1184
  this._container,
1185
1185
  credits
1186
1186
  )
1187
- if (this._map.options.shortCredit) {
1188
- L.DomUtil.add(
1189
- 'span',
1190
- '',
1191
- container,
1192
- ` — ${L.Util.toHTML(this._map.options.shortCredit)}`
1193
- )
1187
+ const shortCredit = this._map.getOption('shortCredit'),
1188
+ captionMenus = this._map.getOption('captionMenus')
1189
+ if (shortCredit) {
1190
+ L.DomUtil.add('span', '', container, ` — ${L.Util.toHTML(shortCredit)}`)
1194
1191
  }
1195
- if (this._map.options.captionMenus) {
1192
+ if (captionMenus) {
1196
1193
  const link = L.DomUtil.add('a', '', container, ` — ${L._('About')}`)
1197
1194
  L.DomEvent.on(link, 'click', L.DomEvent.stop)
1198
1195
  .on(link, 'click', this._map.displayCaption, this._map)
1199
1196
  .on(link, 'dblclick', L.DomEvent.stop)
1200
1197
  }
1201
- if (window.top === window.self && this._map.options.captionMenus) {
1198
+ if (window.top === window.self && captionMenus) {
1202
1199
  // We are not in iframe mode
1203
1200
  L.DomUtil.createLink('', container, ` — ${L._('Home')}`, '/')
1204
1201
  }
1205
- if (this._map.options.captionMenus) {
1202
+ if (captionMenus) {
1206
1203
  L.DomUtil.createLink(
1207
1204
  '',
1208
1205
  container,
@@ -1240,6 +1237,7 @@ U.StarControl = L.Control.extend({
1240
1237
  U.Search = L.PhotonSearch.extend({
1241
1238
  initialize: function (map, input, options) {
1242
1239
  this.options.placeholder = L._('Type a place name or coordinates')
1240
+ this.options.location_bias_scale = 0.5
1243
1241
  L.PhotonSearch.prototype.initialize.call(this, map, input, options)
1244
1242
  this.options.url = map.options.urls.search
1245
1243
  if (map.options.maxBounds) this.options.bbox = map.options.maxBounds.toBBoxString()
@@ -1279,6 +1277,7 @@ U.Search = L.PhotonSearch.extend({
1279
1277
  // Only numbers, abort.
1280
1278
  if (/^[\d .,]*$/.test(this.input.value)) return
1281
1279
  // Do normal search
1280
+ this.options.includePosition = this.map.getZoom() > 10
1282
1281
  L.PhotonSearch.prototype.search.call(this)
1283
1282
  },
1284
1283
 
@@ -415,7 +415,10 @@ L.DomUtil.TextColorFromBackgroundColor = (el, bgcolor) => {
415
415
  L.DomUtil.contrastWCAG21 = (rgb) => {
416
416
  const [r, g, b] = rgb
417
417
  // luminance of inputted colour
418
- const lum = 0.2126 * L.DomUtil.colourMod(r) + 0.7152 * L.DomUtil.colourMod(g) + 0.0722 * L.DomUtil.colourMod(b)
418
+ const lum =
419
+ 0.2126 * L.DomUtil.colourMod(r) +
420
+ 0.7152 * L.DomUtil.colourMod(g) +
421
+ 0.0722 * L.DomUtil.colourMod(b)
419
422
  // white has a luminance of 1
420
423
  const whiteLum = 1
421
424
  const contrast = (whiteLum + 0.05) / (lum + 0.05)
@@ -729,9 +732,6 @@ U.Help = L.Class.extend({
729
732
  formatURL: `${L._(
730
733
  'Supported variables that will be dynamically replaced'
731
734
  )}: {bbox}, {lat}, {lng}, {zoom}, {east}, {north}..., {left}, {top}..., locale, lang`,
732
- formatIconSymbol: L._(
733
- 'Symbol can be either a unicode character or an URL. You can use feature properties as variables: ex.: with "http://myserver.org/images/{name}.png", the {name} variable will be replaced by the "name" value of each marker.'
734
- ),
735
735
  colorValue: L._('Must be a valid CSS value (eg.: DarkBlue or #123456)'),
736
736
  smoothFactor: L._(
737
737
  'How much to simplify the polyline on each zoom level (more = better performance and smoother look, less = more accurate)'
@@ -741,7 +741,7 @@ U.Help = L.Class.extend({
741
741
  ),
742
742
  zoomTo: L._('Zoom level for automatic zooms'),
743
743
  labelKey: L._(
744
- 'The name of the property to use as feature label (eg.: "nom"). You can also use properties inside brackets to use more than one or mix with static content (eg.: "{name} in {place}")'
744
+ 'The name of the property to use as feature label (eg.: "nom"). You can also use properties inside brackets to use more than one or mix with static content (eg.: "{name} in {place}")'
745
745
  ),
746
746
  stroke: L._('Whether to display or not polygons paths.'),
747
747
  fill: L._('Whether to fill polygons with color.'),
@@ -9,8 +9,17 @@ U.FeatureMixin = {
9
9
  // DataLayer the marker belongs to
10
10
  this.datalayer = options.datalayer || null
11
11
  this.properties = { _umap_options: {} }
12
+ let geojson_id
12
13
  if (options.geojson) {
13
14
  this.populate(options.geojson)
15
+ geojson_id = options.geojson.id
16
+ }
17
+
18
+ // Each feature needs an unique identifier
19
+ if (U.Utils.checkId(geojson_id)) {
20
+ this.id = geojson_id
21
+ } else {
22
+ this.id = U.Utils.generateId()
14
23
  }
15
24
  let isDirty = false
16
25
  const self = this
@@ -44,7 +53,7 @@ U.FeatureMixin = {
44
53
  },
45
54
 
46
55
  getSlug: function () {
47
- return this.properties[this.map.options.slugKey || 'name'] || ''
56
+ return this.properties[this.map.getOption('slugKey') || 'name'] || ''
48
57
  },
49
58
 
50
59
  getPermalink: function () {
@@ -94,11 +103,15 @@ U.FeatureMixin = {
94
103
  L._('Feature properties')
95
104
  )
96
105
 
97
- let builder = new U.FormBuilder(this, ['datalayer'], {
98
- callback: function () {
99
- this.edit(e)
100
- }, // removeLayer step will close the edit panel, let's reopen it
101
- })
106
+ let builder = new U.FormBuilder(
107
+ this,
108
+ [['datalayer', { handler: 'DataLayerSwitcher' }]],
109
+ {
110
+ callback: function () {
111
+ this.edit(e)
112
+ }, // removeLayer step will close the edit panel, let's reopen it
113
+ }
114
+ )
102
115
  container.appendChild(builder.build())
103
116
 
104
117
  const properties = []
@@ -200,7 +213,7 @@ U.FeatureMixin = {
200
213
  if (L.Browser.ielt9) return false
201
214
  if (this.datalayer.isRemoteLayer() && this.datalayer.options.remoteData.dynamic)
202
215
  return false
203
- return this.map.options.displayPopupFooter
216
+ return this.map.getOption('displayPopupFooter')
204
217
  },
205
218
 
206
219
  getPopupClass: function () {
@@ -300,7 +313,7 @@ U.FeatureMixin = {
300
313
 
301
314
  zoomTo: function (e) {
302
315
  e = e || {}
303
- const easing = e.easing !== undefined ? e.easing : this.map.options.easing
316
+ const easing = e.easing !== undefined ? e.easing : this.map.getOption('easing')
304
317
  if (easing) {
305
318
  this.map.flyTo(this.getCenter(), this.getBestZoom())
306
319
  } else {
@@ -344,6 +357,7 @@ U.FeatureMixin = {
344
357
  toGeoJSON: function () {
345
358
  const geojson = this.parentClass.prototype.toGeoJSON.call(this)
346
359
  geojson.properties = this.cloneProperties()
360
+ geojson.id = this.id
347
361
  delete geojson.properties._storage_options
348
362
  return geojson
349
363
  },
@@ -965,7 +979,7 @@ U.PathMixin = {
965
979
  zoomTo: function (e) {
966
980
  // Use bounds instead of centroid for paths.
967
981
  e = e || {}
968
- const easing = e.easing !== undefined ? e.easing : this.map.options.easing
982
+ const easing = e.easing !== undefined ? e.easing : this.map.getOption('easing')
969
983
  if (easing) {
970
984
  this.map.flyToBounds(this.getBounds(), this.getBestZoom())
971
985
  } else {