umap-project 2.5.0__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 +4 -3
  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.0.dist-info → umap_project-2.6.0.dist-info}/METADATA +14 -14
  257. {umap_project-2.5.0.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.0.dist-info → umap_project-2.6.0.dist-info}/WHEEL +0 -0
  275. {umap_project-2.5.0.dist-info → umap_project-2.6.0.dist-info}/entry_points.txt +0 -0
  276. {umap_project-2.5.0.dist-info → umap_project-2.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,993 @@
1
+ import {
2
+ DomUtil,
3
+ DomEvent,
4
+ stamp,
5
+ GeoJSON,
6
+ LineUtil,
7
+ } from '../../../vendors/leaflet/leaflet-src.esm.js'
8
+ import * as Utils from '../utils.js'
9
+ import { SCHEMA } from '../schema.js'
10
+ import { translate } from '../i18n.js'
11
+ import { uMapAlert as Alert } from '../../components/alerts/alert.js'
12
+ import {
13
+ LeafletMarker,
14
+ LeafletPolyline,
15
+ LeafletPolygon,
16
+ MaskPolygon,
17
+ } from '../rendering/ui.js'
18
+ import loadPopup from '../rendering/popup.js'
19
+
20
+ class Feature {
21
+ constructor(datalayer, geojson = {}, id = null) {
22
+ this.sync = datalayer.map.sync_engine.proxy(this)
23
+ this._marked_for_deletion = false
24
+ this._isDirty = false
25
+ this._ui = null
26
+
27
+ // DataLayer the feature belongs to
28
+ this.datalayer = datalayer
29
+ this.properties = { _umap_options: {}, ...(geojson.properties || {}) }
30
+ this.staticOptions = {}
31
+
32
+ if (geojson.coordinates) {
33
+ geojson = { geometry: geojson }
34
+ }
35
+ if (geojson.geometry) {
36
+ this.populate(geojson)
37
+ }
38
+
39
+ if (id) {
40
+ this.id = id
41
+ } else {
42
+ let geojson_id
43
+ if (geojson) {
44
+ geojson_id = geojson.id
45
+ }
46
+
47
+ // Each feature needs an unique identifier
48
+ if (Utils.checkId(geojson_id)) {
49
+ this.id = geojson_id
50
+ } else {
51
+ this.id = Utils.generateId()
52
+ }
53
+ }
54
+ }
55
+
56
+ set isDirty(status) {
57
+ this._isDirty = status
58
+ if (this.datalayer) {
59
+ this.datalayer.isDirty = status
60
+ }
61
+ }
62
+
63
+ get isDirty() {
64
+ return this._isDirty
65
+ }
66
+
67
+ get ui() {
68
+ if (!this._ui) this.makeUI()
69
+ return this._ui
70
+ }
71
+
72
+ get map() {
73
+ return this.datalayer?.map
74
+ }
75
+
76
+ get center() {
77
+ return this.ui.getCenter()
78
+ }
79
+
80
+ get bounds() {
81
+ return this.ui.getBounds()
82
+ }
83
+
84
+ get type() {
85
+ return this.geometry.type
86
+ }
87
+
88
+ get coordinates() {
89
+ return this.geometry.coordinates
90
+ }
91
+
92
+ get geometry() {
93
+ return this._geometry
94
+ }
95
+
96
+ set geometry(value) {
97
+ this._geometry = value
98
+ this.pushGeometry()
99
+ }
100
+
101
+ isOnScreen(bounds) {
102
+ return this.ui?.isOnScreen(bounds)
103
+ }
104
+
105
+ pushGeometry() {
106
+ this.ui.setLatLngs(this.toLatLngs())
107
+ }
108
+
109
+ pullGeometry(sync = true) {
110
+ this.fromLatLngs(this.ui.getLatLngs())
111
+ if (sync) {
112
+ this.sync.update('geometry', this.geometry)
113
+ }
114
+ }
115
+
116
+ fromLatLngs(latlngs) {
117
+ this._geometry = this.convertLatLngs(latlngs)
118
+ }
119
+
120
+ makeUI() {
121
+ const klass = this.getUIClass()
122
+ this._ui = new klass(this, this.toLatLngs())
123
+ }
124
+
125
+ getUIClass() {
126
+ return this.getOption('UIClass')
127
+ }
128
+
129
+ getClassName() {
130
+ return this.staticOptions.className
131
+ }
132
+
133
+ getPreviewColor() {
134
+ return this.getDynamicOption(this.staticOptions.mainColor)
135
+ }
136
+
137
+ getSyncMetadata() {
138
+ return {
139
+ subject: 'feature',
140
+ metadata: {
141
+ id: this.id,
142
+ layerId: this.datalayer?.umap_id || null,
143
+ featureType: this.getClassName(),
144
+ },
145
+ }
146
+ }
147
+
148
+ onCommit() {
149
+ // When the layer is a remote layer, we don't want to sync the creation of the
150
+ // points via the websocket, as the other peers will get them themselves.
151
+ if (this.datalayer?.isRemoteLayer()) return
152
+
153
+ // The "endEdit" event is triggered at the end of an edition,
154
+ // and will trigger the sync.
155
+ // In the case of a deletion (or a change of layer), we don't want this
156
+ // event triggered to cause a sync event, as it would reintroduce
157
+ // deleted features.
158
+ // The `._marked_for_deletion` private property is here to track this status.
159
+ if (this._marked_for_deletion === true) {
160
+ this._marked_for_deletion = false
161
+ return
162
+ }
163
+ this.sync.upsert(this.toGeoJSON())
164
+ }
165
+
166
+ isReadOnly() {
167
+ return this.datalayer?.isDataReadOnly()
168
+ }
169
+
170
+ getSlug() {
171
+ return this.properties[this.map.getOption('slugKey') || 'name'] || ''
172
+ }
173
+
174
+ getPermalink() {
175
+ const slug = this.getSlug()
176
+ if (slug)
177
+ return `${Utils.getBaseUrl()}?${Utils.buildQueryString({ feature: slug })}${
178
+ window.location.hash
179
+ }`
180
+ }
181
+
182
+ view({ latlng } = {}) {
183
+ const outlink = this.getOption('outlink')
184
+ const target = this.getOption('outlinkTarget')
185
+ if (outlink) {
186
+ switch (target) {
187
+ case 'self':
188
+ window.location = outlink
189
+ break
190
+ case 'parent':
191
+ window.top.location = outlink
192
+ break
193
+ default:
194
+ window.open(this.properties._umap_options.outlink)
195
+ }
196
+ return
197
+ }
198
+ // TODO deal with an event instead?
199
+ if (this.map.slideshow) {
200
+ this.map.slideshow.current = this
201
+ }
202
+ this.map.currentFeature = this
203
+ this.attachPopup()
204
+ this.ui.openPopup(latlng || this.center)
205
+ }
206
+
207
+ render(fields) {
208
+ const impactData = fields.some((field) => {
209
+ return field.startsWith('properties.')
210
+ })
211
+ if (impactData) {
212
+ if (this.map.currentFeature === this) {
213
+ this.view()
214
+ }
215
+ }
216
+ this.redraw()
217
+ }
218
+
219
+ edit(event) {
220
+ if (!this.map.editEnabled || this.isReadOnly()) return
221
+ const container = DomUtil.create('div', 'umap-feature-container')
222
+ DomUtil.createTitle(
223
+ container,
224
+ translate('Feature properties'),
225
+ `icon-${this.getClassName()}`
226
+ )
227
+
228
+ let builder = new U.FormBuilder(
229
+ this,
230
+ [['datalayer', { handler: 'DataLayerSwitcher' }]],
231
+ {
232
+ callback() {
233
+ this.edit(event)
234
+ }, // removeLayer step will close the edit panel, let's reopen it
235
+ }
236
+ )
237
+ container.appendChild(builder.build())
238
+
239
+ const properties = []
240
+ for (const property of this.datalayer._propertiesIndex) {
241
+ if (['name', 'description'].includes(property)) {
242
+ continue
243
+ }
244
+ properties.push([`properties.${property}`, { label: property }])
245
+ }
246
+ // We always want name and description for now (properties management to come)
247
+ properties.unshift('properties.description')
248
+ properties.unshift('properties.name')
249
+ builder = new U.FormBuilder(this, properties, {
250
+ id: 'umap-feature-properties',
251
+ })
252
+ container.appendChild(builder.build())
253
+ this.appendEditFieldsets(container)
254
+ const advancedActions = DomUtil.createFieldset(
255
+ container,
256
+ translate('Advanced actions')
257
+ )
258
+ this.getAdvancedEditActions(advancedActions)
259
+ const onLoad = this.map.editPanel.open({ content: container })
260
+ onLoad.then(() => {
261
+ builder.helpers['properties.name'].input.focus()
262
+ })
263
+ this.map.editedFeature = this
264
+ if (!this.ui.isOnScreen(this.map.getBounds())) this.zoomTo(event)
265
+ }
266
+
267
+ getAdvancedEditActions(container) {
268
+ const button = Utils.loadTemplate(`
269
+ <button class="button" type="button">
270
+ <i class="icon icon-24 icon-delete"></i>${translate('Delete')}
271
+ </button>`)
272
+ button.addEventListener('click', () => {
273
+ this.confirmDelete().then(() => this.map.editPanel.close())
274
+ })
275
+ container.appendChild(button)
276
+ }
277
+
278
+ appendEditFieldsets(container) {
279
+ const optionsFields = this.getShapeOptions()
280
+ let builder = new U.FormBuilder(this, optionsFields, {
281
+ id: 'umap-feature-shape-properties',
282
+ })
283
+ const shapeProperties = DomUtil.createFieldset(
284
+ container,
285
+ translate('Shape properties')
286
+ )
287
+ shapeProperties.appendChild(builder.build())
288
+
289
+ const advancedOptions = this.getAdvancedOptions()
290
+ builder = new U.FormBuilder(this, advancedOptions, {
291
+ id: 'umap-feature-advanced-properties',
292
+ })
293
+ const advancedProperties = DomUtil.createFieldset(
294
+ container,
295
+ translate('Advanced properties')
296
+ )
297
+ advancedProperties.appendChild(builder.build())
298
+
299
+ const interactionOptions = this.getInteractionOptions()
300
+ builder = new U.FormBuilder(this, interactionOptions)
301
+ const popupFieldset = DomUtil.createFieldset(
302
+ container,
303
+ translate('Interaction options')
304
+ )
305
+ popupFieldset.appendChild(builder.build())
306
+ }
307
+
308
+ getInteractionOptions() {
309
+ return [
310
+ 'properties._umap_options.popupShape',
311
+ 'properties._umap_options.popupTemplate',
312
+ 'properties._umap_options.showLabel',
313
+ 'properties._umap_options.labelDirection',
314
+ 'properties._umap_options.labelInteractive',
315
+ 'properties._umap_options.outlink',
316
+ 'properties._umap_options.outlinkTarget',
317
+ ]
318
+ }
319
+
320
+ endEdit() {}
321
+
322
+ getDisplayName(fallback) {
323
+ if (fallback === undefined) fallback = this.datalayer.getName()
324
+ const key = this.getOption('labelKey') || 'name'
325
+ // Variables mode.
326
+ if (U.Utils.hasVar(key))
327
+ return U.Utils.greedyTemplate(key, this.extendedProperties())
328
+ // Simple mode.
329
+ return this.properties[key] || this.properties.title || fallback
330
+ }
331
+
332
+ hasPopupFooter() {
333
+ if (this.datalayer.isRemoteLayer() && this.datalayer.options.remoteData.dynamic) {
334
+ return false
335
+ }
336
+ return this.map.getOption('displayPopupFooter')
337
+ }
338
+
339
+ getPopupClass() {
340
+ const old = this.getOption('popupTemplate') // Retrocompat.
341
+ return loadPopup(this.getOption('popupShape') || old)
342
+ }
343
+
344
+ attachPopup() {
345
+ const Class = this.getPopupClass()
346
+ this.ui.bindPopup(new Class(this))
347
+ }
348
+
349
+ async confirmDelete() {
350
+ const confirmed = await this.map.dialog.confirm(
351
+ translate('Are you sure you want to delete the feature?')
352
+ )
353
+ if (confirmed) {
354
+ this.del()
355
+ return true
356
+ }
357
+ return false
358
+ }
359
+
360
+ del(sync) {
361
+ this.isDirty = true
362
+ this.map.closePopup()
363
+ if (this.datalayer) {
364
+ this.datalayer.removeFeature(this, sync)
365
+ }
366
+ }
367
+
368
+ connectToDataLayer(datalayer) {
369
+ this.datalayer = datalayer
370
+ // FIXME should be in layer/ui
371
+ this.ui.options.renderer = this.datalayer.renderer
372
+ }
373
+
374
+ disconnectFromDataLayer(datalayer) {
375
+ if (this.datalayer === datalayer) {
376
+ this.datalayer = null
377
+ }
378
+ }
379
+
380
+ cleanProperty([key, value]) {
381
+ // dot in key will break the dot based property access
382
+ // while editing the feature
383
+ key = key.replace('.', '_')
384
+ return [key, value]
385
+ }
386
+
387
+ populate(geojson) {
388
+ this._geometry = geojson.geometry
389
+ this.properties = Object.fromEntries(
390
+ Object.entries(geojson.properties || {}).map(this.cleanProperty)
391
+ )
392
+ this.properties._umap_options = L.extend(
393
+ {},
394
+ this.properties._storage_options,
395
+ this.properties._umap_options
396
+ )
397
+ // Retrocompat
398
+ if (this.properties._umap_options.clickable === false) {
399
+ this.properties._umap_options.interactive = false
400
+ delete this.properties._umap_options.clickable
401
+ }
402
+ }
403
+
404
+ changeDataLayer(datalayer) {
405
+ if (this.datalayer) {
406
+ this.datalayer.isDirty = true
407
+ this.datalayer.removeFeature(this)
408
+ }
409
+
410
+ datalayer.addFeature(this)
411
+ this.sync.upsert(this.toGeoJSON())
412
+ datalayer.isDirty = true
413
+ this.redraw()
414
+ }
415
+
416
+ getOption(option, fallback) {
417
+ let value = fallback
418
+ if (typeof this.staticOptions[option] !== 'undefined') {
419
+ value = this.staticOptions[option]
420
+ } else if (U.Utils.usableOption(this.properties._umap_options, option)) {
421
+ value = this.properties._umap_options[option]
422
+ } else if (this.datalayer) {
423
+ value = this.datalayer.getOption(option, this)
424
+ } else {
425
+ value = this.map.getOption(option)
426
+ }
427
+ return value
428
+ }
429
+
430
+ getDynamicOption(option, fallback) {
431
+ let value = this.getOption(option, fallback)
432
+ // There is a variable inside.
433
+ if (U.Utils.hasVar(value)) {
434
+ value = U.Utils.greedyTemplate(value, this.properties, true)
435
+ if (U.Utils.hasVar(value)) value = this.map.getDefaultOption(option)
436
+ }
437
+ return value
438
+ }
439
+
440
+ zoomTo({ easing, latlng, callback } = {}) {
441
+ if (easing === undefined) easing = this.map.getOption('easing')
442
+ if (callback) this.map.once('moveend', callback.call(this))
443
+ if (easing) {
444
+ this.map.flyTo(this.center, this.getBestZoom())
445
+ } else {
446
+ latlng = latlng || this.center
447
+ this.map.setView(latlng, this.getBestZoom() || this.map.getZoom())
448
+ }
449
+ }
450
+
451
+ getBestZoom() {
452
+ return this.getOption('zoomTo')
453
+ }
454
+
455
+ getNext() {
456
+ return this.datalayer.getNextFeature(this)
457
+ }
458
+
459
+ getPrevious() {
460
+ return this.datalayer.getPreviousFeature(this)
461
+ }
462
+
463
+ cloneProperties() {
464
+ const properties = L.extend({}, this.properties)
465
+ properties._umap_options = L.extend({}, properties._umap_options)
466
+ if (Object.keys && Object.keys(properties._umap_options).length === 0) {
467
+ delete properties._umap_options // It can make a difference on big data sets
468
+ }
469
+ // Legacy
470
+ delete properties._storage_options
471
+ return properties
472
+ }
473
+
474
+ deleteProperty(property) {
475
+ delete this.properties[property]
476
+ this.isDirty = true
477
+ }
478
+
479
+ renameProperty(from, to) {
480
+ this.properties[to] = this.properties[from]
481
+ this.deleteProperty(from)
482
+ }
483
+
484
+ toGeoJSON() {
485
+ return Utils.CopyJSON({
486
+ type: 'Feature',
487
+ geometry: this.geometry,
488
+ properties: this.cloneProperties(),
489
+ id: this.id,
490
+ })
491
+ }
492
+
493
+ getInplaceToolbarActions() {
494
+ return [U.ToggleEditAction, U.DeleteFeatureAction]
495
+ }
496
+
497
+ getMap() {
498
+ return this.map
499
+ }
500
+
501
+ isFiltered() {
502
+ const filterKeys = this.datalayer.getFilterKeys()
503
+ const filter = this.map.browser.options.filter
504
+ if (filter && !this.matchFilter(filter, filterKeys)) return true
505
+ if (!this.matchFacets()) return true
506
+ return false
507
+ }
508
+
509
+ matchFilter(filter, keys) {
510
+ filter = filter.toLowerCase()
511
+ // When user hasn't touched settings, when a feature has no name
512
+ // it will use the datalayer's name, so let's make the filtering
513
+ // consistent.
514
+ // Also, if the user has defined a labelKey with vars, let's
515
+ // compute before filtering
516
+ if (Utils.hasVar(keys) || keys === 'displayName') {
517
+ return this.getDisplayName().toLowerCase().indexOf(filter) !== -1
518
+ }
519
+ keys = keys.split(',')
520
+ for (let i = 0, value; i < keys.length; i++) {
521
+ value = `${this.properties[keys[i]] || ''}`
522
+ if (value.toLowerCase().indexOf(filter) !== -1) return true
523
+ }
524
+ return false
525
+ }
526
+
527
+ matchFacets() {
528
+ const selected = this.map.facets.selected
529
+ for (const [name, { type, min, max, choices }] of Object.entries(selected)) {
530
+ let value = this.properties[name]
531
+ const parser = this.map.facets.getParser(type)
532
+ value = parser(value)
533
+ switch (type) {
534
+ case 'date':
535
+ case 'datetime':
536
+ case 'number':
537
+ if (!Number.isNaN(min) && !Number.isNaN(value) && min > value) return false
538
+ if (!Number.isNaN(max) && !Number.isNaN(value) && max < value) return false
539
+ break
540
+ default:
541
+ value = value || translate('<empty value>')
542
+ if (choices?.length && !choices.includes(value)) return false
543
+ break
544
+ }
545
+ }
546
+ return true
547
+ }
548
+
549
+ isMulti() {
550
+ return false
551
+ }
552
+
553
+ clone() {
554
+ const geojson = this.toGeoJSON()
555
+ delete geojson.id
556
+ delete geojson.properties.id
557
+ const feature = this.datalayer.makeFeature(geojson)
558
+ feature.isDirty = true
559
+ feature.edit()
560
+ return feature
561
+ }
562
+
563
+ extendedProperties() {
564
+ // Include context properties
565
+ const properties = this.map.getGeoContext()
566
+ const locale = L.getLocale()
567
+ if (locale) properties.locale = locale
568
+ if (L.lang) properties.lang = L.lang
569
+ properties.rank = this.getRank() + 1
570
+ properties.layer = this.datalayer.getName()
571
+ if (this.ui._map && this.hasGeom()) {
572
+ const center = this.center
573
+ properties.lat = center.lat
574
+ properties.lon = center.lng
575
+ properties.lng = center.lng
576
+ properties.alt = center?.alt
577
+ if (typeof this.ui.getMeasure !== 'undefined') {
578
+ properties.measure = this.ui.getMeasure()
579
+ }
580
+ }
581
+ return L.extend(properties, this.properties)
582
+ }
583
+
584
+ getRank() {
585
+ return this.datalayer._index.indexOf(L.stamp(this))
586
+ }
587
+
588
+ redraw() {
589
+ if (this.datalayer?.isVisible()) {
590
+ if (this.getUIClass() !== this.ui.getClass()) {
591
+ this.datalayer.hideFeature(this)
592
+ this.makeUI()
593
+ this.datalayer.showFeature(this)
594
+ } else {
595
+ this.ui._redraw()
596
+ }
597
+ }
598
+ }
599
+ }
600
+
601
+ export class Point extends Feature {
602
+ constructor(datalayer, geojson, id) {
603
+ super(datalayer, geojson, id)
604
+ this.staticOptions = {
605
+ mainColor: 'color',
606
+ className: 'marker',
607
+ }
608
+ }
609
+
610
+ toLatLngs() {
611
+ return GeoJSON.coordsToLatLng(this.coordinates)
612
+ }
613
+
614
+ convertLatLngs(latlng) {
615
+ return { coordinates: GeoJSON.latLngToCoords(latlng), type: 'Point' }
616
+ }
617
+
618
+ getUIClass() {
619
+ return super.getUIClass() || LeafletMarker
620
+ }
621
+
622
+ hasGeom() {
623
+ return Boolean(this.coordinates)
624
+ }
625
+
626
+ _getIconUrl(name = 'icon') {
627
+ return this.getOption(`${name}Url`)
628
+ }
629
+
630
+ getShapeOptions() {
631
+ return [
632
+ 'properties._umap_options.color',
633
+ 'properties._umap_options.iconClass',
634
+ 'properties._umap_options.iconUrl',
635
+ 'properties._umap_options.iconOpacity',
636
+ ]
637
+ }
638
+
639
+ getAdvancedOptions() {
640
+ return ['properties._umap_options.zoomTo']
641
+ }
642
+
643
+ appendEditFieldsets(container) {
644
+ super.appendEditFieldsets(container)
645
+ // FIXME edit feature geometry.coordinates instead
646
+ // (by learning FormBuilder to deal with array indexes ?)
647
+ const coordinatesOptions = [
648
+ ['ui._latlng.lat', { handler: 'FloatInput', label: translate('Latitude') }],
649
+ ['ui._latlng.lng', { handler: 'FloatInput', label: translate('Longitude') }],
650
+ ]
651
+ const builder = new U.FormBuilder(this, coordinatesOptions, {
652
+ callback: () => {
653
+ if (!this.ui._latlng.isValid()) {
654
+ Alert.error(translate('Invalid latitude or longitude'))
655
+ builder.restoreField('ui._latlng.lat')
656
+ builder.restoreField('ui._latlng.lng')
657
+ }
658
+ this.zoomTo({ easing: false })
659
+ },
660
+ })
661
+ const fieldset = DomUtil.createFieldset(container, translate('Coordinates'))
662
+ fieldset.appendChild(builder.build())
663
+ }
664
+
665
+ zoomTo(event) {
666
+ if (this.datalayer.isClustered() && !this._icon) {
667
+ // callback is mandatory for zoomToShowLayer
668
+ this.datalayer.layer.zoomToShowLayer(this, event.callback || (() => {}))
669
+ } else {
670
+ super.zoomTo(event)
671
+ }
672
+ }
673
+ }
674
+
675
+ class Path extends Feature {
676
+ hasGeom() {
677
+ return !this.isEmpty()
678
+ }
679
+
680
+ connectToDataLayer(datalayer) {
681
+ super.connectToDataLayer(datalayer)
682
+ // We keep markers on their own layer on top of the paths.
683
+ this.ui.options.pane = this.datalayer.pane
684
+ }
685
+
686
+ edit(event) {
687
+ if (this.map.editEnabled) {
688
+ super.edit(event)
689
+ if (!this.ui.editEnabled()) this.ui.makeGeometryEditable()
690
+ }
691
+ }
692
+
693
+ _toggleEditing(event) {
694
+ if (this.map.editEnabled) {
695
+ if (this.ui.editEnabled()) {
696
+ this.endEdit()
697
+ this.map.editPanel.close()
698
+ } else {
699
+ this.edit(event)
700
+ }
701
+ }
702
+ // FIXME: disable when disabling global edit
703
+ L.DomEvent.stop(event)
704
+ }
705
+
706
+ getShapeOptions() {
707
+ return [
708
+ 'properties._umap_options.color',
709
+ 'properties._umap_options.opacity',
710
+ 'properties._umap_options.weight',
711
+ ]
712
+ }
713
+
714
+ getAdvancedOptions() {
715
+ return [
716
+ 'properties._umap_options.smoothFactor',
717
+ 'properties._umap_options.dashArray',
718
+ 'properties._umap_options.zoomTo',
719
+ ]
720
+ }
721
+
722
+ getBestZoom() {
723
+ return this.getOption('zoomTo') || this.map.getBoundsZoom(this.bounds, true)
724
+ }
725
+
726
+ endEdit() {
727
+ this.ui.disableEdit()
728
+ super.endEdit()
729
+ }
730
+
731
+ transferShape(at, to) {
732
+ const shape = this.ui.enableEdit().deleteShapeAt(at)
733
+ // FIXME: make Leaflet.Editable send an event instead
734
+ this.pullGeometry()
735
+ this.ui.disableEdit()
736
+ if (!shape) return
737
+ to.ui.enableEdit().appendShape(shape)
738
+ to.pullGeometry()
739
+ if (this.isEmpty()) this.del()
740
+ }
741
+
742
+ isolateShape(latlngs) {
743
+ const properties = this.cloneProperties()
744
+ const type = this instanceof LineString ? 'LineString' : 'Polygon'
745
+ const geometry = this.convertLatLngs(latlngs)
746
+ const other = this.datalayer.makeFeature({ type, geometry, properties })
747
+ other.edit()
748
+ return other
749
+ }
750
+
751
+ getInplaceToolbarActions(event) {
752
+ const items = super.getInplaceToolbarActions(event)
753
+ if (this.isMulti()) {
754
+ items.push(U.DeleteShapeAction)
755
+ items.push(U.ExtractShapeFromMultiAction)
756
+ }
757
+ return items
758
+ }
759
+
760
+ zoomTo({ easing, callback }) {
761
+ // Use bounds instead of centroid for paths.
762
+ easing = easing || this.map.getOption('easing')
763
+ if (easing) {
764
+ this.map.flyToBounds(this.bounds, this.getBestZoom())
765
+ } else {
766
+ this.map.fitBounds(this.bounds, this.getBestZoom() || this.map.getZoom())
767
+ }
768
+ if (callback) callback.call(this)
769
+ }
770
+ }
771
+
772
+ export class LineString extends Path {
773
+ constructor(datalayer, geojson, id) {
774
+ super(datalayer, geojson, id)
775
+ this.staticOptions = {
776
+ stroke: true,
777
+ fill: false,
778
+ mainColor: 'color',
779
+ className: 'polyline',
780
+ }
781
+ }
782
+
783
+ toLatLngs(geometry) {
784
+ return GeoJSON.coordsToLatLngs(this.coordinates, this.type === 'LineString' ? 0 : 1)
785
+ }
786
+
787
+ convertLatLngs(latlngs) {
788
+ let multi = !LineUtil.isFlat(latlngs)
789
+ let coordinates = GeoJSON.latLngsToCoords(latlngs, multi ? 1 : 0, false)
790
+ if (coordinates.length === 1 && typeof coordinates[0][0] !== 'number') {
791
+ coordinates = Utils.flattenCoordinates(coordinates)
792
+ multi = false
793
+ }
794
+ const type = multi ? 'MultiLineString' : 'LineString'
795
+ return { coordinates, type }
796
+ }
797
+
798
+ isEmpty() {
799
+ return !this.coordinates.length
800
+ }
801
+
802
+ getUIClass() {
803
+ return super.getUIClass() || LeafletPolyline
804
+ }
805
+
806
+ isSameClass(other) {
807
+ return other instanceof LineString
808
+ }
809
+
810
+ toPolygon() {
811
+ const geojson = this.toGeoJSON()
812
+ geojson.geometry.type = 'Polygon'
813
+ geojson.geometry.coordinates = [
814
+ Utils.flattenCoordinates(geojson.geometry.coordinates),
815
+ ]
816
+
817
+ delete geojson.id // delete the copied id, a new one will be generated.
818
+
819
+ const polygon = this.datalayer.makeFeature(geojson)
820
+ polygon.edit()
821
+ this.del()
822
+ }
823
+
824
+ getAdvancedEditActions(container) {
825
+ super.getAdvancedEditActions(container)
826
+ DomUtil.createButton(
827
+ 'button umap-to-polygon',
828
+ container,
829
+ translate('Transform to polygon'),
830
+ this.toPolygon,
831
+ this
832
+ )
833
+ }
834
+
835
+ _mergeShapes(from, to) {
836
+ const toLeft = to[0]
837
+ const toRight = to[to.length - 1]
838
+ const fromLeft = from[0]
839
+ const fromRight = from[from.length - 1]
840
+ const l2ldistance = toLeft.distanceTo(fromLeft)
841
+ const l2rdistance = toLeft.distanceTo(fromRight)
842
+ const r2ldistance = toRight.distanceTo(fromLeft)
843
+ const r2rdistance = toRight.distanceTo(fromRight)
844
+ let toMerge
845
+ if (l2rdistance < Math.min(l2ldistance, r2ldistance, r2rdistance)) {
846
+ toMerge = [from, to]
847
+ } else if (r2ldistance < Math.min(l2ldistance, l2rdistance, r2rdistance)) {
848
+ toMerge = [to, from]
849
+ } else if (r2rdistance < Math.min(l2ldistance, l2rdistance, r2ldistance)) {
850
+ from.reverse()
851
+ toMerge = [to, from]
852
+ } else {
853
+ from.reverse()
854
+ toMerge = [from, to]
855
+ }
856
+ const a = toMerge[0]
857
+ const b = toMerge[1]
858
+ const p1 = this.map.latLngToContainerPoint(a[a.length - 1])
859
+ const p2 = this.map.latLngToContainerPoint(b[0])
860
+ const tolerance = 5 // px on screen
861
+ if (Math.abs(p1.x - p2.x) <= tolerance && Math.abs(p1.y - p2.y) <= tolerance) {
862
+ a.pop()
863
+ }
864
+ return a.concat(b)
865
+ }
866
+
867
+ mergeShapes() {
868
+ if (!this.isMulti()) return
869
+ const latlngs = this.getLatLngs()
870
+ if (!latlngs.length) return
871
+ while (latlngs.length > 1) {
872
+ latlngs.splice(0, 2, this._mergeShapes(latlngs[1], latlngs[0]))
873
+ }
874
+ this.ui.setLatLngs(latlngs[0])
875
+ if (!this.editEnabled()) this.edit()
876
+ this.editor.reset()
877
+ this.isDirty = true
878
+ }
879
+
880
+ isMulti() {
881
+ return !LineUtil.isFlat(this.coordinates) && this.coordinates.length > 1
882
+ }
883
+ }
884
+
885
+ export class Polygon extends Path {
886
+ constructor(datalayer, geojson, id) {
887
+ super(datalayer, geojson, id)
888
+ this.staticOptions = {
889
+ mainColor: 'fillColor',
890
+ className: 'polygon',
891
+ }
892
+ }
893
+
894
+ toLatLngs() {
895
+ return GeoJSON.coordsToLatLngs(this.coordinates, this.type === 'Polygon' ? 1 : 2)
896
+ }
897
+
898
+ convertLatLngs(latlngs) {
899
+ const holes = !LineUtil.isFlat(latlngs)
900
+ let multi = holes && !LineUtil.isFlat(latlngs[0])
901
+ let coordinates = GeoJSON.latLngsToCoords(latlngs, multi ? 2 : holes ? 1 : 0, true)
902
+ if (Utils.polygonMustBeFlattened(coordinates)) {
903
+ coordinates = coordinates[0]
904
+ multi = false
905
+ }
906
+ const type = multi ? 'MultiPolygon' : 'Polygon'
907
+ return { coordinates, type }
908
+ }
909
+
910
+ isEmpty() {
911
+ return !this.coordinates.length || !this.coordinates[0].length
912
+ }
913
+
914
+ getUIClass() {
915
+ if (this.getOption('mask')) return MaskPolygon
916
+ return super.getUIClass() || LeafletPolygon
917
+ }
918
+
919
+ isSameClass(other) {
920
+ return other instanceof Polygon
921
+ }
922
+
923
+ getShapeOptions() {
924
+ const options = super.getShapeOptions()
925
+ options.push(
926
+ 'properties._umap_options.stroke',
927
+ 'properties._umap_options.fill',
928
+ 'properties._umap_options.fillColor',
929
+ 'properties._umap_options.fillOpacity'
930
+ )
931
+ return options
932
+ }
933
+
934
+ getPreviewColor() {
935
+ // If user set a fillColor, use it, otherwise default to color
936
+ // which is usually the only one set
937
+ const color = this.getDynamicOption(this.staticOptions.mainColor)
938
+ if (color && color !== SCHEMA.color.default) return color
939
+ return this.getDynamicOption('color')
940
+ }
941
+
942
+ getInteractionOptions() {
943
+ const options = super.getInteractionOptions()
944
+ options.push('properties._umap_options.interactive')
945
+ return options
946
+ }
947
+
948
+ toLineString() {
949
+ const geojson = this.toGeoJSON()
950
+ delete geojson.id
951
+ delete geojson.properties.id
952
+ geojson.geometry.type = 'LineString'
953
+ geojson.geometry.coordinates = Utils.flattenCoordinates(
954
+ geojson.geometry.coordinates
955
+ )
956
+ const polyline = this.datalayer.makeFeature(geojson)
957
+ polyline.edit()
958
+ this.del()
959
+ }
960
+
961
+ getAdvancedOptions() {
962
+ const actions = super.getAdvancedOptions()
963
+ actions.push('properties._umap_options.mask')
964
+ return actions
965
+ }
966
+
967
+ getAdvancedEditActions(container) {
968
+ super.getAdvancedEditActions(container)
969
+ const toLineString = DomUtil.createButton(
970
+ 'button umap-to-polyline',
971
+ container,
972
+ translate('Transform to lines'),
973
+ this.toLineString,
974
+ this
975
+ )
976
+ }
977
+
978
+ isMulti() {
979
+ // Change me when Leaflet#3279 is merged.
980
+ // FIXME use TurfJS
981
+ return (
982
+ !LineUtil.isFlat(this.coordinates) &&
983
+ !LineUtil.isFlat(this.coordinates[0]) &&
984
+ this.coordinates.length > 1
985
+ )
986
+ }
987
+
988
+ getInplaceToolbarActions(event) {
989
+ const items = super.getInplaceToolbarActions(event)
990
+ items.push(U.CreateHoleAction)
991
+ return items
992
+ }
993
+ }