umap-project 1.12.1__py3-none-any.whl → 1.13.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 (159) hide show
  1. umap/__init__.py +1 -1
  2. umap/locale/br/LC_MESSAGES/django.mo +0 -0
  3. umap/locale/br/LC_MESSAGES/django.po +82 -54
  4. umap/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  5. umap/locale/cs_CZ/LC_MESSAGES/django.po +82 -54
  6. umap/locale/el/LC_MESSAGES/django.mo +0 -0
  7. umap/locale/el/LC_MESSAGES/django.po +86 -58
  8. umap/locale/en/LC_MESSAGES/django.po +79 -51
  9. umap/locale/es/LC_MESSAGES/django.mo +0 -0
  10. umap/locale/es/LC_MESSAGES/django.po +82 -54
  11. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  12. umap/locale/fr/LC_MESSAGES/django.po +84 -56
  13. umap/locale/hu/LC_MESSAGES/django.mo +0 -0
  14. umap/locale/hu/LC_MESSAGES/django.po +108 -80
  15. umap/locale/it/LC_MESSAGES/django.mo +0 -0
  16. umap/locale/it/LC_MESSAGES/django.po +82 -54
  17. umap/locale/ms/LC_MESSAGES/django.mo +0 -0
  18. umap/locale/ms/LC_MESSAGES/django.po +82 -54
  19. umap/locale/pl/LC_MESSAGES/django.mo +0 -0
  20. umap/locale/pl/LC_MESSAGES/django.po +82 -54
  21. umap/locale/sv/LC_MESSAGES/django.mo +0 -0
  22. umap/locale/sv/LC_MESSAGES/django.po +82 -54
  23. umap/locale/zh_TW/LC_MESSAGES/django.mo +0 -0
  24. umap/locale/zh_TW/LC_MESSAGES/django.po +86 -58
  25. umap/models.py +29 -0
  26. umap/static/umap/base.css +33 -5
  27. umap/static/umap/content.css +41 -2
  28. umap/static/umap/img/16.svg +12 -2
  29. umap/static/umap/img/source/16.svg +165 -820
  30. umap/static/umap/js/umap.browser.js +9 -3
  31. umap/static/umap/js/umap.controls.js +60 -222
  32. umap/static/umap/js/umap.core.js +66 -30
  33. umap/static/umap/js/umap.forms.js +18 -31
  34. umap/static/umap/js/umap.icon.js +44 -21
  35. umap/static/umap/js/umap.js +11 -27
  36. umap/static/umap/js/umap.layer.js +68 -25
  37. umap/static/umap/js/umap.popup.js +87 -0
  38. umap/static/umap/js/umap.share.js +254 -0
  39. umap/static/umap/js/umap.ui.js +4 -2
  40. umap/static/umap/locale/am_ET.js +19 -9
  41. umap/static/umap/locale/am_ET.json +19 -9
  42. umap/static/umap/locale/ar.js +19 -9
  43. umap/static/umap/locale/ar.json +19 -9
  44. umap/static/umap/locale/ast.js +19 -9
  45. umap/static/umap/locale/ast.json +19 -9
  46. umap/static/umap/locale/bg.js +19 -9
  47. umap/static/umap/locale/bg.json +19 -9
  48. umap/static/umap/locale/br.js +22 -12
  49. umap/static/umap/locale/br.json +22 -12
  50. umap/static/umap/locale/ca.js +19 -9
  51. umap/static/umap/locale/ca.json +19 -9
  52. umap/static/umap/locale/cs_CZ.js +19 -9
  53. umap/static/umap/locale/cs_CZ.json +19 -9
  54. umap/static/umap/locale/da.js +19 -9
  55. umap/static/umap/locale/da.json +19 -9
  56. umap/static/umap/locale/de.js +30 -20
  57. umap/static/umap/locale/de.json +30 -20
  58. umap/static/umap/locale/el.js +19 -9
  59. umap/static/umap/locale/el.json +19 -9
  60. umap/static/umap/locale/en.js +19 -9
  61. umap/static/umap/locale/en.json +19 -9
  62. umap/static/umap/locale/en_US.json +19 -9
  63. umap/static/umap/locale/es.js +19 -9
  64. umap/static/umap/locale/es.json +19 -9
  65. umap/static/umap/locale/et.js +19 -9
  66. umap/static/umap/locale/et.json +19 -9
  67. umap/static/umap/locale/fa_IR.js +19 -9
  68. umap/static/umap/locale/fa_IR.json +19 -9
  69. umap/static/umap/locale/fi.js +19 -9
  70. umap/static/umap/locale/fi.json +19 -9
  71. umap/static/umap/locale/fr.js +21 -11
  72. umap/static/umap/locale/fr.json +21 -11
  73. umap/static/umap/locale/gl.js +19 -9
  74. umap/static/umap/locale/gl.json +19 -9
  75. umap/static/umap/locale/he.js +19 -9
  76. umap/static/umap/locale/he.json +19 -9
  77. umap/static/umap/locale/hr.js +19 -9
  78. umap/static/umap/locale/hr.json +19 -9
  79. umap/static/umap/locale/hu.js +19 -9
  80. umap/static/umap/locale/hu.json +19 -9
  81. umap/static/umap/locale/id.js +19 -9
  82. umap/static/umap/locale/id.json +19 -9
  83. umap/static/umap/locale/is.js +19 -9
  84. umap/static/umap/locale/is.json +19 -9
  85. umap/static/umap/locale/it.js +19 -9
  86. umap/static/umap/locale/it.json +19 -9
  87. umap/static/umap/locale/ja.js +19 -9
  88. umap/static/umap/locale/ja.json +19 -9
  89. umap/static/umap/locale/ko.js +19 -9
  90. umap/static/umap/locale/ko.json +19 -9
  91. umap/static/umap/locale/lt.js +19 -9
  92. umap/static/umap/locale/lt.json +19 -9
  93. umap/static/umap/locale/ms.js +19 -9
  94. umap/static/umap/locale/ms.json +19 -9
  95. umap/static/umap/locale/nl.js +20 -10
  96. umap/static/umap/locale/nl.json +20 -10
  97. umap/static/umap/locale/no.js +19 -9
  98. umap/static/umap/locale/no.json +19 -9
  99. umap/static/umap/locale/pl.js +19 -9
  100. umap/static/umap/locale/pl.json +19 -9
  101. umap/static/umap/locale/pl_PL.json +19 -9
  102. umap/static/umap/locale/pt.js +19 -9
  103. umap/static/umap/locale/pt.json +19 -9
  104. umap/static/umap/locale/pt_BR.js +19 -9
  105. umap/static/umap/locale/pt_BR.json +19 -9
  106. umap/static/umap/locale/pt_PT.js +19 -9
  107. umap/static/umap/locale/pt_PT.json +19 -9
  108. umap/static/umap/locale/ro.js +19 -9
  109. umap/static/umap/locale/ro.json +19 -9
  110. umap/static/umap/locale/ru.js +19 -9
  111. umap/static/umap/locale/ru.json +19 -9
  112. umap/static/umap/locale/sk_SK.js +19 -9
  113. umap/static/umap/locale/sk_SK.json +19 -9
  114. umap/static/umap/locale/sl.js +19 -9
  115. umap/static/umap/locale/sl.json +19 -9
  116. umap/static/umap/locale/sr.js +19 -9
  117. umap/static/umap/locale/sr.json +19 -9
  118. umap/static/umap/locale/sv.js +19 -9
  119. umap/static/umap/locale/sv.json +19 -9
  120. umap/static/umap/locale/th_TH.js +19 -9
  121. umap/static/umap/locale/th_TH.json +19 -9
  122. umap/static/umap/locale/tr.js +19 -9
  123. umap/static/umap/locale/tr.json +19 -9
  124. umap/static/umap/locale/uk_UA.js +19 -9
  125. umap/static/umap/locale/uk_UA.json +19 -9
  126. umap/static/umap/locale/vi.js +19 -9
  127. umap/static/umap/locale/vi.json +19 -9
  128. umap/static/umap/locale/vi_VN.json +19 -9
  129. umap/static/umap/locale/zh.js +19 -9
  130. umap/static/umap/locale/zh.json +19 -9
  131. umap/static/umap/locale/zh_CN.json +19 -9
  132. umap/static/umap/locale/zh_TW.Big5.json +19 -9
  133. umap/static/umap/locale/zh_TW.js +55 -45
  134. umap/static/umap/locale/zh_TW.json +55 -45
  135. umap/static/umap/map.css +77 -10
  136. umap/static/umap/test/Map.Export.js +3 -3
  137. umap/static/umap/test/Polygon.js +2 -2
  138. umap/static/umap/test/Polyline.js +2 -0
  139. umap/static/umap/test/index.html +1 -0
  140. umap/static/umap/vendors/leaflet/leaflet-src.esm.js +7055 -7054
  141. umap/static/umap/vendors/leaflet/leaflet-src.js +7144 -7144
  142. umap/static/umap/vendors/toolbar/leaflet.toolbar-src.js +1 -1
  143. umap/templates/auth/user_form.html +1 -1
  144. umap/templates/umap/content.html +1 -1
  145. umap/templates/umap/js.html +1 -0
  146. umap/templates/umap/map_list.html +1 -1
  147. umap/templates/umap/map_table.html +67 -35
  148. umap/templates/umap/user_dashboard.html +23 -1
  149. umap/templatetags/umap_tags.py +7 -27
  150. umap/tests/integration/test_edit_datalayer.py +45 -0
  151. umap/tests/integration/test_export_map.py +2 -3
  152. umap/tests/integration/test_facets_browser.py +86 -0
  153. umap/utils.py +17 -0
  154. umap/views.py +7 -18
  155. {umap_project-1.12.1.dist-info → umap_project-1.13.0.dist-info}/METADATA +4 -11
  156. {umap_project-1.12.1.dist-info → umap_project-1.13.0.dist-info}/RECORD +159 -156
  157. {umap_project-1.12.1.dist-info → umap_project-1.13.0.dist-info}/WHEEL +0 -0
  158. {umap_project-1.12.1.dist-info → umap_project-1.13.0.dist-info}/entry_points.txt +0 -0
  159. {umap_project-1.12.1.dist-info → umap_project-1.13.0.dist-info}/licenses/LICENSE +0 -0
