umap-project 2.5.1__py3-none-any.whl → 2.6.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 (276) hide show
  1. umap/__init__.py +1 -1
  2. umap/admin.py +6 -1
  3. umap/context_processors.py +2 -1
  4. umap/decorators.py +13 -2
  5. umap/forms.py +26 -2
  6. umap/locale/br/LC_MESSAGES/django.mo +0 -0
  7. umap/locale/br/LC_MESSAGES/django.po +252 -146
  8. umap/locale/ca/LC_MESSAGES/django.mo +0 -0
  9. umap/locale/ca/LC_MESSAGES/django.po +274 -162
  10. umap/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  11. umap/locale/cs_CZ/LC_MESSAGES/django.po +261 -150
  12. umap/locale/de/LC_MESSAGES/django.mo +0 -0
  13. umap/locale/de/LC_MESSAGES/django.po +299 -187
  14. umap/locale/el/LC_MESSAGES/django.mo +0 -0
  15. umap/locale/el/LC_MESSAGES/django.po +215 -159
  16. umap/locale/en/LC_MESSAGES/django.po +211 -155
  17. umap/locale/es/LC_MESSAGES/django.mo +0 -0
  18. umap/locale/es/LC_MESSAGES/django.po +255 -144
  19. umap/locale/eu/LC_MESSAGES/django.mo +0 -0
  20. umap/locale/eu/LC_MESSAGES/django.po +254 -198
  21. umap/locale/fa_IR/LC_MESSAGES/django.mo +0 -0
  22. umap/locale/fa_IR/LC_MESSAGES/django.po +347 -235
  23. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  24. umap/locale/fr/LC_MESSAGES/django.po +216 -160
  25. umap/locale/hu/LC_MESSAGES/django.mo +0 -0
  26. umap/locale/hu/LC_MESSAGES/django.po +215 -159
  27. umap/locale/it/LC_MESSAGES/django.mo +0 -0
  28. umap/locale/it/LC_MESSAGES/django.po +252 -146
  29. umap/locale/ms/LC_MESSAGES/django.mo +0 -0
  30. umap/locale/ms/LC_MESSAGES/django.po +252 -146
  31. umap/locale/pl/LC_MESSAGES/django.mo +0 -0
  32. umap/locale/pl/LC_MESSAGES/django.po +254 -148
  33. umap/locale/pt/LC_MESSAGES/django.mo +0 -0
  34. umap/locale/pt/LC_MESSAGES/django.po +215 -159
  35. umap/locale/sv/LC_MESSAGES/django.mo +0 -0
  36. umap/locale/sv/LC_MESSAGES/django.po +254 -143
  37. umap/locale/th_TH/LC_MESSAGES/django.mo +0 -0
  38. umap/locale/th_TH/LC_MESSAGES/django.po +125 -70
  39. umap/locale/zh_TW/LC_MESSAGES/django.mo +0 -0
  40. umap/locale/zh_TW/LC_MESSAGES/django.po +256 -145
  41. umap/migrations/0022_add_team.py +94 -0
  42. umap/models.py +45 -10
  43. umap/settings/__init__.py +2 -0
  44. umap/settings/base.py +9 -2
  45. umap/static/umap/base.css +32 -41
  46. umap/static/umap/content.css +19 -25
  47. umap/static/umap/css/icon.css +63 -37
  48. umap/static/umap/css/importers.css +1 -1
  49. umap/static/umap/css/slideshow.css +7 -5
  50. umap/static/umap/css/tableeditor.css +4 -3
  51. umap/static/umap/img/16-white.svg +1 -4
  52. umap/static/umap/img/16.svg +2 -6
  53. umap/static/umap/img/24-white.svg +4 -4
  54. umap/static/umap/img/24.svg +6 -6
  55. umap/static/umap/img/source/16-white.svg +2 -5
  56. umap/static/umap/img/source/16.svg +3 -7
  57. umap/static/umap/img/source/24-white.svg +7 -14
  58. umap/static/umap/img/source/24.svg +10 -17
  59. umap/static/umap/js/components/alerts/alert.css +20 -8
  60. umap/static/umap/js/modules/autocomplete.js +8 -12
  61. umap/static/umap/js/modules/browser.js +4 -3
  62. umap/static/umap/js/modules/caption.js +9 -11
  63. umap/static/umap/js/modules/data/features.js +993 -0
  64. umap/static/umap/js/modules/data/layer.js +1210 -0
  65. umap/static/umap/js/modules/formatter.js +12 -3
  66. umap/static/umap/js/modules/global.js +21 -5
  67. umap/static/umap/js/modules/importers/overpass.js +22 -8
  68. umap/static/umap/js/modules/permissions.js +280 -0
  69. umap/static/umap/js/{umap.icon.js → modules/rendering/icon.js} +77 -56
  70. umap/static/umap/js/modules/rendering/layers/base.js +105 -0
  71. umap/static/umap/js/modules/rendering/layers/classified.js +484 -0
  72. umap/static/umap/js/modules/rendering/layers/cluster.js +103 -0
  73. umap/static/umap/js/modules/rendering/layers/heat.js +182 -0
  74. umap/static/umap/js/modules/rendering/popup.js +99 -0
  75. umap/static/umap/js/modules/rendering/template.js +217 -0
  76. umap/static/umap/js/modules/rendering/ui.js +610 -0
  77. umap/static/umap/js/modules/rules.js +16 -3
  78. umap/static/umap/js/modules/schema.js +25 -1
  79. umap/static/umap/js/modules/share.js +66 -45
  80. umap/static/umap/js/modules/sync/updaters.js +9 -10
  81. umap/static/umap/js/modules/tableeditor.js +7 -7
  82. umap/static/umap/js/modules/ui/dialog.js +8 -4
  83. umap/static/umap/js/modules/utils.js +22 -13
  84. umap/static/umap/js/umap.controls.js +80 -146
  85. umap/static/umap/js/umap.core.js +9 -9
  86. umap/static/umap/js/umap.forms.js +41 -17
  87. umap/static/umap/js/umap.js +72 -65
  88. umap/static/umap/locale/am_ET.js +8 -2
  89. umap/static/umap/locale/am_ET.json +8 -2
  90. umap/static/umap/locale/ar.js +8 -2
  91. umap/static/umap/locale/ar.json +8 -2
  92. umap/static/umap/locale/ast.js +8 -2
  93. umap/static/umap/locale/ast.json +8 -2
  94. umap/static/umap/locale/bg.js +8 -2
  95. umap/static/umap/locale/bg.json +8 -2
  96. umap/static/umap/locale/br.js +42 -36
  97. umap/static/umap/locale/br.json +42 -36
  98. umap/static/umap/locale/ca.js +67 -61
  99. umap/static/umap/locale/ca.json +67 -61
  100. umap/static/umap/locale/cs_CZ.js +8 -2
  101. umap/static/umap/locale/cs_CZ.json +8 -2
  102. umap/static/umap/locale/da.js +8 -2
  103. umap/static/umap/locale/da.json +8 -2
  104. umap/static/umap/locale/de.js +143 -137
  105. umap/static/umap/locale/de.json +143 -137
  106. umap/static/umap/locale/el.js +54 -48
  107. umap/static/umap/locale/el.json +54 -48
  108. umap/static/umap/locale/en.js +10 -2
  109. umap/static/umap/locale/en.json +10 -2
  110. umap/static/umap/locale/en_US.json +8 -2
  111. umap/static/umap/locale/es.js +8 -2
  112. umap/static/umap/locale/es.json +8 -2
  113. umap/static/umap/locale/et.js +8 -2
  114. umap/static/umap/locale/et.json +8 -2
  115. umap/static/umap/locale/eu.js +346 -338
  116. umap/static/umap/locale/eu.json +346 -338
  117. umap/static/umap/locale/fa_IR.js +415 -407
  118. umap/static/umap/locale/fa_IR.json +415 -407
  119. umap/static/umap/locale/fi.js +8 -2
  120. umap/static/umap/locale/fi.json +8 -2
  121. umap/static/umap/locale/fr.js +11 -3
  122. umap/static/umap/locale/fr.json +11 -3
  123. umap/static/umap/locale/gl.js +8 -2
  124. umap/static/umap/locale/gl.json +8 -2
  125. umap/static/umap/locale/he.js +8 -2
  126. umap/static/umap/locale/he.json +8 -2
  127. umap/static/umap/locale/hr.js +8 -2
  128. umap/static/umap/locale/hr.json +8 -2
  129. umap/static/umap/locale/hu.js +31 -23
  130. umap/static/umap/locale/hu.json +31 -23
  131. umap/static/umap/locale/id.js +8 -2
  132. umap/static/umap/locale/id.json +8 -2
  133. umap/static/umap/locale/is.js +8 -2
  134. umap/static/umap/locale/is.json +8 -2
  135. umap/static/umap/locale/it.js +8 -2
  136. umap/static/umap/locale/it.json +8 -2
  137. umap/static/umap/locale/ja.js +8 -2
  138. umap/static/umap/locale/ja.json +8 -2
  139. umap/static/umap/locale/ko.js +8 -2
  140. umap/static/umap/locale/ko.json +8 -2
  141. umap/static/umap/locale/lt.js +8 -2
  142. umap/static/umap/locale/lt.json +8 -2
  143. umap/static/umap/locale/ms.js +8 -2
  144. umap/static/umap/locale/ms.json +8 -2
  145. umap/static/umap/locale/nl.js +8 -2
  146. umap/static/umap/locale/nl.json +8 -2
  147. umap/static/umap/locale/no.js +8 -2
  148. umap/static/umap/locale/no.json +8 -2
  149. umap/static/umap/locale/pl.js +54 -48
  150. umap/static/umap/locale/pl.json +54 -48
  151. umap/static/umap/locale/pl_PL.json +8 -2
  152. umap/static/umap/locale/pt.js +24 -18
  153. umap/static/umap/locale/pt.json +24 -18
  154. umap/static/umap/locale/pt_BR.js +8 -2
  155. umap/static/umap/locale/pt_BR.json +8 -2
  156. umap/static/umap/locale/pt_PT.js +214 -208
  157. umap/static/umap/locale/pt_PT.json +214 -208
  158. umap/static/umap/locale/ro.js +8 -2
  159. umap/static/umap/locale/ro.json +8 -2
  160. umap/static/umap/locale/ru.js +8 -2
  161. umap/static/umap/locale/ru.json +8 -2
  162. umap/static/umap/locale/sk_SK.js +8 -2
  163. umap/static/umap/locale/sk_SK.json +8 -2
  164. umap/static/umap/locale/sl.js +8 -2
  165. umap/static/umap/locale/sl.json +8 -2
  166. umap/static/umap/locale/sr.js +8 -2
  167. umap/static/umap/locale/sr.json +8 -2
  168. umap/static/umap/locale/sv.js +8 -2
  169. umap/static/umap/locale/sv.json +8 -2
  170. umap/static/umap/locale/th_TH.js +33 -27
  171. umap/static/umap/locale/th_TH.json +33 -27
  172. umap/static/umap/locale/tr.js +8 -2
  173. umap/static/umap/locale/tr.json +8 -2
  174. umap/static/umap/locale/uk_UA.js +8 -2
  175. umap/static/umap/locale/uk_UA.json +8 -2
  176. umap/static/umap/locale/vi.js +8 -2
  177. umap/static/umap/locale/vi.json +8 -2
  178. umap/static/umap/locale/vi_VN.json +8 -2
  179. umap/static/umap/locale/zh.js +8 -2
  180. umap/static/umap/locale/zh.json +8 -2
  181. umap/static/umap/locale/zh_CN.json +8 -2
  182. umap/static/umap/locale/zh_TW.Big5.json +8 -2
  183. umap/static/umap/locale/zh_TW.js +102 -96
  184. umap/static/umap/locale/zh_TW.json +102 -96
  185. umap/static/umap/map.css +111 -108
  186. umap/static/umap/nav.css +19 -10
  187. umap/static/umap/unittests/utils.js +230 -107
  188. umap/static/umap/vars.css +1 -0
  189. umap/static/umap/vendors/csv2geojson/csv2geojson.js +62 -40
  190. umap/static/umap/vendors/editable/Leaflet.Editable.js +2079 -1937
  191. umap/storage.py +1 -0
  192. umap/templates/404.html +5 -1
  193. umap/templates/500.html +3 -1
  194. umap/templates/auth/user_detail.html +8 -2
  195. umap/templates/auth/user_form.html +19 -10
  196. umap/templates/auth/user_stars.html +8 -2
  197. umap/templates/base.html +1 -0
  198. umap/templates/registration/login.html +18 -3
  199. umap/templates/umap/about.html +1 -0
  200. umap/templates/umap/about_summary.html +22 -7
  201. umap/templates/umap/components/alerts/alert.html +42 -21
  202. umap/templates/umap/content.html +2 -0
  203. umap/templates/umap/content_footer.html +7 -3
  204. umap/templates/umap/css.html +1 -0
  205. umap/templates/umap/dashboard_menu.html +15 -0
  206. umap/templates/umap/home.html +14 -4
  207. umap/templates/umap/js.html +4 -9
  208. umap/templates/umap/login_popup_end.html +10 -4
  209. umap/templates/umap/map_detail.html +8 -2
  210. umap/templates/umap/map_fragment.html +3 -1
  211. umap/templates/umap/map_init.html +2 -1
  212. umap/templates/umap/map_list.html +6 -3
  213. umap/templates/umap/map_table.html +36 -12
  214. umap/templates/umap/messages.html +0 -1
  215. umap/templates/umap/navigation.html +2 -1
  216. umap/templates/umap/password_change.html +5 -1
  217. umap/templates/umap/password_change_done.html +8 -2
  218. umap/templates/umap/search.html +8 -2
  219. umap/templates/umap/search_bar.html +1 -0
  220. umap/templates/umap/team_confirm_delete.html +19 -0
  221. umap/templates/umap/team_detail.html +27 -0
  222. umap/templates/umap/team_form.html +60 -0
  223. umap/templates/umap/user_dashboard.html +7 -9
  224. umap/templates/umap/user_teams.html +51 -0
  225. umap/tests/base.py +8 -1
  226. umap/tests/conftest.py +6 -0
  227. umap/tests/fixtures/test_circles_layer.geojson +219 -0
  228. umap/tests/fixtures/test_upload_georss.xml +20 -0
  229. umap/tests/integration/conftest.py +18 -4
  230. umap/tests/integration/helpers.py +12 -0
  231. umap/tests/integration/test_anonymous_owned_map.py +23 -0
  232. umap/tests/integration/test_basics.py +29 -0
  233. umap/tests/integration/test_browser.py +20 -0
  234. umap/tests/integration/test_caption.py +20 -0
  235. umap/tests/integration/test_circles_layer.py +69 -0
  236. umap/tests/integration/test_conditional_rules.py +102 -17
  237. umap/tests/integration/test_draw_polygon.py +138 -13
  238. umap/tests/integration/test_draw_polyline.py +8 -18
  239. umap/tests/integration/test_edit_datalayer.py +3 -3
  240. umap/tests/integration/test_import.py +124 -5
  241. umap/tests/integration/test_owned_map.py +21 -13
  242. umap/tests/integration/test_querystring.py +7 -0
  243. umap/tests/integration/test_team.py +47 -0
  244. umap/tests/integration/test_tilelayer.py +19 -2
  245. umap/tests/integration/test_view_marker.py +28 -1
  246. umap/tests/integration/test_websocket_sync.py +5 -5
  247. umap/tests/test_datalayer.py +32 -7
  248. umap/tests/test_datalayer_views.py +1 -1
  249. umap/tests/test_map.py +30 -4
  250. umap/tests/test_map_views.py +2 -2
  251. umap/tests/test_statics.py +40 -0
  252. umap/tests/test_team_views.py +131 -0
  253. umap/tests/test_views.py +15 -1
  254. umap/urls.py +23 -13
  255. umap/views.py +116 -10
  256. {umap_project-2.5.1.dist-info → umap_project-2.6.0.dist-info}/METADATA +14 -14
  257. {umap_project-2.5.1.dist-info → umap_project-2.6.0.dist-info}/RECORD +260 -253
  258. umap/static/umap/js/umap.datalayer.permissions.js +0 -70
  259. umap/static/umap/js/umap.features.js +0 -1290
  260. umap/static/umap/js/umap.layer.js +0 -1837
  261. umap/static/umap/js/umap.permissions.js +0 -208
  262. umap/static/umap/js/umap.popup.js +0 -341
  263. umap/static/umap/test/TableEditor.js +0 -104
  264. umap/static/umap/vendors/leaflet/leaflet-src.js +0 -14512
  265. umap/static/umap/vendors/leaflet/leaflet-src.js.map +0 -1
  266. umap/static/umap/vendors/leaflet/leaflet.js +0 -6
  267. umap/static/umap/vendors/leaflet/leaflet.js.map +0 -1
  268. umap/static/umap/vendors/markercluster/WhereAreTheJavascriptFiles.txt +0 -5
  269. umap/static/umap/vendors/markercluster/leaflet.markercluster-src.js +0 -2718
  270. umap/static/umap/vendors/markercluster/leaflet.markercluster-src.js.map +0 -1
  271. umap/static/umap/vendors/toolbar/leaflet.toolbar-src.css +0 -117
  272. umap/static/umap/vendors/toolbar/leaflet.toolbar-src.js +0 -365
  273. umap/tests/integration/test_statics.py +0 -47
  274. {umap_project-2.5.1.dist-info → umap_project-2.6.0.dist-info}/WHEEL +0 -0
  275. {umap_project-2.5.1.dist-info → umap_project-2.6.0.dist-info}/entry_points.txt +0 -0
  276. {umap_project-2.5.1.dist-info → umap_project-2.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,484 @@
1
+ import { FeatureGroup, DomUtil } from '../../../../vendors/leaflet/leaflet-src.esm.js'
2
+ import { translate } from '../../i18n.js'
3
+ import { LayerMixin } from './base.js'
4
+ import * as Utils from '../../utils.js'
5
+ import { CircleMarker } from '../ui.js'
6
+
7
+ // Layer where each feature color is relative to the others,
8
+ // so we need all features before behing able to set one
9
+ // feature layer
10
+ const ClassifiedMixin = {
11
+ initialize: function (datalayer) {
12
+ this.datalayer = datalayer
13
+ this.colorSchemes = Object.keys(colorbrewer)
14
+ .filter((k) => k !== 'schemeGroups')
15
+ .sort()
16
+ const key = this.getType().toLowerCase()
17
+ if (!Utils.isObject(this.datalayer.options[key])) {
18
+ this.datalayer.options[key] = {}
19
+ }
20
+ this.ensureOptions(this.datalayer.options[key])
21
+ FeatureGroup.prototype.initialize.call(this, [], this.datalayer.options[key])
22
+ LayerMixin.onInit.call(this, this.datalayer.map)
23
+ },
24
+
25
+ ensureOptions: () => {},
26
+
27
+ dataChanged: function () {
28
+ this.redraw()
29
+ },
30
+
31
+ redraw: function () {
32
+ this.compute()
33
+ if (this._map) this.eachLayer(this._map.addLayer, this._map)
34
+ },
35
+
36
+ getStyleProperty: (feature) => {
37
+ return feature.staticOptions.mainColor
38
+ },
39
+
40
+ getOption: function (option, feature) {
41
+ if (!feature) return
42
+ if (option === this.getStyleProperty(feature)) {
43
+ const value = this._getOption(feature)
44
+ return value
45
+ }
46
+ },
47
+
48
+ addLayer: function (layer) {
49
+ // Do not add yet the layer to the map
50
+ // wait for datachanged event, so we can compute breaks only once
51
+ const id = this.getLayerId(layer)
52
+ this._layers[id] = layer
53
+ return this
54
+ },
55
+
56
+ onAdd: function (map) {
57
+ this.compute()
58
+ LayerMixin.onAdd.call(this, map)
59
+ return FeatureGroup.prototype.onAdd.call(this, map)
60
+ },
61
+
62
+ onRemove: function (map) {
63
+ LayerMixin.onRemove.call(this, map)
64
+ return FeatureGroup.prototype.onRemove.call(this, map)
65
+ },
66
+
67
+ getValues: function () {
68
+ const values = []
69
+ this.datalayer.eachFeature((feature) => {
70
+ const value = this._getValue(feature)
71
+ if (value !== undefined) values.push(value)
72
+ })
73
+ return values
74
+ },
75
+
76
+ renderLegend: function (container) {
77
+ const parent = DomUtil.create('ul', '', container)
78
+ const items = this.getLegendItems()
79
+ for (const [color, label] of items) {
80
+ const li = DomUtil.create('li', '', parent)
81
+ const colorEl = DomUtil.create('span', 'datalayer-color', li)
82
+ colorEl.style.backgroundColor = color
83
+ const labelEl = DomUtil.create('span', '', li)
84
+ labelEl.textContent = label
85
+ }
86
+ },
87
+
88
+ getColorSchemes: function (classes) {
89
+ return this.colorSchemes.filter((scheme) => Boolean(colorbrewer[scheme][classes]))
90
+ },
91
+ }
92
+
93
+ export const Choropleth = FeatureGroup.extend({
94
+ statics: {
95
+ NAME: translate('Choropleth'),
96
+ TYPE: 'Choropleth',
97
+ },
98
+ includes: [LayerMixin, ClassifiedMixin],
99
+ // Have defaults that better suit the choropleth mode.
100
+ defaults: {
101
+ color: 'white',
102
+ fillOpacity: 0.7,
103
+ weight: 2,
104
+ },
105
+ MODES: {
106
+ kmeans: translate('K-means'),
107
+ equidistant: translate('Equidistant'),
108
+ jenks: translate('Jenks-Fisher'),
109
+ quantiles: translate('Quantiles'),
110
+ manual: translate('Manual'),
111
+ },
112
+
113
+ _getValue: function (feature) {
114
+ const key = this.datalayer.options.choropleth.property || 'value'
115
+ const value = +feature.properties[key]
116
+ if (!Number.isNaN(value)) return value
117
+ },
118
+
119
+ compute: function () {
120
+ const values = this.getValues()
121
+
122
+ if (!values.length) {
123
+ this.options.breaks = []
124
+ this.options.colors = []
125
+ return
126
+ }
127
+ const mode = this.datalayer.options.choropleth.mode
128
+ let classes = +this.datalayer.options.choropleth.classes || 5
129
+ let breaks
130
+ classes = Math.min(classes, values.length)
131
+ if (mode === 'manual') {
132
+ const manualBreaks = this.datalayer.options.choropleth.breaks
133
+ if (manualBreaks) {
134
+ breaks = manualBreaks
135
+ .split(',')
136
+ .map((b) => +b)
137
+ .filter((b) => !Number.isNaN(b))
138
+ }
139
+ } else if (mode === 'equidistant') {
140
+ breaks = ss.equalIntervalBreaks(values, classes)
141
+ } else if (mode === 'jenks') {
142
+ breaks = ss.jenks(values, classes)
143
+ } else if (mode === 'quantiles') {
144
+ const quantiles = [...Array(classes)].map((e, i) => i / classes).concat(1)
145
+ breaks = ss.quantile(values, quantiles)
146
+ } else {
147
+ breaks = ss.ckmeans(values, classes).map((cluster) => cluster[0])
148
+ breaks.push(ss.max(values)) // Needed for computing the legend
149
+ }
150
+ this.options.breaks = breaks || []
151
+ this.datalayer.options.choropleth.breaks = this.options.breaks
152
+ .map((b) => +b.toFixed(2))
153
+ .join(',')
154
+ let colorScheme = this.datalayer.options.choropleth.brewer
155
+ if (!colorbrewer[colorScheme]) colorScheme = 'Blues'
156
+ this.options.colors = colorbrewer[colorScheme][this.options.breaks.length - 1] || []
157
+ },
158
+
159
+ _getOption: function (feature) {
160
+ if (!feature) return // FIXME should not happen
161
+ const featureValue = this._getValue(feature)
162
+ // Find the bucket/step/limit that this value is less than and give it that color
163
+ for (let i = 1; i < this.options.breaks.length; i++) {
164
+ if (featureValue <= this.options.breaks[i]) {
165
+ return this.options.colors[i - 1]
166
+ }
167
+ }
168
+ },
169
+
170
+ onEdit: function (field, builder) {
171
+ // Only compute the breaks if we're dealing with choropleth
172
+ if (!field.startsWith('options.choropleth')) return
173
+ // If user touches the breaks, then force manual mode
174
+ if (field === 'options.choropleth.breaks') {
175
+ this.datalayer.options.choropleth.mode = 'manual'
176
+ if (builder) builder.helpers['options.choropleth.mode'].fetch()
177
+ }
178
+ this.compute()
179
+ // If user changes the mode or the number of classes,
180
+ // then update the breaks input value
181
+ if (field === 'options.choropleth.mode' || field === 'options.choropleth.classes') {
182
+ if (builder) builder.helpers['options.choropleth.breaks'].fetch()
183
+ }
184
+ },
185
+
186
+ getEditableOptions: function () {
187
+ return [
188
+ [
189
+ 'options.choropleth.property',
190
+ {
191
+ handler: 'Select',
192
+ selectOptions: this.datalayer._propertiesIndex,
193
+ label: translate('Choropleth property value'),
194
+ },
195
+ ],
196
+ [
197
+ 'options.choropleth.brewer',
198
+ {
199
+ handler: 'Select',
200
+ label: translate('Choropleth color palette'),
201
+ selectOptions: this.colorSchemes,
202
+ },
203
+ ],
204
+ [
205
+ 'options.choropleth.classes',
206
+ {
207
+ handler: 'Range',
208
+ min: 3,
209
+ max: 9,
210
+ step: 1,
211
+ label: translate('Choropleth classes'),
212
+ helpText: translate('Number of desired classes (default 5)'),
213
+ },
214
+ ],
215
+ [
216
+ 'options.choropleth.breaks',
217
+ {
218
+ handler: 'BlurInput',
219
+ label: translate('Choropleth breakpoints'),
220
+ helpText: translate(
221
+ 'Comma separated list of numbers, including min and max values.'
222
+ ),
223
+ },
224
+ ],
225
+ [
226
+ 'options.choropleth.mode',
227
+ {
228
+ handler: 'MultiChoice',
229
+ default: 'kmeans',
230
+ choices: Object.entries(this.MODES),
231
+ label: translate('Choropleth mode'),
232
+ },
233
+ ],
234
+ ]
235
+ },
236
+
237
+ getLegendItems: function () {
238
+ return this.options.breaks.slice(0, -1).map((el, index) => {
239
+ const from = +this.options.breaks[index].toFixed(1)
240
+ const to = +this.options.breaks[index + 1].toFixed(1)
241
+ return [this.options.colors[index], `${from} - ${to}`]
242
+ })
243
+ },
244
+ })
245
+
246
+ export const Circles = FeatureGroup.extend({
247
+ statics: {
248
+ NAME: translate('Proportional circles'),
249
+ TYPE: 'Circles',
250
+ },
251
+ includes: [LayerMixin, ClassifiedMixin],
252
+ defaults: {
253
+ weight: 1,
254
+ UIClass: CircleMarker,
255
+ },
256
+
257
+ ensureOptions: function (options) {
258
+ if (!Utils.isObject(this.datalayer.options.circles.radius)) {
259
+ this.datalayer.options.circles.radius = {}
260
+ }
261
+ },
262
+
263
+ _getValue: function (feature) {
264
+ const key = this.datalayer.options.circles.property || 'value'
265
+ const value = +feature.properties[key]
266
+ if (!Number.isNaN(value)) return value
267
+ },
268
+
269
+ compute: function () {
270
+ const values = this.getValues()
271
+ this.options.minValue = Math.sqrt(Math.min(...values))
272
+ this.options.maxValue = Math.sqrt(Math.max(...values))
273
+ this.options.minPX = this.datalayer.options.circles.radius?.min || 2
274
+ this.options.maxPX = this.datalayer.options.circles.radius?.max || 50
275
+ },
276
+
277
+ onEdit: function (field, builder) {
278
+ this.compute()
279
+ },
280
+
281
+ _computeRadius: function (value) {
282
+ const valuesRange = this.options.maxValue - this.options.minValue
283
+ const pxRange = this.options.maxPX - this.options.minPX
284
+ const radius =
285
+ this.options.minPX +
286
+ ((Math.sqrt(value) - this.options.minValue) / valuesRange) * pxRange
287
+ return radius || this.options.minPX
288
+ },
289
+
290
+ _getOption: function (feature) {
291
+ if (!feature) return // FIXME should not happen
292
+ return this._computeRadius(this._getValue(feature))
293
+ },
294
+
295
+ getEditableOptions: function () {
296
+ return [
297
+ [
298
+ 'options.circles.property',
299
+ {
300
+ handler: 'Select',
301
+ selectOptions: this.datalayer._propertiesIndex,
302
+ label: translate('Property name to compute circles'),
303
+ },
304
+ ],
305
+ [
306
+ 'options.circles.radius.min',
307
+ {
308
+ handler: 'Range',
309
+ label: translate('Min circle radius'),
310
+ min: 2,
311
+ max: 10,
312
+ step: 1,
313
+ },
314
+ ],
315
+ [
316
+ 'options.circles.radius.max',
317
+ {
318
+ handler: 'Range',
319
+ label: translate('Max circle radius'),
320
+ min: 12,
321
+ max: 50,
322
+ step: 2,
323
+ },
324
+ ],
325
+ ]
326
+ },
327
+
328
+ getStyleProperty: (feature) => {
329
+ return 'radius'
330
+ },
331
+
332
+ renderLegend: function (container) {
333
+ const parent = DomUtil.create('ul', 'circles-layer-legend', container)
334
+ const color = this.datalayer.getOption('color')
335
+ const values = this.getValues()
336
+ if (!values.length) return
337
+ values.sort((a, b) => a - b)
338
+ const minValue = values[0]
339
+ const maxValue = values[values.length - 1]
340
+ const medianValue = values[Math.round(values.length / 2)]
341
+ const items = [
342
+ [this.options.minPX, minValue],
343
+ [this._computeRadius(medianValue), medianValue],
344
+ [this.options.maxPX, maxValue],
345
+ ]
346
+ for (const [size, label] of items) {
347
+ const li = DomUtil.create('li', '', parent)
348
+ const circleEl = DomUtil.create('span', 'circle', li)
349
+ circleEl.style.backgroundColor = color
350
+ circleEl.style.height = `${size * 2}px`
351
+ circleEl.style.width = `${size * 2}px`
352
+ circleEl.style.opacity = this.datalayer.getOption('opacity')
353
+ const labelEl = DomUtil.create('span', 'label', li)
354
+ labelEl.textContent = label
355
+ }
356
+ },
357
+ })
358
+
359
+ export const Categorized = FeatureGroup.extend({
360
+ statics: {
361
+ NAME: translate('Categorized'),
362
+ TYPE: 'Categorized',
363
+ },
364
+ includes: [LayerMixin, ClassifiedMixin],
365
+ MODES: {
366
+ manual: translate('Manual'),
367
+ alpha: translate('Alphabetical'),
368
+ },
369
+ defaults: {
370
+ color: 'white',
371
+ // fillColor: 'red',
372
+ fillOpacity: 0.7,
373
+ weight: 2,
374
+ },
375
+
376
+ _getValue: function (feature) {
377
+ const key =
378
+ this.datalayer.options.categorized.property || this.datalayer._propertiesIndex[0]
379
+ return feature.properties[key]
380
+ },
381
+
382
+ _getOption: function (feature) {
383
+ if (!feature) return // FIXME should not happen
384
+ const featureValue = this._getValue(feature)
385
+ for (let i = 0; i < this.options.categories.length; i++) {
386
+ if (featureValue === this.options.categories[i]) {
387
+ return this.options.colors[i]
388
+ }
389
+ }
390
+ },
391
+
392
+ compute: function () {
393
+ const values = this.getValues()
394
+
395
+ if (!values.length) {
396
+ this.options.categories = []
397
+ this.options.colors = []
398
+ return
399
+ }
400
+ const mode = this.datalayer.options.categorized.mode
401
+ let categories = []
402
+ if (mode === 'manual') {
403
+ const manualCategories = this.datalayer.options.categorized.categories
404
+ if (manualCategories) {
405
+ categories = manualCategories.split(',')
406
+ }
407
+ } else {
408
+ categories = values
409
+ .filter((val, idx, arr) => arr.indexOf(val) === idx)
410
+ .sort(Utils.naturalSort)
411
+ }
412
+ this.options.categories = categories
413
+ this.datalayer.options.categorized.categories = this.options.categories.join(',')
414
+ const colorScheme = this.datalayer.options.categorized.brewer
415
+ this._classes = this.options.categories.length
416
+ if (colorbrewer[colorScheme]?.[this._classes]) {
417
+ this.options.colors = colorbrewer[colorScheme][this._classes]
418
+ } else {
419
+ this.options.colors = colorbrewer?.Accent[this._classes]
420
+ ? colorbrewer?.Accent[this._classes]
421
+ : U.COLORS // Fixme: move COLORS to modules/
422
+ }
423
+ },
424
+
425
+ getEditableOptions: function () {
426
+ return [
427
+ [
428
+ 'options.categorized.property',
429
+ {
430
+ handler: 'Select',
431
+ selectOptions: this.datalayer._propertiesIndex,
432
+ label: translate('Category property'),
433
+ },
434
+ ],
435
+ [
436
+ 'options.categorized.brewer',
437
+ {
438
+ handler: 'Select',
439
+ label: translate('Color palette'),
440
+ selectOptions: this.getColorSchemes(this._classes),
441
+ },
442
+ ],
443
+ [
444
+ 'options.categorized.categories',
445
+ {
446
+ handler: 'BlurInput',
447
+ label: translate('Categories'),
448
+ helpText: translate('Comma separated list of categories.'),
449
+ },
450
+ ],
451
+ [
452
+ 'options.categorized.mode',
453
+ {
454
+ handler: 'MultiChoice',
455
+ default: 'alpha',
456
+ choices: Object.entries(this.MODES),
457
+ label: translate('Categories mode'),
458
+ },
459
+ ],
460
+ ]
461
+ },
462
+
463
+ onEdit: function (field, builder) {
464
+ // Only compute the categories if we're dealing with categorized
465
+ if (!field.startsWith('options.categorized')) return
466
+ // If user touches the categories, then force manual mode
467
+ if (field === 'options.categorized.categories') {
468
+ this.datalayer.options.categorized.mode = 'manual'
469
+ if (builder) builder.helpers['options.categorized.mode'].fetch()
470
+ }
471
+ this.compute()
472
+ // If user changes the mode
473
+ // then update the categories input value
474
+ if (field === 'options.categorized.mode') {
475
+ if (builder) builder.helpers['options.categorized.categories'].fetch()
476
+ }
477
+ },
478
+
479
+ getLegendItems: function () {
480
+ return this.options.categories.map((limit, index) => {
481
+ return [this.options.colors[index], this.options.categories[index]]
482
+ })
483
+ },
484
+ })
@@ -0,0 +1,103 @@
1
+ // WARNING must be loaded dynamically, or at least after leaflet.markercluster
2
+ // Uses global L.MarkerCluster and L.MarkerClusterGroup, not exposed as ESM
3
+ import { translate } from '../../i18n.js'
4
+ import { LayerMixin } from './base.js'
5
+ import * as Utils from '../../utils.js'
6
+ import { Evented } from '../../../../vendors/leaflet/leaflet-src.esm.js'
7
+ import { Cluster as ClusterIcon } from '../icon.js'
8
+
9
+ const MarkerCluster = L.MarkerCluster.extend({
10
+ // Custom class so we can call computeTextColor
11
+ // when element is already on the DOM.
12
+
13
+ _initIcon: function () {
14
+ L.MarkerCluster.prototype._initIcon.call(this)
15
+ const div = this._icon.querySelector('div')
16
+ // Compute text color only when icon is added to the DOM.
17
+ div.style.color = this._iconObj.computeTextColor(div)
18
+ },
19
+ })
20
+
21
+ export const Cluster = L.MarkerClusterGroup.extend({
22
+ statics: {
23
+ NAME: translate('Clustered'),
24
+ TYPE: 'Cluster',
25
+ },
26
+ includes: [LayerMixin],
27
+
28
+ initialize: function (datalayer) {
29
+ this.datalayer = datalayer
30
+ if (!Utils.isObject(this.datalayer.options.cluster)) {
31
+ this.datalayer.options.cluster = {}
32
+ }
33
+ const options = {
34
+ polygonOptions: {
35
+ color: this.datalayer.getColor(),
36
+ },
37
+ iconCreateFunction: (cluster) => new ClusterIcon(datalayer, cluster),
38
+ }
39
+ if (this.datalayer.options.cluster?.radius) {
40
+ options.maxClusterRadius = this.datalayer.options.cluster.radius
41
+ }
42
+ L.MarkerClusterGroup.prototype.initialize.call(this, options)
43
+ LayerMixin.onInit.call(this, this.datalayer.map)
44
+ this._markerCluster = MarkerCluster
45
+ this._layers = []
46
+ },
47
+
48
+ onAdd: function (map) {
49
+ LayerMixin.onAdd.call(this, map)
50
+ return L.MarkerClusterGroup.prototype.onAdd.call(this, map)
51
+ },
52
+
53
+ onRemove: function (map) {
54
+ // In some situation, the onRemove is called before the layer is really
55
+ // added to the map: basically when combining a defaultView=data + max/minZoom
56
+ // and loading the map at a zoom outside of that zoom range.
57
+ // FIXME: move this upstream (_unbindEvents should accept a map parameter
58
+ // instead of relying on this._map)
59
+ this._map = map
60
+ LayerMixin.onRemove.call(this, map)
61
+ return L.MarkerClusterGroup.prototype.onRemove.call(this, map)
62
+ },
63
+
64
+ addLayer: function (layer) {
65
+ this._layers.push(layer)
66
+ return L.MarkerClusterGroup.prototype.addLayer.call(this, layer)
67
+ },
68
+
69
+ removeLayer: function (layer) {
70
+ this._layers.splice(this._layers.indexOf(layer), 1)
71
+ return L.MarkerClusterGroup.prototype.removeLayer.call(this, layer)
72
+ },
73
+
74
+ getEditableOptions: () => [
75
+ [
76
+ 'options.cluster.radius',
77
+ {
78
+ handler: 'BlurIntInput',
79
+ placeholder: translate('Clustering radius'),
80
+ helpText: translate('Override clustering radius (default 80)'),
81
+ },
82
+ ],
83
+ [
84
+ 'options.cluster.textColor',
85
+ {
86
+ handler: 'TextColorPicker',
87
+ placeholder: translate('Auto'),
88
+ helpText: translate('Text color for the cluster label'),
89
+ },
90
+ ],
91
+ ],
92
+
93
+ onEdit: function (field, builder) {
94
+ if (field === 'options.cluster.radius') {
95
+ // No way to reset radius of an already instanciated MarkerClusterGroup...
96
+ this.datalayer.resetLayer(true)
97
+ return
98
+ }
99
+ if (field === 'options.color') {
100
+ this.options.polygonOptions.color = this.datalayer.getColor()
101
+ }
102
+ },
103
+ })