umap-project 1.14.0a1__py3-none-any.whl → 1.14.0a2__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 (182) hide show
  1. umap/__init__.py +1 -1
  2. umap/static/.gitignore +0 -0
  3. umap/static/umap/base.css +994 -0
  4. umap/static/umap/bitbucket.png +0 -0
  5. umap/static/umap/content.css +395 -0
  6. umap/static/umap/favicons/apple-touch-icon.png +0 -0
  7. umap/static/umap/favicons/favicon.ico +0 -0
  8. umap/static/umap/favicons/icon-192.png +0 -0
  9. umap/static/umap/favicons/icon-512.png +0 -0
  10. umap/static/umap/favicons/icon.svg +5 -0
  11. umap/static/umap/font/FiraSans-Light.woff +0 -0
  12. umap/static/umap/font/FiraSans-Light.woff2 +0 -0
  13. umap/static/umap/font/FiraSans-LightItalic.woff +0 -0
  14. umap/static/umap/font/FiraSans-LightItalic.woff2 +0 -0
  15. umap/static/umap/font/FiraSans-SemiBold.woff +0 -0
  16. umap/static/umap/font/FiraSans-SemiBold.woff2 +0 -0
  17. umap/static/umap/font.css +33 -0
  18. umap/static/umap/github.png +0 -0
  19. umap/static/umap/img/16-white.svg +190 -0
  20. umap/static/umap/img/16.svg +182 -0
  21. umap/static/umap/img/24-white.svg +62 -0
  22. umap/static/umap/img/24.svg +90 -0
  23. umap/static/umap/img/edit.svg +7 -0
  24. umap/static/umap/img/icon-bg.png +0 -0
  25. umap/static/umap/img/logo.svg +4 -0
  26. umap/static/umap/img/logo_filigree.png +0 -0
  27. umap/static/umap/img/logo_small.svg +14 -0
  28. umap/static/umap/img/marker.png +0 -0
  29. umap/static/umap/img/opensource.svg +7 -0
  30. umap/static/umap/img/osm.svg +7 -0
  31. umap/static/umap/img/search.gif +0 -0
  32. umap/static/umap/img/source/16-white.svg +980 -0
  33. umap/static/umap/img/source/16.svg +201 -0
  34. umap/static/umap/img/source/24-white.svg +83 -0
  35. umap/static/umap/img/source/24.svg +110 -0
  36. umap/static/umap/js/components/fragment.js +13 -0
  37. umap/static/umap/js/modules/global.js +8 -0
  38. umap/static/umap/js/modules/urls.js +29 -0
  39. umap/static/umap/js/umap.autocomplete.js +336 -0
  40. umap/static/umap/js/umap.browser.js +148 -0
  41. umap/static/umap/js/umap.controls.js +1542 -0
  42. umap/static/umap/js/umap.core.js +851 -0
  43. umap/static/umap/js/umap.datalayer.permissions.js +72 -0
  44. umap/static/umap/js/umap.features.js +1216 -0
  45. umap/static/umap/js/umap.forms.js +1267 -0
  46. umap/static/umap/js/umap.icon.js +234 -0
  47. umap/static/umap/js/umap.importer.js +166 -0
  48. umap/static/umap/js/umap.js +2010 -0
  49. umap/static/umap/js/umap.layer.js +1636 -0
  50. umap/static/umap/js/umap.permissions.js +212 -0
  51. umap/static/umap/js/umap.popup.js +340 -0
  52. umap/static/umap/js/umap.share.js +254 -0
  53. umap/static/umap/js/umap.slideshow.js +165 -0
  54. umap/static/umap/js/umap.tableeditor.js +120 -0
  55. umap/static/umap/js/umap.ui.js +240 -0
  56. umap/static/umap/js/umap.xhr.js +304 -0
  57. umap/static/umap/locale/am_ET.js +447 -0
  58. umap/static/umap/locale/am_ET.json +445 -0
  59. umap/static/umap/locale/ar.js +447 -0
  60. umap/static/umap/locale/ar.json +445 -0
  61. umap/static/umap/locale/ast.js +447 -0
  62. umap/static/umap/locale/ast.json +445 -0
  63. umap/static/umap/locale/bg.js +447 -0
  64. umap/static/umap/locale/bg.json +445 -0
  65. umap/static/umap/locale/br.js +447 -0
  66. umap/static/umap/locale/br.json +445 -0
  67. umap/static/umap/locale/ca.js +447 -0
  68. umap/static/umap/locale/ca.json +445 -0
  69. umap/static/umap/locale/cs_CZ.js +447 -0
  70. umap/static/umap/locale/cs_CZ.json +445 -0
  71. umap/static/umap/locale/da.js +447 -0
  72. umap/static/umap/locale/da.json +445 -0
  73. umap/static/umap/locale/de.js +447 -0
  74. umap/static/umap/locale/de.json +445 -0
  75. umap/static/umap/locale/el.js +447 -0
  76. umap/static/umap/locale/el.json +445 -0
  77. umap/static/umap/locale/en.js +447 -0
  78. umap/static/umap/locale/en.json +445 -0
  79. umap/static/umap/locale/en_US.json +445 -0
  80. umap/static/umap/locale/es.js +447 -0
  81. umap/static/umap/locale/es.json +445 -0
  82. umap/static/umap/locale/et.js +447 -0
  83. umap/static/umap/locale/et.json +445 -0
  84. umap/static/umap/locale/eu.js +413 -0
  85. umap/static/umap/locale/eu.json +411 -0
  86. umap/static/umap/locale/fa_IR.js +447 -0
  87. umap/static/umap/locale/fa_IR.json +445 -0
  88. umap/static/umap/locale/fi.js +447 -0
  89. umap/static/umap/locale/fi.json +445 -0
  90. umap/static/umap/locale/fr.js +447 -0
  91. umap/static/umap/locale/fr.json +445 -0
  92. umap/static/umap/locale/gl.js +447 -0
  93. umap/static/umap/locale/gl.json +445 -0
  94. umap/static/umap/locale/he.js +447 -0
  95. umap/static/umap/locale/he.json +445 -0
  96. umap/static/umap/locale/hr.js +447 -0
  97. umap/static/umap/locale/hr.json +445 -0
  98. umap/static/umap/locale/hu.js +447 -0
  99. umap/static/umap/locale/hu.json +445 -0
  100. umap/static/umap/locale/id.js +447 -0
  101. umap/static/umap/locale/id.json +445 -0
  102. umap/static/umap/locale/is.js +447 -0
  103. umap/static/umap/locale/is.json +445 -0
  104. umap/static/umap/locale/it.js +447 -0
  105. umap/static/umap/locale/it.json +445 -0
  106. umap/static/umap/locale/ja.js +447 -0
  107. umap/static/umap/locale/ja.json +445 -0
  108. umap/static/umap/locale/ko.js +447 -0
  109. umap/static/umap/locale/ko.json +445 -0
  110. umap/static/umap/locale/lt.js +447 -0
  111. umap/static/umap/locale/lt.json +445 -0
  112. umap/static/umap/locale/ms.js +447 -0
  113. umap/static/umap/locale/ms.json +445 -0
  114. umap/static/umap/locale/nl.js +447 -0
  115. umap/static/umap/locale/nl.json +445 -0
  116. umap/static/umap/locale/no.js +447 -0
  117. umap/static/umap/locale/no.json +445 -0
  118. umap/static/umap/locale/pl.js +447 -0
  119. umap/static/umap/locale/pl.json +445 -0
  120. umap/static/umap/locale/pl_PL.json +445 -0
  121. umap/static/umap/locale/pt.js +447 -0
  122. umap/static/umap/locale/pt.json +445 -0
  123. umap/static/umap/locale/pt_BR.js +447 -0
  124. umap/static/umap/locale/pt_BR.json +445 -0
  125. umap/static/umap/locale/pt_PT.js +447 -0
  126. umap/static/umap/locale/pt_PT.json +445 -0
  127. umap/static/umap/locale/ro.js +447 -0
  128. umap/static/umap/locale/ro.json +445 -0
  129. umap/static/umap/locale/ru.js +447 -0
  130. umap/static/umap/locale/ru.json +445 -0
  131. umap/static/umap/locale/si.js +439 -0
  132. umap/static/umap/locale/si.json +437 -0
  133. umap/static/umap/locale/sk_SK.js +447 -0
  134. umap/static/umap/locale/sk_SK.json +445 -0
  135. umap/static/umap/locale/sl.js +447 -0
  136. umap/static/umap/locale/sl.json +445 -0
  137. umap/static/umap/locale/sr.js +447 -0
  138. umap/static/umap/locale/sr.json +445 -0
  139. umap/static/umap/locale/sv.js +447 -0
  140. umap/static/umap/locale/sv.json +445 -0
  141. umap/static/umap/locale/th_TH.js +447 -0
  142. umap/static/umap/locale/th_TH.json +445 -0
  143. umap/static/umap/locale/tr.js +447 -0
  144. umap/static/umap/locale/tr.json +445 -0
  145. umap/static/umap/locale/uk_UA.js +447 -0
  146. umap/static/umap/locale/uk_UA.json +445 -0
  147. umap/static/umap/locale/vi.js +447 -0
  148. umap/static/umap/locale/vi.json +445 -0
  149. umap/static/umap/locale/vi_VN.json +445 -0
  150. umap/static/umap/locale/zh.js +447 -0
  151. umap/static/umap/locale/zh.json +445 -0
  152. umap/static/umap/locale/zh_CN.json +445 -0
  153. umap/static/umap/locale/zh_TW.Big5.json +445 -0
  154. umap/static/umap/locale/zh_TW.js +447 -0
  155. umap/static/umap/locale/zh_TW.json +445 -0
  156. umap/static/umap/map.css +1843 -0
  157. umap/static/umap/nav.css +81 -0
  158. umap/static/umap/openstreetmap.png +0 -0
  159. umap/static/umap/test/.eslintrc +22 -0
  160. umap/static/umap/test/Choropleth.js +243 -0
  161. umap/static/umap/test/Controls.js +100 -0
  162. umap/static/umap/test/DataLayer.js +495 -0
  163. umap/static/umap/test/Feature.js +382 -0
  164. umap/static/umap/test/Map.Export.js +106 -0
  165. umap/static/umap/test/Map.Init.js +46 -0
  166. umap/static/umap/test/Map.js +342 -0
  167. umap/static/umap/test/Marker.js +122 -0
  168. umap/static/umap/test/Permissions.js +74 -0
  169. umap/static/umap/test/Polygon.js +367 -0
  170. umap/static/umap/test/Polyline.js +402 -0
  171. umap/static/umap/test/TableEditor.js +100 -0
  172. umap/static/umap/test/URLs.js +54 -0
  173. umap/static/umap/test/Util.js +549 -0
  174. umap/static/umap/test/_pre.js +460 -0
  175. umap/static/umap/test/index.html +135 -0
  176. umap/static/umap/theme.css +1 -0
  177. umap/static/umap/twitter.png +0 -0
  178. {umap_project-1.14.0a1.dist-info → umap_project-1.14.0a2.dist-info}/METADATA +1 -1
  179. {umap_project-1.14.0a1.dist-info → umap_project-1.14.0a2.dist-info}/RECORD +182 -6
  180. {umap_project-1.14.0a1.dist-info → umap_project-1.14.0a2.dist-info}/WHEEL +0 -0
  181. {umap_project-1.14.0a1.dist-info → umap_project-1.14.0a2.dist-info}/entry_points.txt +0 -0
  182. {umap_project-1.14.0a1.dist-info → umap_project-1.14.0a2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,1636 @@
1
+ L.U.Layer = {
2
+ canBrowse: true,
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
+
14
+ getFeatures: function () {
15
+ return this._layers
16
+ },
17
+
18
+ getEditableOptions: function () {
19
+ return []
20
+ },
21
+
22
+ onEdit: function () {},
23
+
24
+ hasDataVisible: function () {
25
+ return !!Object.keys(this._layers).length
26
+ },
27
+ }
28
+
29
+ L.U.Layer.Default = L.FeatureGroup.extend({
30
+ statics: {
31
+ NAME: L._('Default'),
32
+ TYPE: 'Default',
33
+ },
34
+ includes: [L.U.Layer],
35
+
36
+ initialize: function (datalayer) {
37
+ this.datalayer = datalayer
38
+ L.FeatureGroup.prototype.initialize.call(this)
39
+ },
40
+ })
41
+
42
+ L.U.MarkerCluster = L.MarkerCluster.extend({
43
+ // Custom class so we can call computeTextColor
44
+ // when element is already on the DOM.
45
+
46
+ _initIcon: function () {
47
+ L.MarkerCluster.prototype._initIcon.call(this)
48
+ const div = this._icon.querySelector('div')
49
+ // Compute text color only when icon is added to the DOM.
50
+ div.style.color = this._iconObj.computeTextColor(div)
51
+ },
52
+ })
53
+
54
+ L.U.Layer.Cluster = L.MarkerClusterGroup.extend({
55
+ statics: {
56
+ NAME: L._('Clustered'),
57
+ TYPE: 'Cluster',
58
+ },
59
+ includes: [L.U.Layer],
60
+
61
+ initialize: function (datalayer) {
62
+ this.datalayer = datalayer
63
+ const options = {
64
+ polygonOptions: {
65
+ color: this.datalayer.getColor(),
66
+ },
67
+ iconCreateFunction: function (cluster) {
68
+ return new L.U.Icon.Cluster(datalayer, cluster)
69
+ },
70
+ }
71
+ if (this.datalayer.options.cluster && this.datalayer.options.cluster.radius) {
72
+ options.maxClusterRadius = this.datalayer.options.cluster.radius
73
+ }
74
+ L.MarkerClusterGroup.prototype.initialize.call(this, options)
75
+ this._markerCluster = L.U.MarkerCluster
76
+ this._layers = []
77
+ },
78
+
79
+ onRemove: function (map) {
80
+ // In some situation, the onRemove is called before the layer is really
81
+ // added to the map: basically when combining a defaultView=data + max/minZoom
82
+ // and loading the map at a zoom outside of that zoom range.
83
+ // FIXME: move this upstream (_unbindEvents should accept a map parameter
84
+ // instead of relying on this._map)
85
+ this._map = map
86
+ return L.MarkerClusterGroup.prototype.onRemove.call(this, map)
87
+ },
88
+
89
+ addLayer: function (layer) {
90
+ this._layers.push(layer)
91
+ return L.MarkerClusterGroup.prototype.addLayer.call(this, layer)
92
+ },
93
+
94
+ removeLayer: function (layer) {
95
+ this._layers.splice(this._layers.indexOf(layer), 1)
96
+ return L.MarkerClusterGroup.prototype.removeLayer.call(this, layer)
97
+ },
98
+
99
+ getEditableOptions: function () {
100
+ if (!L.Util.isObject(this.datalayer.options.cluster)) {
101
+ this.datalayer.options.cluster = {}
102
+ }
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
+ },
122
+
123
+ onEdit: function (field, builder) {
124
+ if (field === 'options.cluster.radius') {
125
+ // No way to reset radius of an already instanciated MarkerClusterGroup...
126
+ this.datalayer.resetLayer(true)
127
+ return
128
+ }
129
+ if (field === 'options.color') {
130
+ this.options.polygonOptions.color = this.datalayer.getColor()
131
+ }
132
+ },
133
+ })
134
+
135
+ L.U.Layer.Choropleth = L.FeatureGroup.extend({
136
+ statics: {
137
+ NAME: L._('Choropleth'),
138
+ TYPE: 'Choropleth',
139
+ },
140
+ includes: [L.U.Layer],
141
+ canBrowse: true,
142
+ // Have defaults that better suit the choropleth mode.
143
+ defaults: {
144
+ color: 'white',
145
+ fillColor: 'red',
146
+ fillOpacity: 0.7,
147
+ weight: 2,
148
+ },
149
+ MODES: {
150
+ kmeans: L._('K-means'),
151
+ equidistant: L._('Equidistant'),
152
+ jenks: L._('Jenks-Fisher'),
153
+ quantiles: L._('Quantiles'),
154
+ manual: L._('Manual'),
155
+ },
156
+
157
+ initialize: function (datalayer) {
158
+ this.datalayer = datalayer
159
+ if (!L.Util.isObject(this.datalayer.options.choropleth)) {
160
+ this.datalayer.options.choropleth = {}
161
+ }
162
+ L.FeatureGroup.prototype.initialize.call(
163
+ this,
164
+ [],
165
+ this.datalayer.options.choropleth
166
+ )
167
+ this.datalayer.onceDataLoaded(() => {
168
+ this.redraw()
169
+ this.datalayer.on('datachanged', this.redraw, this)
170
+ })
171
+ },
172
+
173
+ redraw: function () {
174
+ this.computeBreaks()
175
+ if (this._map) this.eachLayer(this._map.addLayer, this._map)
176
+ },
177
+
178
+ _getValue: function (feature) {
179
+ const key = this.datalayer.options.choropleth.property || 'value'
180
+ return +feature.properties[key] // TODO: should we catch values non castable to int ?
181
+ },
182
+
183
+ computeBreaks: function () {
184
+ const values = []
185
+ this.datalayer.eachLayer((layer) => {
186
+ let value = this._getValue(layer)
187
+ if (!isNaN(value)) values.push(value)
188
+ })
189
+ if (!values.length) {
190
+ this.options.breaks = []
191
+ this.options.colors = []
192
+ return
193
+ }
194
+ let mode = this.datalayer.options.choropleth.mode,
195
+ classes = +this.datalayer.options.choropleth.classes || 5,
196
+ breaks
197
+ if (mode === 'manual') {
198
+ const manualBreaks = this.datalayer.options.choropleth.breaks
199
+ if (manualBreaks) {
200
+ breaks = manualBreaks
201
+ .split(',')
202
+ .map((b) => +b)
203
+ .filter((b) => !isNaN(b))
204
+ }
205
+ } else if (mode === 'equidistant') {
206
+ breaks = ss.equalIntervalBreaks(values, classes)
207
+ } else if (mode === 'jenks') {
208
+ breaks = ss.jenks(values, classes)
209
+ } else if (mode === 'quantiles') {
210
+ const quantiles = [...Array(classes)].map((e, i) => i / classes).concat(1)
211
+ breaks = ss.quantile(values, quantiles)
212
+ } else {
213
+ breaks = ss.ckmeans(values, classes).map((cluster) => cluster[0])
214
+ breaks.push(ss.max(values)) // Needed for computing the legend
215
+ }
216
+ this.options.breaks = breaks || []
217
+ this.datalayer.options.choropleth.breaks = this.options.breaks
218
+ .map((b) => +b.toFixed(2))
219
+ .join(',')
220
+ const fillColor = this.datalayer.getOption('fillColor') || this.defaults.fillColor
221
+ let colorScheme = this.datalayer.options.choropleth.brewer
222
+ if (!colorbrewer[colorScheme]) colorScheme = 'Blues'
223
+ this.options.colors = colorbrewer[colorScheme][this.options.breaks.length - 1] || []
224
+ },
225
+
226
+ getColor: function (feature) {
227
+ if (!feature) return // FIXME shold not happen
228
+ const featureValue = this._getValue(feature)
229
+ // Find the bucket/step/limit that this value is less than and give it that color
230
+ for (let i = 1; i < this.options.breaks.length; i++) {
231
+ if (featureValue <= this.options.breaks[i]) {
232
+ return this.options.colors[i - 1]
233
+ }
234
+ }
235
+ },
236
+
237
+ getOption: function (option, feature) {
238
+ if (feature && option === feature.staticOptions.mainColor) {
239
+ return this.getColor(feature)
240
+ }
241
+ },
242
+
243
+ addLayer: function (layer) {
244
+ // Do not add yet the layer to the map
245
+ // wait for datachanged event, so we want compute breaks once
246
+ var id = this.getLayerId(layer)
247
+ this._layers[id] = layer
248
+ return this
249
+ },
250
+
251
+ onAdd: function (map) {
252
+ this.computeBreaks()
253
+ L.FeatureGroup.prototype.onAdd.call(this, map)
254
+ },
255
+
256
+ onEdit: function (field, builder) {
257
+ // If user touches the breaks, then force manual mode
258
+ if (field === 'options.choropleth.breaks') {
259
+ this.datalayer.options.choropleth.mode = 'manual'
260
+ builder.helpers['options.choropleth.mode'].fetch()
261
+ }
262
+ this.computeBreaks()
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()
267
+ }
268
+ },
269
+
270
+ getEditableOptions: function () {
271
+ const brewerSchemes = Object.keys(colorbrewer)
272
+ .filter((k) => k !== 'schemeGroups')
273
+ .sort()
274
+
275
+ return [
276
+ [
277
+ 'options.choropleth.property',
278
+ {
279
+ handler: 'Select',
280
+ selectOptions: this.datalayer._propertiesIndex,
281
+ label: L._('Choropleth property value'),
282
+ },
283
+ ],
284
+ [
285
+ 'options.choropleth.brewer',
286
+ {
287
+ handler: 'Select',
288
+ label: L._('Choropleth color palette'),
289
+ selectOptions: brewerSchemes,
290
+ },
291
+ ],
292
+ [
293
+ 'options.choropleth.classes',
294
+ {
295
+ handler: 'Range',
296
+ min: 3,
297
+ max: 9,
298
+ step: 1,
299
+ label: L._('Choropleth classes'),
300
+ helpText: L._('Number of desired classes (default 5)'),
301
+ },
302
+ ],
303
+ [
304
+ 'options.choropleth.breaks',
305
+ {
306
+ handler: 'BlurInput',
307
+ label: L._('Choropleth breakpoints'),
308
+ helpText: L._(
309
+ 'Comma separated list of numbers, including min and max values.'
310
+ ),
311
+ },
312
+ ],
313
+ [
314
+ 'options.choropleth.mode',
315
+ {
316
+ handler: 'MultiChoice',
317
+ default: 'kmeans',
318
+ choices: Object.entries(this.MODES),
319
+ label: L._('Choropleth mode'),
320
+ },
321
+ ],
322
+ ]
323
+ },
324
+
325
+ renderLegend: function (container) {
326
+ const parent = L.DomUtil.create('ul', '', container)
327
+ let li, color, label
328
+
329
+ this.options.breaks.slice(0, -1).forEach((limit, index) => {
330
+ li = L.DomUtil.create('li', '', parent)
331
+ color = L.DomUtil.create('span', 'datalayer-color', li)
332
+ color.style.backgroundColor = this.options.colors[index]
333
+ label = L.DomUtil.create('span', '', li)
334
+ label.textContent = `${+this.options.breaks[index].toFixed(
335
+ 1
336
+ )} - ${+this.options.breaks[index + 1].toFixed(1)}`
337
+ })
338
+ },
339
+ })
340
+
341
+ L.U.Layer.Heat = L.HeatLayer.extend({
342
+ statics: {
343
+ NAME: L._('Heatmap'),
344
+ TYPE: 'Heat',
345
+ },
346
+ includes: [L.U.Layer],
347
+ canBrowse: false,
348
+
349
+ initialize: function (datalayer) {
350
+ this.datalayer = datalayer
351
+ L.HeatLayer.prototype.initialize.call(this, [], this.datalayer.options.heat)
352
+ },
353
+
354
+ addLayer: function (layer) {
355
+ if (layer instanceof L.Marker) {
356
+ let latlng = layer.getLatLng(),
357
+ alt
358
+ if (
359
+ this.datalayer.options.heat &&
360
+ this.datalayer.options.heat.intensityProperty
361
+ ) {
362
+ alt = parseFloat(
363
+ layer.properties[this.datalayer.options.heat.intensityProperty || 0]
364
+ )
365
+ latlng = new L.LatLng(latlng.lat, latlng.lng, alt)
366
+ }
367
+ this.addLatLng(latlng)
368
+ }
369
+ },
370
+
371
+ clearLayers: function () {
372
+ this.setLatLngs([])
373
+ },
374
+
375
+ getFeatures: function () {
376
+ return {}
377
+ },
378
+
379
+ getBounds: function () {
380
+ return L.latLngBounds(this._latlngs)
381
+ },
382
+
383
+ getEditableOptions: function () {
384
+ if (!L.Util.isObject(this.datalayer.options.heat)) {
385
+ this.datalayer.options.heat = {}
386
+ }
387
+ return [
388
+ [
389
+ 'options.heat.radius',
390
+ {
391
+ handler: 'Range',
392
+ min: 10,
393
+ max: 100,
394
+ step: 5,
395
+ label: L._('Heatmap radius'),
396
+ helpText: L._('Override heatmap radius (default 25)'),
397
+ },
398
+ ],
399
+ [
400
+ 'options.heat.intensityProperty',
401
+ {
402
+ handler: 'BlurInput',
403
+ placeholder: L._('Heatmap intensity property'),
404
+ helpText: L._('Optional intensity property for heatmap'),
405
+ },
406
+ ],
407
+ ]
408
+ },
409
+
410
+ onEdit: function (field, builder) {
411
+ if (field === 'options.heat.intensityProperty') {
412
+ this.datalayer.resetLayer(true) // We need to repopulate the latlngs
413
+ return
414
+ }
415
+ if (field === 'options.heat.radius') {
416
+ this.options.radius = this.datalayer.options.heat.radius
417
+ }
418
+ this._updateOptions()
419
+ },
420
+
421
+ redraw: function () {
422
+ // setlalngs call _redraw through setAnimFrame, thus async, so this
423
+ // can ends with race condition if we remove the layer very faslty after.
424
+ // TODO: PR in upstream Leaflet.heat
425
+ if (!this._map) return
426
+ L.HeatLayer.prototype.redraw.call(this)
427
+ },
428
+
429
+ _redraw: function () {
430
+ // Import patch from https://github.com/Leaflet/Leaflet.heat/pull/78
431
+ // Remove me when this get merged and released.
432
+ if (!this._map) {
433
+ return
434
+ }
435
+ var data = [],
436
+ r = this._heat._r,
437
+ size = this._map.getSize(),
438
+ bounds = new L.Bounds(L.point([-r, -r]), size.add([r, r])),
439
+ cellSize = r / 2,
440
+ grid = [],
441
+ panePos = this._map._getMapPanePos(),
442
+ offsetX = panePos.x % cellSize,
443
+ offsetY = panePos.y % cellSize,
444
+ i,
445
+ len,
446
+ p,
447
+ cell,
448
+ x,
449
+ y,
450
+ j,
451
+ len2
452
+
453
+ this._max = 1
454
+
455
+ for (i = 0, len = this._latlngs.length; i < len; i++) {
456
+ p = this._map.latLngToContainerPoint(this._latlngs[i])
457
+ x = Math.floor((p.x - offsetX) / cellSize) + 2
458
+ y = Math.floor((p.y - offsetY) / cellSize) + 2
459
+
460
+ var alt =
461
+ this._latlngs[i].alt !== undefined
462
+ ? this._latlngs[i].alt
463
+ : this._latlngs[i][2] !== undefined
464
+ ? +this._latlngs[i][2]
465
+ : 1
466
+
467
+ grid[y] = grid[y] || []
468
+ cell = grid[y][x]
469
+
470
+ if (!cell) {
471
+ cell = grid[y][x] = [p.x, p.y, alt]
472
+ cell.p = p
473
+ } else {
474
+ cell[0] = (cell[0] * cell[2] + p.x * alt) / (cell[2] + alt) // x
475
+ cell[1] = (cell[1] * cell[2] + p.y * alt) / (cell[2] + alt) // y
476
+ cell[2] += alt // cumulated intensity value
477
+ }
478
+
479
+ // Set the max for the current zoom level
480
+ if (cell[2] > this._max) {
481
+ this._max = cell[2]
482
+ }
483
+ }
484
+
485
+ this._heat.max(this._max)
486
+
487
+ for (i = 0, len = grid.length; i < len; i++) {
488
+ if (grid[i]) {
489
+ for (j = 0, len2 = grid[i].length; j < len2; j++) {
490
+ cell = grid[i][j]
491
+ if (cell && bounds.contains(cell.p)) {
492
+ data.push([
493
+ Math.round(cell[0]),
494
+ Math.round(cell[1]),
495
+ Math.min(cell[2], this._max),
496
+ ])
497
+ }
498
+ }
499
+ }
500
+ }
501
+
502
+ this._heat.data(data).draw(this.options.minOpacity)
503
+
504
+ this._frame = null
505
+ },
506
+ })
507
+
508
+ L.U.DataLayer = L.Evented.extend({
509
+ options: {
510
+ displayOnLoad: true,
511
+ inCaption: true,
512
+ browsable: true,
513
+ editMode: 'advanced',
514
+ },
515
+
516
+ initialize: function (map, data) {
517
+ this.map = map
518
+ this._index = Array()
519
+ this._layers = {}
520
+ this._geojson = null
521
+ this._propertiesIndex = []
522
+ this._loaded = false // Are layer metadata loaded
523
+ this._dataloaded = false // Are layer data loaded
524
+
525
+ this.parentPane = this.map.getPane('overlayPane')
526
+ this.pane = this.map.createPane(`datalayer${L.stamp(this)}`, this.parentPane)
527
+ this.pane.dataset.id = L.stamp(this)
528
+ this.renderer = L.svg({ pane: this.pane })
529
+
530
+ let isDirty = false
531
+ let isDeleted = false
532
+ const self = this
533
+ try {
534
+ Object.defineProperty(this, 'isDirty', {
535
+ get: function () {
536
+ return isDirty
537
+ },
538
+ set: function (status) {
539
+ if (!isDirty && status) self.fire('dirty')
540
+ isDirty = status
541
+ if (status) {
542
+ self.map.addDirtyDatalayer(self)
543
+ // A layer can be made dirty by indirect action (like dragging layers)
544
+ // we need to have it loaded before saving it.
545
+ if (!self.isLoaded()) self.fetchData()
546
+ } else {
547
+ self.map.removeDirtyDatalayer(self)
548
+ self.isDeleted = false
549
+ }
550
+ },
551
+ })
552
+ } catch (e) {
553
+ // Certainly IE8, which has a limited version of defineProperty
554
+ }
555
+ try {
556
+ Object.defineProperty(this, 'isDeleted', {
557
+ get: function () {
558
+ return isDeleted
559
+ },
560
+ set: function (status) {
561
+ if (!isDeleted && status) self.fire('deleted')
562
+ isDeleted = status
563
+ if (status) self.isDirty = status
564
+ },
565
+ })
566
+ } catch (e) {
567
+ // Certainly IE8, which has a limited version of defineProperty
568
+ }
569
+ this.setUmapId(data.id)
570
+ this.setOptions(data)
571
+ // Retrocompat
572
+ if (this.options.remoteData && this.options.remoteData.from) {
573
+ this.options.fromZoom = this.options.remoteData.from
574
+ delete this.options.remoteData.from
575
+ }
576
+ if (this.options.remoteData && this.options.remoteData.to) {
577
+ this.options.toZoom = this.options.remoteData.to
578
+ delete this.options.remoteData.to
579
+ }
580
+ this.backupOptions()
581
+ this.connectToMap()
582
+ this.permissions = new L.U.DataLayerPermissions(this)
583
+ if (this.showAtLoad()) this.show()
584
+ if (!this.umap_id) this.isDirty = true
585
+
586
+ this.onceLoaded(function () {
587
+ this.map.on('moveend', this.onMoveEnd, this)
588
+ })
589
+ // Only layers that are displayed on load must be hidden/shown
590
+ // Automatically, others will be shown manually, and thus will
591
+ // be in the "forced visibility" mode
592
+ if (this.autoLoaded()) this.map.on('zoomend', this.onZoomEnd, this)
593
+ },
594
+
595
+ onMoveEnd: function (e) {
596
+ if (this.isRemoteLayer() && this.showAtZoom()) this.fetchRemoteData()
597
+ },
598
+
599
+ onZoomEnd: function (e) {
600
+ if (this._forcedVisibility) return
601
+ if (!this.showAtZoom() && this.isVisible()) this.hide()
602
+ if (this.showAtZoom() && !this.isVisible()) this.show()
603
+ },
604
+
605
+ showAtLoad: function () {
606
+ return this.autoLoaded() && this.showAtZoom()
607
+ },
608
+
609
+ autoLoaded: function () {
610
+ return (
611
+ (this.map.datalayersOnLoad &&
612
+ this.umap_id &&
613
+ this.map.datalayersOnLoad.indexOf(this.umap_id.toString()) !== -1) ||
614
+ (!this.map.datalayersOnLoad && this.options.displayOnLoad)
615
+ )
616
+ },
617
+
618
+ insertBefore: function (other) {
619
+ if (!other) return
620
+ this.parentPane.insertBefore(this.pane, other.pane)
621
+ },
622
+
623
+ insertAfter: function (other) {
624
+ if (!other) return
625
+ this.parentPane.insertBefore(this.pane, other.pane.nextSibling)
626
+ },
627
+
628
+ bringToTop: function () {
629
+ this.parentPane.appendChild(this.pane)
630
+ },
631
+
632
+ hasDataVisible: function () {
633
+ return this.layer.hasDataVisible()
634
+ },
635
+
636
+ resetLayer: function (force) {
637
+ // Only reset if type is defined (undefined is the default) and different from current type
638
+ if (
639
+ this.layer &&
640
+ (!this.options.type || this.options.type === this.layer.getType()) &&
641
+ !force
642
+ ) {
643
+ return
644
+ }
645
+ const visible = this.isVisible()
646
+ if (this.layer) this.layer.clearLayers()
647
+ // delete this.layer?
648
+ if (visible) this.map.removeLayer(this.layer)
649
+ const Class = L.U.Layer[this.options.type] || L.U.Layer.Default
650
+ this.layer = new Class(this)
651
+ this.eachLayer(this.showFeature)
652
+ if (visible) this.show()
653
+ this.propagateRemote()
654
+ },
655
+
656
+ eachLayer: function (method, context) {
657
+ for (const i in this._layers) {
658
+ method.call(context || this, this._layers[i])
659
+ }
660
+ return this
661
+ },
662
+
663
+ eachFeature: function (method, context) {
664
+ if (this.layer && this.layer.canBrowse) {
665
+ for (let i = 0; i < this._index.length; i++) {
666
+ method.call(context || this, this._layers[this._index[i]])
667
+ }
668
+ }
669
+ return this
670
+ },
671
+
672
+ fetchData: function () {
673
+ if (!this.umap_id) return
674
+ if (this._loading) return
675
+ this._loading = true
676
+ this.map.get(this._dataUrl(), {
677
+ callback: function (geojson, response) {
678
+ this._last_modified = response.getResponseHeader('Last-Modified')
679
+ // FIXME: for now this property is set dynamically from backend
680
+ // And thus it's not in the geojson file in the server
681
+ // So do not let all options to be reset
682
+ // Fix is a proper migration so all datalayers settings are
683
+ // in DB, and we remove it from geojson flat files.
684
+ if (geojson._umap_options) {
685
+ geojson._umap_options.editMode = this.options.editMode
686
+ }
687
+ // In case of maps pre 1.0 still around
688
+ if (geojson._storage) geojson._storage.editMode = this.options.editMode
689
+ this.fromUmapGeoJSON(geojson)
690
+ this.backupOptions()
691
+ this.fire('loaded')
692
+ this._loading = false
693
+ },
694
+ context: this,
695
+ })
696
+ },
697
+
698
+ fromGeoJSON: function (geojson) {
699
+ this.addData(geojson)
700
+ this._geojson = geojson
701
+ this._dataloaded = true
702
+ this.fire('dataloaded')
703
+ this.fire('datachanged')
704
+ },
705
+
706
+ fromUmapGeoJSON: function (geojson) {
707
+ if (geojson._storage) geojson._umap_options = geojson._storage // Retrocompat
708
+ if (geojson._umap_options) this.setOptions(geojson._umap_options)
709
+ if (this.isRemoteLayer()) this.fetchRemoteData()
710
+ else this.fromGeoJSON(geojson)
711
+ this._loaded = true
712
+ },
713
+
714
+ clear: function () {
715
+ this.layer.clearLayers()
716
+ this._layers = {}
717
+ this._index = Array()
718
+ if (this._geojson) {
719
+ this.backupData()
720
+ this._geojson = null
721
+ }
722
+ this.fire('datachanged')
723
+ },
724
+
725
+ backupData: function () {
726
+ this._geojson_bk = L.Util.CopyJSON(this._geojson)
727
+ },
728
+
729
+ reindex: function () {
730
+ const features = []
731
+ this.eachFeature((feature) => features.push(feature))
732
+ L.Util.sortFeatures(features, this.map.getOption('sortKey'))
733
+ this._index = []
734
+ for (let i = 0; i < features.length; i++) {
735
+ this._index.push(L.Util.stamp(features[i]))
736
+ }
737
+ },
738
+
739
+ showAtZoom: function () {
740
+ const from = parseInt(this.options.fromZoom, 10),
741
+ to = parseInt(this.options.toZoom, 10),
742
+ zoom = this.map.getZoom()
743
+ return !((!isNaN(from) && zoom < from) || (!isNaN(to) && zoom > to))
744
+ },
745
+
746
+ fetchRemoteData: function (force) {
747
+ if (!this.isRemoteLayer()) return
748
+ if (!this.options.remoteData.dynamic && this.hasDataLoaded() && !force) return
749
+ if (!this.isVisible()) return
750
+ let url = this.map.localizeUrl(this.options.remoteData.url)
751
+ if (this.options.remoteData.proxy)
752
+ url = this.map.proxyUrl(url, this.options.remoteData.ttl)
753
+ this.map.ajax({
754
+ uri: url,
755
+ verb: 'GET',
756
+ callback: (raw) => {
757
+ this.clear()
758
+ this.rawToGeoJSON(raw, this.options.remoteData.format, (geojson) =>
759
+ this.fromGeoJSON(geojson)
760
+ )
761
+ },
762
+ })
763
+ },
764
+
765
+ onceLoaded: function (callback, context) {
766
+ if (this.isLoaded()) callback.call(context || this, this)
767
+ else this.once('loaded', callback, context)
768
+ return this
769
+ },
770
+
771
+ onceDataLoaded: function (callback, context) {
772
+ if (this.hasDataLoaded()) callback.call(context || this, this)
773
+ else this.once('dataloaded', callback, context)
774
+ return this
775
+ },
776
+
777
+ isLoaded: function () {
778
+ return !this.umap_id || this._loaded
779
+ },
780
+
781
+ hasDataLoaded: function () {
782
+ return this._dataloaded
783
+ },
784
+
785
+ setUmapId: function (id) {
786
+ // Datalayer is null when listening creation form
787
+ if (!this.umap_id && id) this.umap_id = id
788
+ },
789
+
790
+ backupOptions: function () {
791
+ this._backupOptions = L.Util.CopyJSON(this.options)
792
+ },
793
+
794
+ resetOptions: function () {
795
+ this.options = L.Util.CopyJSON(this._backupOptions)
796
+ },
797
+
798
+ setOptions: function (options) {
799
+ this.options = L.Util.CopyJSON(L.U.DataLayer.prototype.options) // Start from fresh.
800
+ this.updateOptions(options)
801
+ },
802
+
803
+ updateOptions: function (options) {
804
+ L.Util.setOptions(this, options)
805
+ this.resetLayer()
806
+ },
807
+
808
+ connectToMap: function () {
809
+ const id = L.stamp(this)
810
+ if (!this.map.datalayers[id]) {
811
+ this.map.datalayers[id] = this
812
+ if (L.Util.indexOf(this.map.datalayers_index, this) === -1)
813
+ this.map.datalayers_index.push(this)
814
+ }
815
+ this.map.updateDatalayersControl()
816
+ },
817
+
818
+ _dataUrl: function () {
819
+ const template = this.map.options.urls.datalayer_view
820
+
821
+ let url = L.Util.template(template, {
822
+ pk: this.umap_id,
823
+ map_id: this.map.options.umap_id,
824
+ })
825
+
826
+ // No browser cache for owners/editors.
827
+ if (this.map.hasEditMode()) url = `${url}?${Date.now()}`
828
+ return url
829
+ },
830
+
831
+ isRemoteLayer: function () {
832
+ return !!(
833
+ this.options.remoteData &&
834
+ this.options.remoteData.url &&
835
+ this.options.remoteData.format
836
+ )
837
+ },
838
+
839
+ isClustered: function () {
840
+ return this.options.type === 'Cluster'
841
+ },
842
+
843
+ showFeature: function (feature) {
844
+ const filterKeys = this.map.getFilterKeys(),
845
+ filter = this.map.browser.options.filter
846
+ if (filter && !feature.matchFilter(filter, filterKeys)) return
847
+ if (!feature.matchFacets()) return
848
+ this.layer.addLayer(feature)
849
+ },
850
+
851
+ addLayer: function (feature) {
852
+ const id = L.stamp(feature)
853
+ feature.connectToDataLayer(this)
854
+ this._index.push(id)
855
+ this._layers[id] = feature
856
+ this.indexProperties(feature)
857
+ this.map.features_index[feature.getSlug()] = feature
858
+ this.showFeature(feature)
859
+ if (this.hasDataLoaded()) this.fire('datachanged')
860
+ },
861
+
862
+ removeLayer: function (feature) {
863
+ const id = L.stamp(feature)
864
+ feature.disconnectFromDataLayer(this)
865
+ this._index.splice(this._index.indexOf(id), 1)
866
+ delete this._layers[id]
867
+ this.layer.removeLayer(feature)
868
+ delete this.map.features_index[feature.getSlug()]
869
+ if (this.hasDataLoaded()) this.fire('datachanged')
870
+ },
871
+
872
+ indexProperties: function (feature) {
873
+ for (const i in feature.properties)
874
+ if (typeof feature.properties[i] !== 'object') this.indexProperty(i)
875
+ },
876
+
877
+ indexProperty: function (name) {
878
+ if (!name) return
879
+ if (name.indexOf('_') === 0) return
880
+ if (L.Util.indexOf(this._propertiesIndex, name) !== -1) return
881
+ this._propertiesIndex.push(name)
882
+ },
883
+
884
+ deindexProperty: function (name) {
885
+ const idx = this._propertiesIndex.indexOf(name)
886
+ if (idx !== -1) this._propertiesIndex.splice(idx, 1)
887
+ },
888
+
889
+ addData: function (geojson) {
890
+ try {
891
+ // Do not fail if remote data is somehow invalid,
892
+ // otherwise the layer becomes uneditable.
893
+ this.geojsonToFeatures(geojson)
894
+ } catch (err) {
895
+ console.log('Error with DataLayer', this.umap_id)
896
+ console.error(err)
897
+ }
898
+ },
899
+
900
+ addRawData: function (c, type) {
901
+ this.rawToGeoJSON(c, type, (geojson) => this.addData(geojson))
902
+ },
903
+
904
+ rawToGeoJSON: function (c, type, callback) {
905
+ const toDom = (x) => new DOMParser().parseFromString(x, 'text/xml')
906
+
907
+ // TODO add a duck typing guessType
908
+ if (type === 'csv') {
909
+ csv2geojson.csv2geojson(
910
+ c,
911
+ {
912
+ delimiter: 'auto',
913
+ includeLatLon: false,
914
+ },
915
+ (err, result) => {
916
+ if (err) {
917
+ let message
918
+ if (err.type === 'Error') {
919
+ message = err.message
920
+ } else {
921
+ message = L._('{count} errors during import: {message}', {
922
+ count: err.length,
923
+ message: err[0].message,
924
+ })
925
+ }
926
+ this.map.ui.alert({ content: message, level: 'error', duration: 10000 })
927
+ console.log(err)
928
+ }
929
+ if (result && result.features.length) {
930
+ callback(result)
931
+ }
932
+ }
933
+ )
934
+ } else if (type === 'gpx') {
935
+ callback(toGeoJSON.gpx(toDom(c)))
936
+ } else if (type === 'georss') {
937
+ callback(GeoRSSToGeoJSON(toDom(c)))
938
+ } else if (type === 'kml') {
939
+ callback(toGeoJSON.kml(toDom(c)))
940
+ } else if (type === 'osm') {
941
+ let d
942
+ try {
943
+ d = JSON.parse(c)
944
+ } catch (e) {
945
+ d = toDom(c)
946
+ }
947
+ callback(osmtogeojson(d, { flatProperties: true }))
948
+ } else if (type === 'geojson') {
949
+ try {
950
+ const gj = JSON.parse(c)
951
+ callback(gj)
952
+ } catch (err) {
953
+ this.map.ui.alert({ content: `Invalid JSON file: ${err}` })
954
+ return
955
+ }
956
+ }
957
+ },
958
+
959
+ geojsonToFeatures: function (geojson) {
960
+ if (!geojson) return
961
+ const features = geojson instanceof Array ? geojson : geojson.features
962
+ let i
963
+ let len
964
+ let latlng
965
+ let latlngs
966
+
967
+ if (features) {
968
+ L.Util.sortFeatures(features, this.map.getOption('sortKey'))
969
+ for (i = 0, len = features.length; i < len; i++) {
970
+ this.geojsonToFeatures(features[i])
971
+ }
972
+ return this
973
+ }
974
+
975
+ const geometry = geojson.type === 'Feature' ? geojson.geometry : geojson
976
+ if (!geometry) return // null geometry is valid geojson.
977
+ const coords = geometry.coordinates
978
+ let layer
979
+ let tmp
980
+
981
+ switch (geometry.type) {
982
+ case 'Point':
983
+ try {
984
+ latlng = L.GeoJSON.coordsToLatLng(coords)
985
+ } catch (e) {
986
+ console.error('Invalid latlng object from', coords)
987
+ break
988
+ }
989
+ layer = this._pointToLayer(geojson, latlng)
990
+ break
991
+
992
+ case 'MultiLineString':
993
+ case 'LineString':
994
+ latlngs = L.GeoJSON.coordsToLatLngs(
995
+ coords,
996
+ geometry.type === 'LineString' ? 0 : 1
997
+ )
998
+ if (!latlngs.length) break
999
+ layer = this._lineToLayer(geojson, latlngs)
1000
+ break
1001
+
1002
+ case 'MultiPolygon':
1003
+ case 'Polygon':
1004
+ latlngs = L.GeoJSON.coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2)
1005
+ layer = this._polygonToLayer(geojson, latlngs)
1006
+ break
1007
+ case 'GeometryCollection':
1008
+ return this.geojsonToFeatures(geometry.geometries)
1009
+
1010
+ default:
1011
+ this.map.ui.alert({
1012
+ content: L._('Skipping unknown geometry.type: {type}', {
1013
+ type: geometry.type || 'undefined',
1014
+ }),
1015
+ level: 'error',
1016
+ })
1017
+ }
1018
+ if (layer) {
1019
+ this.addLayer(layer)
1020
+ return layer
1021
+ }
1022
+ },
1023
+
1024
+ _pointToLayer: function (geojson, latlng) {
1025
+ return new L.U.Marker(this.map, latlng, { geojson: geojson, datalayer: this })
1026
+ },
1027
+
1028
+ _lineToLayer: function (geojson, latlngs) {
1029
+ return new L.U.Polyline(this.map, latlngs, {
1030
+ geojson: geojson,
1031
+ datalayer: this,
1032
+ color: null,
1033
+ })
1034
+ },
1035
+
1036
+ _polygonToLayer: function (geojson, latlngs) {
1037
+ // Ensure no empty hole
1038
+ // for (let i = latlngs.length - 1; i > 0; i--) {
1039
+ // if (!latlngs.slice()[i].length) latlngs.splice(i, 1);
1040
+ // }
1041
+ return new L.U.Polygon(this.map, latlngs, { geojson: geojson, datalayer: this })
1042
+ },
1043
+
1044
+ importRaw: function (raw, type) {
1045
+ this.addRawData(raw, type)
1046
+ this.isDirty = true
1047
+ this.zoomTo()
1048
+ },
1049
+
1050
+ importFromFiles: function (files, type) {
1051
+ for (let i = 0, f; (f = files[i]); i++) {
1052
+ this.importFromFile(f, type)
1053
+ }
1054
+ },
1055
+
1056
+ importFromFile: function (f, type) {
1057
+ const reader = new FileReader()
1058
+ type = type || L.Util.detectFileType(f)
1059
+ reader.readAsText(f)
1060
+ reader.onload = (e) => this.importRaw(e.target.result, type)
1061
+ },
1062
+
1063
+ importFromUrl: function (url, type) {
1064
+ url = this.map.localizeUrl(url)
1065
+ this.map.xhr._ajax({
1066
+ verb: 'GET',
1067
+ uri: url,
1068
+ callback: (data) => this.importRaw(data, type),
1069
+ })
1070
+ },
1071
+
1072
+ getColor: function () {
1073
+ return this.options.color || this.map.getOption('color')
1074
+ },
1075
+
1076
+ getDeleteUrl: function () {
1077
+ return L.Util.template(this.map.options.urls.datalayer_delete, {
1078
+ pk: this.umap_id,
1079
+ map_id: this.map.options.umap_id,
1080
+ })
1081
+ },
1082
+
1083
+ getVersionsUrl: function () {
1084
+ return L.Util.template(this.map.options.urls.datalayer_versions, {
1085
+ pk: this.umap_id,
1086
+ map_id: this.map.options.umap_id,
1087
+ })
1088
+ },
1089
+
1090
+ getVersionUrl: function (name) {
1091
+ return L.Util.template(this.map.options.urls.datalayer_version, {
1092
+ pk: this.umap_id,
1093
+ map_id: this.map.options.umap_id,
1094
+ name: name,
1095
+ })
1096
+ },
1097
+
1098
+ _delete: function () {
1099
+ this.isDeleted = true
1100
+ this.erase()
1101
+ },
1102
+
1103
+ empty: function () {
1104
+ if (this.isRemoteLayer()) return
1105
+ this.clear()
1106
+ this.isDirty = true
1107
+ },
1108
+
1109
+ clone: function () {
1110
+ const options = L.Util.CopyJSON(this.options)
1111
+ options.name = L._('Clone of {name}', { name: this.options.name })
1112
+ delete options.id
1113
+ const geojson = L.Util.CopyJSON(this._geojson),
1114
+ datalayer = this.map.createDataLayer(options)
1115
+ datalayer.fromGeoJSON(geojson)
1116
+ return datalayer
1117
+ },
1118
+
1119
+ erase: function () {
1120
+ this.hide()
1121
+ delete this.map.datalayers[L.stamp(this)]
1122
+ this.map.datalayers_index.splice(this.getRank(), 1)
1123
+ this.parentPane.removeChild(this.pane)
1124
+ this.map.updateDatalayersControl()
1125
+ this.fire('erase')
1126
+ this._leaflet_events_bk = this._leaflet_events
1127
+ this.map.off('moveend', this.onMoveEnd, this)
1128
+ this.map.off('zoomend', this.onZoomEnd, this)
1129
+ this.off()
1130
+ this.clear()
1131
+ delete this._loaded
1132
+ delete this._dataloaded
1133
+ },
1134
+
1135
+ reset: function () {
1136
+ if (!this.umap_id) this.erase()
1137
+
1138
+ this.resetOptions()
1139
+ this.parentPane.appendChild(this.pane)
1140
+ if (this._leaflet_events_bk && !this._leaflet_events) {
1141
+ this._leaflet_events = this._leaflet_events_bk
1142
+ }
1143
+ this.clear()
1144
+ this.hide()
1145
+ if (this.isRemoteLayer()) this.fetchRemoteData()
1146
+ else if (this._geojson_bk) this.fromGeoJSON(this._geojson_bk)
1147
+ this._loaded = true
1148
+ this.show()
1149
+ this.isDirty = false
1150
+ },
1151
+
1152
+ redraw: function () {
1153
+ if (!this.isVisible()) return
1154
+ this.hide()
1155
+ this.show()
1156
+ },
1157
+
1158
+ edit: function () {
1159
+ if (!this.map.editEnabled || !this.isLoaded()) {
1160
+ return
1161
+ }
1162
+ const container = L.DomUtil.create('div', 'umap-layer-properties-container'),
1163
+ metadataFields = [
1164
+ 'options.name',
1165
+ 'options.description',
1166
+ ['options.type', { handler: 'LayerTypeChooser', label: L._('Type of layer') }],
1167
+ ['options.displayOnLoad', { label: L._('Display on load'), handler: 'Switch' }],
1168
+ [
1169
+ 'options.browsable',
1170
+ {
1171
+ label: L._('Data is browsable'),
1172
+ handler: 'Switch',
1173
+ helpEntries: 'browsable',
1174
+ },
1175
+ ],
1176
+ [
1177
+ 'options.inCaption',
1178
+ {
1179
+ label: L._('Show this layer in the caption'),
1180
+ handler: 'Switch',
1181
+ },
1182
+ ],
1183
+ ]
1184
+ const title = L.DomUtil.add('h3', '', container, L._('Layer properties'))
1185
+ let builder = new L.U.FormBuilder(this, metadataFields, {
1186
+ callback: function (e) {
1187
+ this.map.updateDatalayersControl()
1188
+ if (e.helper.field === 'options.type') {
1189
+ this.resetLayer()
1190
+ this.edit()
1191
+ }
1192
+ },
1193
+ })
1194
+ container.appendChild(builder.build())
1195
+
1196
+ const redrawCallback = function (e) {
1197
+ const field = e.helper.field,
1198
+ builder = e.helper.builder
1199
+ this.hide()
1200
+ this.layer.onEdit(field, builder)
1201
+ this.show()
1202
+ }
1203
+
1204
+ const layerOptions = this.layer.getEditableOptions()
1205
+
1206
+ if (layerOptions.length) {
1207
+ builder = new L.U.FormBuilder(this, layerOptions, {
1208
+ id: 'datalayer-layer-properties',
1209
+ callback: redrawCallback,
1210
+ })
1211
+ const layerProperties = L.DomUtil.createFieldset(
1212
+ container,
1213
+ `${this.layer.getName()}: ${L._('settings')}`
1214
+ )
1215
+ layerProperties.appendChild(builder.build())
1216
+ }
1217
+
1218
+ let shapeOptions = [
1219
+ 'options.color',
1220
+ 'options.iconClass',
1221
+ 'options.iconUrl',
1222
+ 'options.iconOpacity',
1223
+ 'options.opacity',
1224
+ 'options.stroke',
1225
+ 'options.weight',
1226
+ 'options.fill',
1227
+ 'options.fillColor',
1228
+ 'options.fillOpacity',
1229
+ ]
1230
+
1231
+ builder = new L.U.FormBuilder(this, shapeOptions, {
1232
+ id: 'datalayer-advanced-properties',
1233
+ callback: redrawCallback,
1234
+ })
1235
+ const shapeProperties = L.DomUtil.createFieldset(container, L._('Shape properties'))
1236
+ shapeProperties.appendChild(builder.build())
1237
+
1238
+ let optionsFields = [
1239
+ 'options.smoothFactor',
1240
+ 'options.dashArray',
1241
+ 'options.zoomTo',
1242
+ 'options.fromZoom',
1243
+ 'options.toZoom',
1244
+ 'options.labelKey',
1245
+ ]
1246
+
1247
+ builder = new L.U.FormBuilder(this, optionsFields, {
1248
+ id: 'datalayer-advanced-properties',
1249
+ callback: redrawCallback,
1250
+ })
1251
+ const advancedProperties = L.DomUtil.createFieldset(
1252
+ container,
1253
+ L._('Advanced properties')
1254
+ )
1255
+ advancedProperties.appendChild(builder.build())
1256
+
1257
+ const popupFields = [
1258
+ 'options.popupShape',
1259
+ 'options.popupTemplate',
1260
+ 'options.popupContentTemplate',
1261
+ 'options.showLabel',
1262
+ 'options.labelDirection',
1263
+ 'options.labelInteractive',
1264
+ 'options.outlinkTarget',
1265
+ 'options.interactive',
1266
+ ]
1267
+ builder = new L.U.FormBuilder(this, popupFields, { callback: redrawCallback })
1268
+ const popupFieldset = L.DomUtil.createFieldset(
1269
+ container,
1270
+ L._('Interaction options')
1271
+ )
1272
+ popupFieldset.appendChild(builder.build())
1273
+
1274
+ if (!L.Util.isObject(this.options.remoteData)) {
1275
+ this.options.remoteData = {}
1276
+ }
1277
+ const remoteDataFields = [
1278
+ [
1279
+ 'options.remoteData.url',
1280
+ { handler: 'Url', label: L._('Url'), helpEntries: 'formatURL' },
1281
+ ],
1282
+ ['options.remoteData.format', { handler: 'DataFormat', label: L._('Format') }],
1283
+ 'options.fromZoom',
1284
+ 'options.toZoom',
1285
+ [
1286
+ 'options.remoteData.dynamic',
1287
+ { handler: 'Switch', label: L._('Dynamic'), helpEntries: 'dynamicRemoteData' },
1288
+ ],
1289
+ [
1290
+ 'options.remoteData.licence',
1291
+ {
1292
+ label: L._('Licence'),
1293
+ helpText: L._('Please be sure the licence is compliant with your use.'),
1294
+ },
1295
+ ],
1296
+ ]
1297
+ if (this.map.options.urls.ajax_proxy) {
1298
+ remoteDataFields.push([
1299
+ 'options.remoteData.proxy',
1300
+ {
1301
+ handler: 'Switch',
1302
+ label: L._('Proxy request'),
1303
+ helpEntries: 'proxyRemoteData',
1304
+ },
1305
+ ])
1306
+ remoteDataFields.push([
1307
+ 'options.remoteData.ttl',
1308
+ { handler: 'ProxyTTLSelect', label: L._('Cache proxied request') },
1309
+ ])
1310
+ }
1311
+
1312
+ const remoteDataContainer = L.DomUtil.createFieldset(container, L._('Remote data'))
1313
+ builder = new L.U.FormBuilder(this, remoteDataFields)
1314
+ remoteDataContainer.appendChild(builder.build())
1315
+ L.DomUtil.createButton(
1316
+ 'button umap-verify',
1317
+ remoteDataContainer,
1318
+ L._('Verify remote URL'),
1319
+ () => this.fetchRemoteData(true),
1320
+ this
1321
+ )
1322
+
1323
+ if (this.map.options.urls.datalayer_versions) this.buildVersionsFieldset(container)
1324
+
1325
+ const advancedActions = L.DomUtil.createFieldset(container, L._('Advanced actions'))
1326
+ const advancedButtons = L.DomUtil.create('div', 'button-bar half', advancedActions)
1327
+ const deleteLink = L.DomUtil.createButton(
1328
+ 'button delete_datalayer_button umap-delete',
1329
+ advancedButtons,
1330
+ L._('Delete'),
1331
+ function () {
1332
+ this._delete()
1333
+ this.map.ui.closePanel()
1334
+ },
1335
+ this
1336
+ )
1337
+ if (!this.isRemoteLayer()) {
1338
+ const emptyLink = L.DomUtil.createButton(
1339
+ 'button umap-empty',
1340
+ advancedButtons,
1341
+ L._('Empty'),
1342
+ this.empty,
1343
+ this
1344
+ )
1345
+ }
1346
+ const cloneLink = L.DomUtil.createButton(
1347
+ 'button umap-clone',
1348
+ advancedButtons,
1349
+ L._('Clone'),
1350
+ function () {
1351
+ const datalayer = this.clone()
1352
+ datalayer.edit()
1353
+ },
1354
+ this
1355
+ )
1356
+ if (this.umap_id) {
1357
+ const download = L.DomUtil.createLink(
1358
+ 'button umap-download',
1359
+ advancedButtons,
1360
+ L._('Download'),
1361
+ this._dataUrl(),
1362
+ '_blank'
1363
+ )
1364
+ }
1365
+ this.map.ui.openPanel({ data: { html: container }, className: 'dark' })
1366
+ },
1367
+
1368
+ getOwnOption: function (option) {
1369
+ if (L.Util.usableOption(this.options, option)) return this.options[option]
1370
+ },
1371
+
1372
+ getOption: function (option, feature) {
1373
+ if (this.layer && this.layer.getOption) {
1374
+ const value = this.layer.getOption(option, feature)
1375
+ if (typeof value !== 'undefined') return value
1376
+ }
1377
+ if (typeof this.getOwnOption(option) !== 'undefined') {
1378
+ return this.getOwnOption(option)
1379
+ } else if (this.layer && this.layer.defaults && this.layer.defaults[option]) {
1380
+ return this.layer.defaults[option]
1381
+ } else {
1382
+ return this.map.getOption(option)
1383
+ }
1384
+ },
1385
+
1386
+ buildVersionsFieldset: function (container) {
1387
+ const appendVersion = function (data) {
1388
+ const date = new Date(parseInt(data.at, 10))
1389
+ const content = `${date.toLocaleString(L.lang)} (${parseInt(data.size) / 1000}Kb)`
1390
+ const el = L.DomUtil.create('div', 'umap-datalayer-version', versionsContainer)
1391
+ const button = L.DomUtil.createButton(
1392
+ '',
1393
+ el,
1394
+ '',
1395
+ () => this.restore(data.name),
1396
+ this
1397
+ )
1398
+ button.title = L._('Restore this version')
1399
+ L.DomUtil.add('span', '', el, content)
1400
+ }
1401
+
1402
+ const versionsContainer = L.DomUtil.createFieldset(container, L._('Versions'), {
1403
+ callback: function () {
1404
+ this.map.xhr.get(this.getVersionsUrl(), {
1405
+ callback: function (data) {
1406
+ for (let i = 0; i < data.versions.length; i++) {
1407
+ appendVersion.call(this, data.versions[i])
1408
+ }
1409
+ },
1410
+ context: this,
1411
+ })
1412
+ },
1413
+ context: this,
1414
+ })
1415
+ },
1416
+
1417
+ restore: function (version) {
1418
+ if (!this.map.editEnabled) return
1419
+ if (!confirm(L._('Are you sure you want to restore this version?'))) return
1420
+ this.map.xhr.get(this.getVersionUrl(version), {
1421
+ callback: function (geojson) {
1422
+ if (geojson._storage) geojson._umap_options = geojson._storage // Retrocompat.
1423
+ if (geojson._umap_options) this.setOptions(geojson._umap_options)
1424
+ this.empty()
1425
+ if (this.isRemoteLayer()) this.fetchRemoteData()
1426
+ else this.addData(geojson)
1427
+ this.isDirty = true
1428
+ },
1429
+ context: this,
1430
+ })
1431
+ },
1432
+
1433
+ featuresToGeoJSON: function () {
1434
+ const features = []
1435
+ this.eachLayer((layer) => features.push(layer.toGeoJSON()))
1436
+ return features
1437
+ },
1438
+
1439
+ show: function () {
1440
+ if (!this.isLoaded()) this.fetchData()
1441
+ this.map.addLayer(this.layer)
1442
+ this.fire('show')
1443
+ },
1444
+
1445
+ hide: function () {
1446
+ this.map.removeLayer(this.layer)
1447
+ this.fire('hide')
1448
+ },
1449
+
1450
+ toggle: function () {
1451
+ // From now on, do not try to how/hide
1452
+ // automatically this layer.
1453
+ this._forcedVisibility = true
1454
+ if (!this.isVisible()) this.show()
1455
+ else this.hide()
1456
+ },
1457
+
1458
+ zoomTo: function () {
1459
+ if (!this.isVisible()) return
1460
+ const bounds = this.layer.getBounds()
1461
+ if (bounds.isValid()) this.map.fitBounds(bounds)
1462
+ },
1463
+
1464
+ allowBrowse: function () {
1465
+ return !!this.options.browsable && this.canBrowse() && this.isVisible()
1466
+ },
1467
+
1468
+ count: function () {
1469
+ return this._index.length
1470
+ },
1471
+
1472
+ hasData: function () {
1473
+ return !!this._index.length
1474
+ },
1475
+
1476
+ isVisible: function () {
1477
+ return this.layer && this.map.hasLayer(this.layer)
1478
+ },
1479
+
1480
+ canBrowse: function () {
1481
+ return this.layer && this.layer.canBrowse
1482
+ },
1483
+
1484
+ getFeatureByIndex: function (index) {
1485
+ if (index === -1) index = this._index.length - 1
1486
+ const id = this._index[index]
1487
+ return this._layers[id]
1488
+ },
1489
+
1490
+ getNextFeature: function (feature) {
1491
+ const id = this._index.indexOf(L.stamp(feature))
1492
+ const nextId = this._index[id + 1]
1493
+ return nextId ? this._layers[nextId] : this.getNextBrowsable().getFeatureByIndex(0)
1494
+ },
1495
+
1496
+ getPreviousFeature: function (feature) {
1497
+ if (this._index <= 1) {
1498
+ return null
1499
+ }
1500
+ const id = this._index.indexOf(L.stamp(feature))
1501
+ const previousId = this._index[id - 1]
1502
+ return previousId
1503
+ ? this._layers[previousId]
1504
+ : this.getPreviousBrowsable().getFeatureByIndex(-1)
1505
+ },
1506
+
1507
+ getPreviousBrowsable: function () {
1508
+ let id = this.getRank()
1509
+ let next
1510
+ const index = this.map.datalayers_index
1511
+ while (((id = index[++id] ? id : 0), (next = index[id]))) {
1512
+ if (next === this || (next.allowBrowse() && next.hasData())) break
1513
+ }
1514
+ return next
1515
+ },
1516
+
1517
+ getNextBrowsable: function () {
1518
+ let id = this.getRank()
1519
+ let prev
1520
+ const index = this.map.datalayers_index
1521
+ while (((id = index[--id] ? id : index.length - 1), (prev = index[id]))) {
1522
+ if (prev === this || (prev.allowBrowse() && prev.hasData())) break
1523
+ }
1524
+ return prev
1525
+ },
1526
+
1527
+ umapGeoJSON: function () {
1528
+ return {
1529
+ type: 'FeatureCollection',
1530
+ features: this.isRemoteLayer() ? [] : this.featuresToGeoJSON(),
1531
+ _umap_options: this.options,
1532
+ }
1533
+ },
1534
+
1535
+ getRank: function () {
1536
+ return this.map.datalayers_index.indexOf(this)
1537
+ },
1538
+
1539
+ isReadOnly: function () {
1540
+ // isReadOnly must return true if unset
1541
+ return this.options.editMode === 'disabled'
1542
+ },
1543
+
1544
+ isDataReadOnly: function () {
1545
+ // This layer cannot accept features
1546
+ return this.isReadOnly() || this.isRemoteLayer()
1547
+ },
1548
+
1549
+ save: function () {
1550
+ if (this.isDeleted) return this.saveDelete()
1551
+ if (!this.isLoaded()) {
1552
+ return
1553
+ }
1554
+ const geojson = this.umapGeoJSON()
1555
+ const formData = new FormData()
1556
+ formData.append('name', this.options.name)
1557
+ formData.append('display_on_load', !!this.options.displayOnLoad)
1558
+ formData.append('rank', this.getRank())
1559
+ formData.append('settings', JSON.stringify(this.options))
1560
+ // Filename support is shaky, don't do it for now.
1561
+ const blob = new Blob([JSON.stringify(geojson)], { type: 'application/json' })
1562
+ formData.append('geojson', blob)
1563
+ const saveUrl = this.map.urls.get('datalayer_save', {
1564
+ map_id: this.map.options.umap_id,
1565
+ pk: this.umap_id,
1566
+ })
1567
+ this.map.post(saveUrl, {
1568
+ data: formData,
1569
+ callback: function (data, response) {
1570
+ // Response contains geojson only if save has conflicted and conflicts have
1571
+ // been resolved. So we need to reload to get extra data (saved from someone else)
1572
+ if (data.geojson) {
1573
+ this.clear()
1574
+ this.fromGeoJSON(data.geojson)
1575
+ }
1576
+ this._geojson = geojson
1577
+ this._last_modified = response.getResponseHeader('Last-Modified')
1578
+ this.setUmapId(data.id)
1579
+ this.updateOptions(data)
1580
+ this.backupOptions()
1581
+ this.connectToMap()
1582
+ this._loaded = true
1583
+ this.redraw() // Needed for reordering features
1584
+ this.isDirty = false
1585
+ this.permissions.save()
1586
+ },
1587
+ context: this,
1588
+ headers: this._last_modified
1589
+ ? { 'If-Unmodified-Since': this._last_modified }
1590
+ : {},
1591
+ })
1592
+ },
1593
+
1594
+ saveDelete: function () {
1595
+ const callback = function () {
1596
+ this.isDirty = false
1597
+ this.map.continueSaving()
1598
+ }
1599
+ if (!this.umap_id) return callback.call(this)
1600
+ this.map.xhr.post(this.getDeleteUrl(), {
1601
+ callback: callback,
1602
+ context: this,
1603
+ })
1604
+ },
1605
+
1606
+ getMap: function () {
1607
+ return this.map
1608
+ },
1609
+
1610
+ getName: function () {
1611
+ return this.options.name || L._('Untitled layer')
1612
+ },
1613
+
1614
+ tableEdit: function () {
1615
+ if (this.isRemoteLayer() || !this.isVisible()) return
1616
+ const editor = new L.U.TableEditor(this)
1617
+ editor.edit()
1618
+ },
1619
+ })
1620
+
1621
+ L.TileLayer.include({
1622
+ toJSON: function () {
1623
+ return {
1624
+ minZoom: this.options.minZoom,
1625
+ maxZoom: this.options.maxZoom,
1626
+ attribution: this.options.attribution,
1627
+ url_template: this._url,
1628
+ name: this.options.name,
1629
+ tms: this.options.tms,
1630
+ }
1631
+ },
1632
+
1633
+ getAttribution: function () {
1634
+ return L.Util.toHTML(this.options.attribution)
1635
+ },
1636
+ })