@@ -67,15 +67,8 @@ L.U.Icon.Default = L.U.Icon.extend({
67
67
 
68
68
  onAdd: function () {
69
69
  const src = this._getIconUrl('icon')
70
- // Decide whether to switch svg to white or not, but do it
71
- // only for internal SVG, as invert could do weird things
72
- if (src.startsWith('/') && src.endsWith('.svg')) {
73
- const bgcolor = this._getColor()
74
- // Must be called after icon container is added to the DOM
75
- if (L.DomUtil.contrastedColor(this.elements.container, bgcolor)) {
76
- this.elements.img.style.filter = 'invert(1)'
77
- }
78
- }
70
+ const bgcolor = this._getColor()
71
+ L.U.Icon.setIconContrast(this.elements.icon, this.elements.container, src, bgcolor)
79
72
  },
80
73
 
81
74
  createIcon: function () {
@@ -89,18 +82,7 @@ L.U.Icon.Default = L.U.Icon.extend({
89
82
  this.elements.arrow = L.DomUtil.create('div', 'icon_arrow', this.elements.main)
90
83
  const src = this._getIconUrl('icon')
91
84
  if (src) {
92
- // An url.
93
- if (
94
- src.startsWith('http') ||
95
- src.startsWith('/') ||
96
- src.startsWith('data:image')
97
- ) {
98
- this.elements.img = L.DomUtil.create('img', null, this.elements.container)
99
- this.elements.img.src = src
100
- } else {
101
- this.elements.span = L.DomUtil.create('span', null, this.elements.container)
102
- this.elements.span.textContent = src
103
- }
85
+ this.elements.icon = L.U.Icon.makeIconElement(src, this.elements.container)
104
86
  }
105
87
  this._setIconStyles(this.elements.main, 'icon')
106
88
  return this.elements.main
@@ -208,3 +190,44 @@ L.U.Icon.Cluster = L.DivIcon.extend({
208
190
  return color || L.DomUtil.TextColorFromBackgroundColor(el, backgroundColor)
209
191
  },
210
192
  })
193
+
194
+ L.U.Icon.isImg = function (src) {
195
+ return L.Util.isPath(src) || L.Util.isRemoteUrl(src) || L.Util.isDataImage(src)
196
+ }
197
+
198
+ L.U.Icon.makeIconElement = function (src, parent) {
199
+ let icon
200
+ if (L.U.Icon.isImg(src)) {
201
+ icon = L.DomUtil.create('img')
202
+ icon.src = src
203
+ } else {
204
+ icon = L.DomUtil.create('span')
205
+ icon.textContent = src
206
+ }
207
+ parent.appendChild(icon)
208
+ return icon
209
+ }
210
+
211
+ L.U.Icon.setIconContrast = function (el, parent, src, bgcolor) {
212
+ /*
213
+ * el: the element we'll adapt the style, it can be an image or text
214
+ * parent: the element we'll consider to decide whether to adapt the style,
215
+ * by looking at its background color
216
+ * src: the raw "icon" value, can be an URL, a path, text, emoticon, etc.
217
+ * bgcolor: the background color, used for caching and in case we cannot guess the
218
+ * parent background color
219
+ */
220
+
221
+ if (L.DomUtil.contrastedColor(parent, bgcolor)) {
222
+ // Decide whether to switch svg to white or not, but do it
223
+ // only for internal SVG, as invert could do weird things
224
+ if (L.Util.isPath(src) && src.endsWith('.svg')) {
225
+ // Must be called after icon container is added to the DOM
226
+ // An image
227
+ el.style.filter = 'invert(1)'
228
+ } else if (!el.src) {
229
+ // Text icon
230
+ el.style.color = 'white'
231
+ }
232
+ }
233
+ }
@@ -29,7 +29,12 @@ L.Map.mergeOptions({
29
29
  name: '',
30
30
  description: '',
31
31
  displayPopupFooter: false,
32
- demoTileInfos: { s: 'a', z: 9, x: 265, y: 181, r: '' },
32
+ // When a TileLayer is in TMS mode, it needs -y instead of y.
33
+ // This is usually handled by the TileLayer instance itself, but
34
+ // we cannot rely on this because of the y is overriden by Leaflet
35
+ // See https://github.com/Leaflet/Leaflet/pull/9201
36
+ // And let's remove this -y when this PR is merged and released.
37
+ demoTileInfos: { s: 'a', z: 9, x: 265, y: 181, '-y': 181, r: '' },
33
38
  licences: [],
34
39
  licence: '',
35
40
  enableMarkerDraw: true,
@@ -254,7 +259,7 @@ L.U.Map.include({
254
259
  }
255
260
  this.initShortcuts()
256
261
  this.onceDataLoaded(function () {
257
- if (L.Util.queryString('share')) this.renderShareBox()
262
+ if (L.Util.queryString('share')) this.share.open()
258
263
  else if (this.options.onLoadPanel === 'databrowser') this.openBrowser()
259
264
  else if (this.options.onLoadPanel === 'caption') this.displayCaption()
260
265
  else if (
@@ -347,6 +352,7 @@ L.U.Map.include({
347
352
  this.browser = new L.U.Browser(this)
348
353
  this.importer = new L.U.Importer(this)
349
354
  this.drop = new L.U.DropControl(this)
355
+ this.share = new L.U.Share(this)
350
356
  this._controls.tilelayers = new L.U.TileLayerControl(this)
351
357
  this._controls.tilelayers.setLayers()
352
358
 
@@ -848,28 +854,6 @@ L.U.Map.include({
848
854
  })
849
855
  },
850
856
 
851
- format: function (mode) {
852
- const type = this.EXPORT_TYPES[mode]
853
- const content = type.formatter(this)
854
- let name = this.options.name || 'data'
855
- name = name.replace(/[^a-z0-9]/gi, '_').toLowerCase()
856
- const filename = name + type.ext
857
- return { content, filetype: type.filetype, filename }
858
- },
859
-
860
- download: function (mode) {
861
- const { content, filetype, filename } = this.format(mode)
862
- const blob = new Blob([content], { type: filetype })
863
- window.URL = window.URL || window.webkitURL
864
- const el = document.createElement('a')
865
- el.download = filename
866
- el.href = window.URL.createObjectURL(blob)
867
- el.style.display = 'none'
868
- document.body.appendChild(el)
869
- el.click()
870
- document.body.removeChild(el)
871
- },
872
-
873
857
  processFileToImport: function (file, layer, type) {
874
858
  type = type || L.Util.detectFileType(file)
875
859
  if (!type) {
@@ -1701,9 +1685,9 @@ L.U.Map.include({
1701
1685
  L.DomUtil.createButton(
1702
1686
  'button umap-download',
1703
1687
  advancedButtons,
1704
- L._('Open download panel'),
1705
- this.renderShareBox,
1706
- this
1688
+ L._('Open share & download panel'),
1689
+ this.share.open,
1690
+ this.share
1707
1691
  )
1708
1692
  },
1709
1693
 
@@ -1,6 +1,16 @@
1
1
  L.U.Layer = {
2
2
  canBrowse: true,
3
3
 
4
+ getType: function () {
5
+ const proto = Object.getPrototypeOf(this)
6
+ return proto.constructor.TYPE
7
+ },
8
+
9
+ getName: function () {
10
+ const proto = Object.getPrototypeOf(this)
11
+ return proto.constructor.NAME
12
+ },
13
+
4
14
  getFeatures: function () {
5
15
  return this._layers
6
16
  },
@@ -9,7 +19,7 @@ L.U.Layer = {
9
19
  return []
10
20
  },
11
21
 
12
- postUpdate: function () {},
22
+ onEdit: function () {},
13
23
 
14
24
  hasDataVisible: function () {
15
25
  return !!Object.keys(this._layers).length
@@ -17,7 +27,10 @@ L.U.Layer = {
17
27
  }
18
28
 
19
29
  L.U.Layer.Default = L.FeatureGroup.extend({
20
- _type: 'Default',
30
+ statics: {
31
+ NAME: L._('Default'),
32
+ TYPE: 'Default',
33
+ },
21
34
  includes: [L.U.Layer],
22
35
 
23
36
  initialize: function (datalayer) {
@@ -39,7 +52,10 @@ L.U.MarkerCluster = L.MarkerCluster.extend({
39
52
  })
40
53
 
41
54
  L.U.Layer.Cluster = L.MarkerClusterGroup.extend({
42
- _type: 'Cluster',
55
+ statics: {
56
+ NAME: L._('Clustered'),
57
+ TYPE: 'Cluster',
58
+ },
43
59
  includes: [L.U.Layer],
44
60
 
45
61
  initialize: function (datalayer) {
@@ -104,20 +120,23 @@ L.U.Layer.Cluster = L.MarkerClusterGroup.extend({
104
120
  ]
105
121
  },
106
122
 
107
- postUpdate: function (e) {
108
- if (e.helper.field === 'options.cluster.radius') {
123
+ onEdit: function (field, builder) {
124
+ if (field === 'options.cluster.radius') {
109
125
  // No way to reset radius of an already instanciated MarkerClusterGroup...
110
126
  this.datalayer.resetLayer(true)
111
127
  return
112
128
  }
113
- if (e.helper.field === 'options.color') {
129
+ if (field === 'options.color') {
114
130
  this.options.polygonOptions.color = this.datalayer.getColor()
115
131
  }
116
132
  },
117
133
  })
118
134
 
119
135
  L.U.Layer.Choropleth = L.FeatureGroup.extend({
120
- _type: 'Choropleth',
136
+ statics: {
137
+ NAME: L._('Choropleth'),
138
+ TYPE: 'Choropleth',
139
+ },
121
140
  includes: [L.U.Layer],
122
141
  canBrowse: true,
123
142
  // Have defaults that better suit the choropleth mode.
@@ -234,14 +253,17 @@ L.U.Layer.Choropleth = L.FeatureGroup.extend({
234
253
  L.FeatureGroup.prototype.onAdd.call(this, map)
235
254
  },
236
255
 
237
- postUpdate: function (e) {
238
- if (e.helper.field === 'options.choropleth.breaks') {
256
+ onEdit: function (field, builder) {
257
+ // If user touches the breaks, then force manual mode
258
+ if (field === 'options.choropleth.breaks') {
239
259
  this.datalayer.options.choropleth.mode = 'manual'
240
- e.helper.builder.helpers['options.choropleth.mode'].fetch()
260
+ builder.helpers['options.choropleth.mode'].fetch()
241
261
  }
242
262
  this.computeBreaks()
243
- if (e.helper.field !== 'options.choropleth.breaks') {
244
- e.helper.builder.helpers['options.choropleth.breaks'].fetch()
263
+ // If user changes the mode or the number of classes,
264
+ // then update the breaks input value
265
+ if (field === 'options.choropleth.mode' || field === 'options.choropleth.classes') {
266
+ builder.helpers['options.choropleth.breaks'].fetch()
245
267
  }
246
268
  },
247
269
 
@@ -317,7 +339,10 @@ L.U.Layer.Choropleth = L.FeatureGroup.extend({
317
339
  })
318
340
 
319
341
  L.U.Layer.Heat = L.HeatLayer.extend({
320
- _type: 'Heat',
342
+ statics: {
343
+ NAME: L._('Heatmap'),
344
+ TYPE: 'Heat',
345
+ },
321
346
  includes: [L.U.Layer],
322
347
  canBrowse: false,
323
348
 
@@ -382,12 +407,12 @@ L.U.Layer.Heat = L.HeatLayer.extend({
382
407
  ]
383
408
  },
384
409
 
385
- postUpdate: function (e) {
386
- if (e.helper.field === 'options.heat.intensityProperty') {
410
+ onEdit: function (field, builder) {
411
+ if (field === 'options.heat.intensityProperty') {
387
412
  this.datalayer.resetLayer(true) // We need to repopulate the latlngs
388
413
  return
389
414
  }
390
- if (e.helper.field === 'options.heat.radius') {
415
+ if (field === 'options.heat.radius') {
391
416
  this.options.radius = this.datalayer.options.heat.radius
392
417
  }
393
418
  this._updateOptions()
@@ -612,7 +637,7 @@ L.U.DataLayer = L.Evented.extend({
612
637
  // Only reset if type is defined (undefined is the default) and different from current type
613
638
  if (
614
639
  this.layer &&
615
- (!this.options.type || this.options.type === this.layer._type) &&
640
+ (!this.options.type || this.options.type === this.layer.getType()) &&
616
641
  !force
617
642
  ) {
618
643
  return
@@ -1185,6 +1210,28 @@ L.U.DataLayer = L.Evented.extend({
1185
1210
  })
1186
1211
  container.appendChild(builder.build())
1187
1212
 
1213
+ const redrawCallback = function (e) {
1214
+ const field = e.helper.field,
1215
+ builder = e.helper.builder
1216
+ this.hide()
1217
+ this.layer.onEdit(field, builder)
1218
+ this.show()
1219
+ }
1220
+
1221
+ const layerOptions = this.layer.getEditableOptions()
1222
+
1223
+ if (layerOptions.length) {
1224
+ builder = new L.U.FormBuilder(this, layerOptions, {
1225
+ id: 'datalayer-layer-properties',
1226
+ callback: redrawCallback,
1227
+ })
1228
+ const layerProperties = L.DomUtil.createFieldset(
1229
+ container,
1230
+ `${this.layer.getName()}: ${L._('settings')}`
1231
+ )
1232
+ layerProperties.appendChild(builder.build())
1233
+ }
1234
+
1188
1235
  let shapeOptions = [
1189
1236
  'options.color',
1190
1237
  'options.iconClass',
@@ -1198,12 +1245,6 @@ L.U.DataLayer = L.Evented.extend({
1198
1245
  'options.fillOpacity',
1199
1246
  ]
1200
1247
 
1201
- const redrawCallback = function (e) {
1202
- this.hide()
1203
- this.layer.postUpdate(e)
1204
- this.show()
1205
- }
1206
-
1207
1248
  builder = new L.U.FormBuilder(this, shapeOptions, {
1208
1249
  id: 'datalayer-advanced-properties',
1209
1250
  callback: redrawCallback,
@@ -1220,8 +1261,6 @@ L.U.DataLayer = L.Evented.extend({
1220
1261
  'options.labelKey',
1221
1262
  ]
1222
1263
 
1223
- optionsFields = optionsFields.concat(this.layer.getEditableOptions())
1224
-
1225
1264
  builder = new L.U.FormBuilder(this, optionsFields, {
1226
1265
  id: 'datalayer-advanced-properties',
1227
1266
  callback: redrawCallback,
@@ -1443,6 +1482,10 @@ L.U.DataLayer = L.Evented.extend({
1443
1482
  return !!this.options.browsable && this.canBrowse() && this.isVisible()
1444
1483
  },
1445
1484
 
1485
+ count: function () {
1486
+ return this._index.length
1487
+ },
1488
+
1446
1489
  hasData: function () {
1447
1490
  return !!this._index.length
1448
1491
  },
@@ -251,3 +251,90 @@ L.U.PopupTemplate.GeoRSSLink = L.U.PopupTemplate.Default.extend({
251
251
  return a
252
252
  },
253
253
  })
254
+
255
+ L.U.PopupTemplate.OSM = L.U.PopupTemplate.Default.extend({
256
+ options: {
257
+ className: 'umap-openstreetmap',
258
+ },
259
+
260
+ getName: function () {
261
+ const props = this.feature.properties
262
+ if (L.locale && props[`name:${L.locale}`]) return props[`name:${L.locale}`]
263
+ return props.name
264
+ },
265
+
266
+ renderBody: function () {
267
+ const props = this.feature.properties
268
+ const container = L.DomUtil.add('div')
269
+ const title = L.DomUtil.add('h3', 'popup-title', container)
270
+ const color = this.feature.getDynamicOption('color')
271
+ title.style.backgroundColor = color
272
+ const iconUrl = this.feature.getDynamicOption('iconUrl')
273
+ let icon = L.U.Icon.makeIconElement(iconUrl, title)
274
+ L.DomUtil.addClass(icon, 'icon')
275
+ L.U.Icon.setIconContrast(icon, title, iconUrl, color)
276
+ if (L.DomUtil.contrastedColor(title, color)) title.style.color = 'white'
277
+ L.DomUtil.add('span', '', title, this.getName())
278
+ const street = props['addr:street']
279
+ if (street) {
280
+ const row = L.DomUtil.add('address', 'address', container)
281
+ const number = props['addr:housenumber']
282
+ if (number) {
283
+ // Poor way to deal with international forms of writting addresses
284
+ L.DomUtil.add('span', '', row, `${L._('No.')}: ${number}`)
285
+ L.DomUtil.add('span', '', row, `${L._('Street')}: ${street}`)
286
+ } else {
287
+ L.DomUtil.add('span', '', row, street)
288
+ }
289
+ }
290
+ if (props.website) {
291
+ L.DomUtil.element(
292
+ 'a',
293
+ { href: props.website, textContent: props.website },
294
+ container
295
+ )
296
+ }
297
+ const phone = props.phone || props['contact:phone']
298
+ if (phone) {
299
+ L.DomUtil.add(
300
+ 'div',
301
+ '',
302
+ container,
303
+ L.DomUtil.element('a', { href: `tel:${phone}`, textContent: phone })
304
+ )
305
+ }
306
+ if (props.mobile) {
307
+ L.DomUtil.add(
308
+ 'div',
309
+ '',
310
+ container,
311
+ L.DomUtil.element('a', {
312
+ href: `tel:${props.mobile}`,
313
+ textContent: props.mobile,
314
+ })
315
+ )
316
+ }
317
+ const email = props.email || props['contact:email']
318
+ if (email) {
319
+ L.DomUtil.add(
320
+ 'div',
321
+ '',
322
+ container,
323
+ L.DomUtil.element('a', { href: `mailto:${email}`, textContent: email })
324
+ )
325
+ }
326
+ const id = props['@id']
327
+ if (id) {
328
+ L.DomUtil.add(
329
+ 'div',
330
+ 'osm-link',
331
+ container,
332
+ L.DomUtil.element('a', {
333
+ href: `https://www.openstreetmap.org/${id}`,
334
+ textContent: L._('See on OpenStreetMap'),
335
+ })
336
+ )
337
+ }
338
+ return container
339
+ },
340
+ })
@@ -0,0 +1,254 @@
1
+ L.U.Share = L.Class.extend({
2
+ EXPORT_TYPES: {
3
+ geojson: {
4
+ formatter: function (map) {
5
+ return JSON.stringify(map.toGeoJSON(), null, 2)
6
+ },
7
+ ext: '.geojson',
8
+ filetype: 'application/json',
9
+ },
10
+ gpx: {
11
+ formatter: function (map) {
12
+ return togpx(map.toGeoJSON())
13
+ },
14
+ ext: '.gpx',
15
+ filetype: 'application/gpx+xml',
16
+ },
17
+ kml: {
18
+ formatter: function (map) {
19
+ return tokml(map.toGeoJSON())
20
+ },
21
+ ext: '.kml',
22
+ filetype: 'application/vnd.google-earth.kml+xml',
23
+ },
24
+ csv: {
25
+ formatter: function (map) {
26
+ const table = []
27
+ map.eachFeature((feature) => {
28
+ const row = feature.toGeoJSON()['properties'],
29
+ center = feature.getCenter()
30
+ delete row['_umap_options']
31
+ row['Latitude'] = center.lat
32
+ row['Longitude'] = center.lng
33
+ table.push(row)
34
+ })
35
+ return csv2geojson.dsv.csvFormat(table)
36
+ },
37
+ ext: '.csv',
38
+ filetype: 'text/csv',
39
+ },
40
+ },
41
+
42
+ initialize: function (map) {
43
+ this.map = map
44
+ },
45
+
46
+ build: function () {
47
+ this.container = L.DomUtil.create('div', 'umap-share')
48
+ this.title = L.DomUtil.create('h3', '', this.container)
49
+ this.title.textContent = L._('Share and download')
50
+
51
+ L.DomUtil.createCopiableInput(
52
+ this.container,
53
+ L._('Link to view the map'),
54
+ window.location.protocol + L.Util.getBaseUrl()
55
+ )
56
+
57
+ if (this.map.options.shortUrl) {
58
+ L.DomUtil.createCopiableInput(
59
+ this.container,
60
+ L._('Short link'),
61
+ this.map.options.shortUrl
62
+ )
63
+ }
64
+
65
+ L.DomUtil.create('hr', '', this.container)
66
+
67
+ L.DomUtil.add('h4', '', this.container, L._('Download'))
68
+ L.DomUtil.add('small', 'label', this.container, L._("Only visible layers' data"))
69
+ for (const key in this.EXPORT_TYPES) {
70
+ if (this.EXPORT_TYPES.hasOwnProperty(key)) {
71
+ L.DomUtil.createButton(
72
+ 'download-file',
73
+ this.container,
74
+ this.EXPORT_TYPES[key].name || key,
75
+ () => this.download(key),
76
+ this
77
+ )
78
+ }
79
+ }
80
+ L.DomUtil.create('div', 'vspace', this.container)
81
+ L.DomUtil.add(
82
+ 'small',
83
+ 'label',
84
+ this.container,
85
+ L._('All data and settings of the map')
86
+ )
87
+ const downloadUrl = L.Util.template(this.map.options.urls.map_download, {
88
+ map_id: this.map.options.umap_id,
89
+ })
90
+ const link = L.DomUtil.createLink(
91
+ 'download-backup',
92
+ this.container,
93
+ L._('full backup'),
94
+ downloadUrl
95
+ )
96
+ let name = this.map.options.name || 'data'
97
+ name = name.replace(/[^a-z0-9]/gi, '_').toLowerCase()
98
+ link.setAttribute('download', `${name}.umap`)
99
+ L.DomUtil.create('hr', '', this.container)
100
+
101
+ const embedTitle = L.DomUtil.add('h4', '', this.container, L._('Embed the map'))
102
+ const iframe = L.DomUtil.create('textarea', 'umap-share-iframe', this.container)
103
+ const urlTitle = L.DomUtil.add('h4', '', this.container, L._('Direct link'))
104
+ const exportUrl = L.DomUtil.createCopiableInput(
105
+ this.container,
106
+ L._('Share this link to open a customized map view'),
107
+ ''
108
+ )
109
+
110
+ exportUrl.type = 'text'
111
+ const UIFields = [
112
+ ['dimensions.width', { handler: 'Input', label: L._('width') }],
113
+ ['dimensions.height', { handler: 'Input', label: L._('height') }],
114
+ [
115
+ 'options.includeFullScreenLink',
116
+ { handler: 'Switch', label: L._('Include full screen link?') },
117
+ ],
118
+ [
119
+ 'options.currentView',
120
+ { handler: 'Switch', label: L._('Current view instead of default map view?') },
121
+ ],
122
+ [
123
+ 'options.keepCurrentDatalayers',
124
+ { handler: 'Switch', label: L._('Keep current visible layers') },
125
+ ],
126
+ [
127
+ 'options.viewCurrentFeature',
128
+ { handler: 'Switch', label: L._('Open current feature on load') },
129
+ ],
130
+ 'queryString.moreControl',
131
+ 'queryString.scrollWheelZoom',
132
+ 'queryString.miniMap',
133
+ 'queryString.scaleControl',
134
+ 'queryString.onLoadPanel',
135
+ 'queryString.captionBar',
136
+ 'queryString.captionMenus',
137
+ ]
138
+ for (let i = 0; i < this.map.HIDDABLE_CONTROLS.length; i++) {
139
+ UIFields.push(`queryString.${this.map.HIDDABLE_CONTROLS[i]}Control`)
140
+ }
141
+ const iframeExporter = new L.U.IframeExporter(this.map)
142
+ const buildIframeCode = () => {
143
+ iframe.innerHTML = iframeExporter.build()
144
+ exportUrl.value = window.location.protocol + iframeExporter.buildUrl()
145
+ }
146
+ buildIframeCode()
147
+ const builder = new L.U.FormBuilder(iframeExporter, UIFields, {
148
+ callback: buildIframeCode,
149
+ })
150
+ const iframeOptions = L.DomUtil.createFieldset(
151
+ this.container,
152
+ L._('Embed and link options')
153
+ )
154
+ iframeOptions.appendChild(builder.build())
155
+ },
156
+
157
+ open: function () {
158
+ if (!this.container) this.build()
159
+ this.map.ui.openPanel({ data: { html: this.container } })
160
+ },
161
+
162
+ format: function (mode) {
163
+ const type = this.EXPORT_TYPES[mode]
164
+ const content = type.formatter(this.map)
165
+ let name = this.map.options.name || 'data'
166
+ name = name.replace(/[^a-z0-9]/gi, '_').toLowerCase()
167
+ const filename = name + type.ext
168
+ return { content, filetype: type.filetype, filename }
169
+ },
170
+
171
+ download: function (mode) {
172
+ const { content, filetype, filename } = this.format(mode)
173
+ const blob = new Blob([content], { type: filetype })
174
+ window.URL = window.URL || window.webkitURL
175
+ const el = document.createElement('a')
176
+ el.download = filename
177
+ el.href = window.URL.createObjectURL(blob)
178
+ el.style.display = 'none'
179
+ document.body.appendChild(el)
180
+ el.click()
181
+ document.body.removeChild(el)
182
+ },
183
+ })
184
+
185
+ L.U.IframeExporter = L.Evented.extend({
186
+ options: {
187
+ includeFullScreenLink: true,
188
+ currentView: false,
189
+ keepCurrentDatalayers: false,
190
+ viewCurrentFeature: false,
191
+ },
192
+
193
+ queryString: {
194
+ scaleControl: false,
195
+ miniMap: false,
196
+ scrollWheelZoom: false,
197
+ zoomControl: true,
198
+ editMode: 'disabled',
199
+ moreControl: true,
200
+ searchControl: null,
201
+ tilelayersControl: null,
202
+ embedControl: null,
203
+ datalayersControl: true,
204
+ onLoadPanel: 'none',
205
+ captionBar: false,
206
+ captionMenus: true,
207
+ },
208
+
209
+ dimensions: {
210
+ width: '100%',
211
+ height: '300px',
212
+ },
213
+
214
+ initialize: function (map) {
215
+ this.map = map
216
+ this.baseUrl = L.Util.getBaseUrl()
217
+ // Use map default, not generic default
218
+ this.queryString.onLoadPanel = this.map.options.onLoadPanel
219
+ },
220
+
221
+ getMap: function () {
222
+ return this.map
223
+ },
224
+
225
+ buildUrl: function (options) {
226
+ const datalayers = []
227
+ if (this.options.viewCurrentFeature && this.map.currentFeature) {
228
+ this.queryString.feature = this.map.currentFeature.getSlug()
229
+ }
230
+ if (this.options.keepCurrentDatalayers) {
231
+ this.map.eachDataLayer((datalayer) => {
232
+ if (datalayer.isVisible() && datalayer.umap_id) {
233
+ datalayers.push(datalayer.umap_id)
234
+ }
235
+ })
236
+ this.queryString.datalayers = datalayers.join(',')
237
+ } else {
238
+ delete this.queryString.datalayers
239
+ }
240
+ const currentView = this.options.currentView ? window.location.hash : ''
241
+ const queryString = L.extend({}, this.queryString, options)
242
+ return `${this.baseUrl}?${L.Util.buildQueryString(queryString)}${currentView}`
243
+ },
244
+
245
+ build: function () {
246
+ const iframeUrl = this.buildUrl()
247
+ let code = `<iframe width="${this.dimensions.width}" height="${this.dimensions.height}" frameborder="0" allowfullscreen allow="geolocation" src="${iframeUrl}"></iframe>`
248
+ if (this.options.includeFullScreenLink) {
249
+ const fullUrl = this.buildUrl({ scrollWheelZoom: true })
250
+ code += `<p><a href="${fullUrl}">${L._('See full screen')}</a></p>`
251
+ }
252
+ return code
253
+ },
254
+ })