umap-project 2.7.2__py3-none-any.whl → 2.8.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) hide show
  1. umap/__init__.py +1 -1
  2. umap/forms.py +4 -14
  3. umap/locale/am_ET/LC_MESSAGES/django.mo +0 -0
  4. umap/locale/am_ET/LC_MESSAGES/django.po +278 -151
  5. umap/locale/ar/LC_MESSAGES/django.mo +0 -0
  6. umap/locale/ar/LC_MESSAGES/django.po +335 -141
  7. umap/locale/bg/LC_MESSAGES/django.mo +0 -0
  8. umap/locale/bg/LC_MESSAGES/django.po +279 -152
  9. umap/locale/br/LC_MESSAGES/django.mo +0 -0
  10. umap/locale/br/LC_MESSAGES/django.po +95 -79
  11. umap/locale/ca/LC_MESSAGES/django.mo +0 -0
  12. umap/locale/ca/LC_MESSAGES/django.po +85 -68
  13. umap/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  14. umap/locale/cs_CZ/LC_MESSAGES/django.po +78 -66
  15. umap/locale/da/LC_MESSAGES/django.mo +0 -0
  16. umap/locale/da/LC_MESSAGES/django.po +280 -153
  17. umap/locale/de/LC_MESSAGES/django.mo +0 -0
  18. umap/locale/de/LC_MESSAGES/django.po +80 -64
  19. umap/locale/el/LC_MESSAGES/django.mo +0 -0
  20. umap/locale/el/LC_MESSAGES/django.po +82 -66
  21. umap/locale/en/LC_MESSAGES/django.po +73 -61
  22. umap/locale/es/LC_MESSAGES/django.mo +0 -0
  23. umap/locale/es/LC_MESSAGES/django.po +75 -63
  24. umap/locale/et/LC_MESSAGES/django.mo +0 -0
  25. umap/locale/et/LC_MESSAGES/django.po +280 -153
  26. umap/locale/eu/LC_MESSAGES/django.mo +0 -0
  27. umap/locale/eu/LC_MESSAGES/django.po +82 -66
  28. umap/locale/fa_IR/LC_MESSAGES/django.mo +0 -0
  29. umap/locale/fa_IR/LC_MESSAGES/django.po +80 -64
  30. umap/locale/fi/LC_MESSAGES/django.mo +0 -0
  31. umap/locale/fi/LC_MESSAGES/django.po +278 -151
  32. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  33. umap/locale/fr/LC_MESSAGES/django.po +75 -63
  34. umap/locale/gl/LC_MESSAGES/django.mo +0 -0
  35. umap/locale/gl/LC_MESSAGES/django.po +280 -153
  36. umap/locale/he/LC_MESSAGES/django.mo +0 -0
  37. umap/locale/he/LC_MESSAGES/django.po +281 -154
  38. umap/locale/hu/LC_MESSAGES/django.mo +0 -0
  39. umap/locale/hu/LC_MESSAGES/django.po +80 -64
  40. umap/locale/is/LC_MESSAGES/django.mo +0 -0
  41. umap/locale/is/LC_MESSAGES/django.po +280 -153
  42. umap/locale/it/LC_MESSAGES/django.mo +0 -0
  43. umap/locale/it/LC_MESSAGES/django.po +82 -66
  44. umap/locale/ja/LC_MESSAGES/django.mo +0 -0
  45. umap/locale/ja/LC_MESSAGES/django.po +280 -153
  46. umap/locale/ko/LC_MESSAGES/django.mo +0 -0
  47. umap/locale/ko/LC_MESSAGES/django.po +280 -153
  48. umap/locale/lt/LC_MESSAGES/django.mo +0 -0
  49. umap/locale/lt/LC_MESSAGES/django.po +280 -153
  50. umap/locale/ms/LC_MESSAGES/django.mo +0 -0
  51. umap/locale/ms/LC_MESSAGES/django.po +82 -66
  52. umap/locale/nl/LC_MESSAGES/django.mo +0 -0
  53. umap/locale/nl/LC_MESSAGES/django.po +280 -153
  54. umap/locale/pl/LC_MESSAGES/django.mo +0 -0
  55. umap/locale/pl/LC_MESSAGES/django.po +82 -66
  56. umap/locale/pt/LC_MESSAGES/django.mo +0 -0
  57. umap/locale/pt/LC_MESSAGES/django.po +75 -63
  58. umap/locale/pt_BR/LC_MESSAGES/django.mo +0 -0
  59. umap/locale/pt_BR/LC_MESSAGES/django.po +280 -153
  60. umap/locale/pt_PT/LC_MESSAGES/django.mo +0 -0
  61. umap/locale/pt_PT/LC_MESSAGES/django.po +280 -153
  62. umap/locale/ru/LC_MESSAGES/django.mo +0 -0
  63. umap/locale/ru/LC_MESSAGES/django.po +280 -153
  64. umap/locale/sk_SK/LC_MESSAGES/django.mo +0 -0
  65. umap/locale/sk_SK/LC_MESSAGES/django.po +280 -153
  66. umap/locale/sl/LC_MESSAGES/django.mo +0 -0
  67. umap/locale/sl/LC_MESSAGES/django.po +280 -153
  68. umap/locale/sr/LC_MESSAGES/django.mo +0 -0
  69. umap/locale/sr/LC_MESSAGES/django.po +280 -153
  70. umap/locale/sv/LC_MESSAGES/django.mo +0 -0
  71. umap/locale/sv/LC_MESSAGES/django.po +81 -65
  72. umap/locale/th_TH/LC_MESSAGES/django.mo +0 -0
  73. umap/locale/th_TH/LC_MESSAGES/django.po +257 -185
  74. umap/locale/tr/LC_MESSAGES/django.mo +0 -0
  75. umap/locale/tr/LC_MESSAGES/django.po +280 -153
  76. umap/locale/uk_UA/LC_MESSAGES/django.mo +0 -0
  77. umap/locale/uk_UA/LC_MESSAGES/django.po +280 -153
  78. umap/locale/vi/LC_MESSAGES/django.mo +0 -0
  79. umap/locale/vi/LC_MESSAGES/django.po +278 -151
  80. umap/locale/zh/LC_MESSAGES/django.mo +0 -0
  81. umap/locale/zh/LC_MESSAGES/django.po +278 -151
  82. umap/locale/zh_TW/LC_MESSAGES/django.mo +0 -0
  83. umap/locale/zh_TW/LC_MESSAGES/django.po +97 -81
  84. umap/management/commands/empty_trash.py +35 -0
  85. umap/management/commands/migrate_to_S3.py +29 -0
  86. umap/migrations/0023_alter_datalayer_uuid.py +19 -0
  87. umap/migrations/0024_alter_map_share_status.py +30 -0
  88. umap/migrations/0025_alter_datalayer_geojson.py +24 -0
  89. umap/models.py +68 -116
  90. umap/settings/base.py +23 -3
  91. umap/settings/local_s3.py +45 -0
  92. umap/static/umap/base.css +3 -603
  93. umap/static/umap/content.css +5 -3
  94. umap/static/umap/css/bar.css +202 -0
  95. umap/static/umap/css/form.css +620 -0
  96. umap/static/umap/css/icon.css +21 -1
  97. umap/static/umap/css/popup.css +125 -0
  98. umap/static/umap/img/16-white.svg +16 -4
  99. umap/static/umap/img/16.svg +1 -1
  100. umap/static/umap/img/source/16-white.svg +46 -45
  101. umap/static/umap/img/source/16.svg +1 -753
  102. umap/static/umap/js/components/fragment.js +3 -1
  103. umap/static/umap/js/modules/browser.js +20 -19
  104. umap/static/umap/js/modules/caption.js +21 -22
  105. umap/static/umap/js/modules/data/features.js +120 -78
  106. umap/static/umap/js/modules/data/layer.js +195 -153
  107. umap/static/umap/js/modules/facets.js +9 -9
  108. umap/static/umap/js/modules/formatter.js +5 -5
  109. umap/static/umap/js/modules/global.js +4 -52
  110. umap/static/umap/js/modules/help.js +18 -21
  111. umap/static/umap/js/modules/importer.js +133 -56
  112. umap/static/umap/js/modules/importers/cadastrefr.js +4 -0
  113. umap/static/umap/js/modules/importers/geodatamine.js +3 -3
  114. umap/static/umap/js/modules/importers/overpass.js +5 -0
  115. umap/static/umap/js/modules/permissions.js +85 -87
  116. umap/static/umap/js/modules/rendering/icon.js +2 -1
  117. umap/static/umap/js/modules/rendering/layers/base.js +15 -15
  118. umap/static/umap/js/modules/rendering/layers/classified.js +1 -1
  119. umap/static/umap/js/modules/rendering/layers/cluster.js +1 -1
  120. umap/static/umap/js/modules/rendering/layers/heat.js +1 -1
  121. umap/static/umap/js/modules/rendering/map.js +390 -0
  122. umap/static/umap/js/modules/rendering/popup.js +19 -19
  123. umap/static/umap/js/modules/rendering/template.js +88 -21
  124. umap/static/umap/js/modules/rendering/ui.js +63 -14
  125. umap/static/umap/js/modules/request.js +2 -2
  126. umap/static/umap/js/modules/rules.js +22 -25
  127. umap/static/umap/js/modules/saving.js +47 -0
  128. umap/static/umap/js/modules/schema.js +6 -0
  129. umap/static/umap/js/modules/share.js +21 -24
  130. umap/static/umap/js/modules/slideshow.js +24 -20
  131. umap/static/umap/js/modules/sync/updaters.js +7 -9
  132. umap/static/umap/js/modules/tableeditor.js +20 -19
  133. umap/static/umap/js/modules/ui/bar.js +196 -0
  134. umap/static/umap/js/modules/ui/dialog.js +6 -1
  135. umap/static/umap/js/modules/ui/panel.js +10 -9
  136. umap/static/umap/js/modules/umap.js +1691 -0
  137. umap/static/umap/js/modules/urls.js +2 -2
  138. umap/static/umap/js/modules/utils.js +22 -6
  139. umap/static/umap/js/umap.controls.js +81 -305
  140. umap/static/umap/js/umap.core.js +29 -50
  141. umap/static/umap/js/umap.forms.js +78 -27
  142. umap/static/umap/keycloak.png +0 -0
  143. umap/static/umap/locale/am_ET.js +26 -10
  144. umap/static/umap/locale/am_ET.json +26 -10
  145. umap/static/umap/locale/ar.js +26 -10
  146. umap/static/umap/locale/ar.json +26 -10
  147. umap/static/umap/locale/ast.js +26 -10
  148. umap/static/umap/locale/ast.json +26 -10
  149. umap/static/umap/locale/bg.js +26 -10
  150. umap/static/umap/locale/bg.json +26 -10
  151. umap/static/umap/locale/br.js +27 -20
  152. umap/static/umap/locale/br.json +27 -20
  153. umap/static/umap/locale/ca.js +32 -29
  154. umap/static/umap/locale/ca.json +32 -29
  155. umap/static/umap/locale/cs_CZ.js +24 -17
  156. umap/static/umap/locale/cs_CZ.json +24 -17
  157. umap/static/umap/locale/da.js +26 -10
  158. umap/static/umap/locale/da.json +26 -10
  159. umap/static/umap/locale/de.js +21 -14
  160. umap/static/umap/locale/de.json +21 -14
  161. umap/static/umap/locale/el.js +28 -12
  162. umap/static/umap/locale/el.json +28 -12
  163. umap/static/umap/locale/en.js +14 -9
  164. umap/static/umap/locale/en.json +14 -9
  165. umap/static/umap/locale/en_US.json +26 -10
  166. umap/static/umap/locale/es.js +16 -13
  167. umap/static/umap/locale/es.json +16 -13
  168. umap/static/umap/locale/et.js +26 -10
  169. umap/static/umap/locale/et.json +26 -10
  170. umap/static/umap/locale/eu.js +16 -9
  171. umap/static/umap/locale/eu.json +16 -9
  172. umap/static/umap/locale/fa_IR.js +16 -9
  173. umap/static/umap/locale/fa_IR.json +16 -9
  174. umap/static/umap/locale/fi.js +26 -10
  175. umap/static/umap/locale/fi.json +26 -10
  176. umap/static/umap/locale/fr.js +14 -9
  177. umap/static/umap/locale/fr.json +14 -9
  178. umap/static/umap/locale/gl.js +26 -10
  179. umap/static/umap/locale/gl.json +26 -10
  180. umap/static/umap/locale/he.js +26 -10
  181. umap/static/umap/locale/he.json +26 -10
  182. umap/static/umap/locale/hr.js +26 -10
  183. umap/static/umap/locale/hr.json +26 -10
  184. umap/static/umap/locale/hu.js +16 -9
  185. umap/static/umap/locale/hu.json +16 -9
  186. umap/static/umap/locale/id.js +26 -10
  187. umap/static/umap/locale/id.json +26 -10
  188. umap/static/umap/locale/is.js +26 -10
  189. umap/static/umap/locale/is.json +26 -10
  190. umap/static/umap/locale/it.js +26 -10
  191. umap/static/umap/locale/it.json +26 -10
  192. umap/static/umap/locale/ja.js +26 -10
  193. umap/static/umap/locale/ja.json +26 -10
  194. umap/static/umap/locale/ko.js +26 -10
  195. umap/static/umap/locale/ko.json +26 -10
  196. umap/static/umap/locale/lt.js +26 -10
  197. umap/static/umap/locale/lt.json +26 -10
  198. umap/static/umap/locale/ms.js +28 -12
  199. umap/static/umap/locale/ms.json +28 -12
  200. umap/static/umap/locale/nl.js +28 -12
  201. umap/static/umap/locale/nl.json +28 -12
  202. umap/static/umap/locale/no.js +26 -10
  203. umap/static/umap/locale/no.json +26 -10
  204. umap/static/umap/locale/pl.js +28 -12
  205. umap/static/umap/locale/pl.json +28 -12
  206. umap/static/umap/locale/pl_PL.json +26 -10
  207. umap/static/umap/locale/pt.js +16 -9
  208. umap/static/umap/locale/pt.json +16 -9
  209. umap/static/umap/locale/pt_BR.js +26 -10
  210. umap/static/umap/locale/pt_BR.json +26 -10
  211. umap/static/umap/locale/pt_PT.js +16 -9
  212. umap/static/umap/locale/pt_PT.json +16 -9
  213. umap/static/umap/locale/ro.js +26 -10
  214. umap/static/umap/locale/ro.json +26 -10
  215. umap/static/umap/locale/ru.js +26 -10
  216. umap/static/umap/locale/ru.json +26 -10
  217. umap/static/umap/locale/si.js +7 -7
  218. umap/static/umap/locale/si.json +7 -7
  219. umap/static/umap/locale/sk_SK.js +26 -10
  220. umap/static/umap/locale/sk_SK.json +26 -10
  221. umap/static/umap/locale/sl.js +26 -10
  222. umap/static/umap/locale/sl.json +26 -10
  223. umap/static/umap/locale/sr.js +26 -10
  224. umap/static/umap/locale/sr.json +26 -10
  225. umap/static/umap/locale/sv.js +27 -11
  226. umap/static/umap/locale/sv.json +27 -11
  227. umap/static/umap/locale/th_TH.js +28 -12
  228. umap/static/umap/locale/th_TH.json +28 -12
  229. umap/static/umap/locale/tr.js +26 -10
  230. umap/static/umap/locale/tr.json +26 -10
  231. umap/static/umap/locale/uk_UA.js +26 -10
  232. umap/static/umap/locale/uk_UA.json +26 -10
  233. umap/static/umap/locale/vi.js +26 -10
  234. umap/static/umap/locale/vi.json +26 -10
  235. umap/static/umap/locale/vi_VN.json +26 -10
  236. umap/static/umap/locale/zh.js +26 -10
  237. umap/static/umap/locale/zh.json +26 -10
  238. umap/static/umap/locale/zh_CN.json +26 -10
  239. umap/static/umap/locale/zh_TW.Big5.json +26 -10
  240. umap/static/umap/locale/zh_TW.js +34 -27
  241. umap/static/umap/locale/zh_TW.json +34 -27
  242. umap/static/umap/map.css +39 -530
  243. umap/static/umap/unittests/URLs.js +15 -15
  244. umap/static/umap/unittests/utils.js +23 -1
  245. umap/static/umap/vars.css +2 -1
  246. umap/static/umap/vendors/formbuilder/Leaflet.FormBuilder.js +5 -1
  247. umap/storage/__init__.py +3 -0
  248. umap/storage/fs.py +101 -0
  249. umap/storage/s3.py +61 -0
  250. umap/templates/base.html +2 -0
  251. umap/templates/registration/login.html +7 -6
  252. umap/templates/umap/components/alerts/alert.html +4 -0
  253. umap/templates/umap/css.html +6 -0
  254. umap/templates/umap/js.html +3 -2
  255. umap/templates/umap/map_init.html +6 -5
  256. umap/templates/umap/user_dashboard.html +20 -19
  257. umap/tests/base.py +11 -1
  258. umap/tests/fixtures/empty_tile.png +0 -0
  259. umap/tests/fixtures/test_upload_simple_marker.json +19 -0
  260. umap/tests/integration/conftest.py +4 -1
  261. umap/tests/integration/test_anonymous_owned_map.py +18 -10
  262. umap/tests/integration/test_browser.py +16 -1
  263. umap/tests/integration/test_dashboard.py +1 -1
  264. umap/tests/integration/test_edit_datalayer.py +29 -7
  265. umap/tests/integration/test_import.py +28 -4
  266. umap/tests/integration/test_optimistic_merge.py +31 -8
  267. umap/tests/integration/test_owned_map.py +22 -16
  268. umap/tests/integration/test_popup.py +44 -0
  269. umap/tests/integration/test_save.py +35 -0
  270. umap/tests/integration/test_view_marker.py +12 -0
  271. umap/tests/integration/test_view_polyline.py +257 -0
  272. umap/tests/integration/test_websocket_sync.py +81 -9
  273. umap/tests/test_dashboard.py +82 -0
  274. umap/tests/test_datalayer.py +6 -7
  275. umap/tests/test_datalayer_s3.py +135 -0
  276. umap/tests/test_datalayer_views.py +28 -10
  277. umap/tests/test_empty_trash.py +34 -0
  278. umap/tests/test_map.py +12 -3
  279. umap/tests/test_map_views.py +69 -37
  280. umap/tests/test_statics.py +1 -1
  281. umap/tests/test_team_views.py +35 -1
  282. umap/tests/test_views.py +31 -52
  283. umap/urls.py +3 -3
  284. umap/views.py +126 -90
  285. {umap_project-2.7.2.dist-info → umap_project-2.8.0.dist-info}/METADATA +16 -14
  286. {umap_project-2.7.2.dist-info → umap_project-2.8.0.dist-info}/RECORD +290 -269
  287. {umap_project-2.7.2.dist-info → umap_project-2.8.0.dist-info}/WHEEL +1 -1
  288. umap/management/commands/purge_purgatory.py +0 -28
  289. umap/static/umap/js/umap.js +0 -1903
  290. umap/tests/test_purge_purgatory.py +0 -25
  291. /umap/{storage.py → storage/staticfiles.py} +0 -0
  292. {umap_project-2.7.2.dist-info → umap_project-2.8.0.dist-info}/entry_points.txt +0 -0
  293. {umap_project-2.7.2.dist-info → umap_project-2.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,1903 +0,0 @@
1
- L.Map.mergeOptions({
2
- overlay: {},
3
- datalayers: [],
4
- hash: true,
5
- maxZoomLimit: 24,
6
- attributionControl: false,
7
- editMode: 'advanced',
8
- noControl: false, // Do not render any control.
9
- name: '',
10
- description: '',
11
- // When a TileLayer is in TMS mode, it needs -y instead of y.
12
- // This is usually handled by the TileLayer instance itself, but
13
- // we cannot rely on this because of the y is overriden by Leaflet
14
- // See https://github.com/Leaflet/Leaflet/pull/9201
15
- // And let's remove this -y when this PR is merged and released.
16
- demoTileInfos: { s: 'a', z: 9, x: 265, y: 181, '-y': 181, r: '' },
17
- licences: [],
18
- licence: '',
19
- enableMarkerDraw: true,
20
- enablePolygonDraw: true,
21
- enablePolylineDraw: true,
22
- limitBounds: {},
23
- slideshow: {},
24
- clickable: true,
25
- permissions: {},
26
- featuresHaveOwner: false,
27
- })
28
-
29
- U.Map = L.Map.extend({
30
- includes: [ControlsMixin],
31
-
32
- initialize: async function (el, geojson) {
33
- this.sync_engine = new U.SyncEngine(this)
34
- this.sync = this.sync_engine.proxy(this)
35
- // Locale name (pt_PT, en_US…)
36
- // To be used for Django localization
37
- if (geojson.properties.locale) L.setLocale(geojson.properties.locale)
38
-
39
- // Language code (pt-pt, en-us…)
40
- // To be used in javascript APIs
41
- if (geojson.properties.lang) L.lang = geojson.properties.lang
42
-
43
- this.setOptionsFromQueryString(geojson.properties)
44
- // Prevent default creation of controls
45
- const zoomControl = geojson.properties.zoomControl
46
- const fullscreenControl = geojson.properties.fullscreenControl
47
- geojson.properties.zoomControl = false
48
- geojson.properties.fullscreenControl = false
49
-
50
- L.Map.prototype.initialize.call(this, el, geojson.properties)
51
-
52
- if (geojson.properties.schema) this.overrideSchema(geojson.properties.schema)
53
-
54
- // After calling parent initialize, as we are doing initCenter our-selves
55
- if (geojson.geometry) this.options.center = this.latLng(geojson.geometry)
56
- this.urls = new U.URLs(this.options.urls)
57
-
58
- this.panel = new U.Panel(this)
59
- this.dialog = new U.Dialog({ className: 'dark' })
60
- this.tooltip = new U.Tooltip(this._controlContainer)
61
- this.contextmenu = new U.ContextMenu()
62
- if (this.hasEditMode()) {
63
- this.editPanel = new U.EditPanel(this)
64
- this.fullPanel = new U.FullPanel(this)
65
- }
66
- if (!this.options.noControl) {
67
- L.DomEvent.on(document.body, 'dataloading', (e) => this.fire('dataloading', e))
68
- L.DomEvent.on(document.body, 'dataload', (e) => this.fire('dataload', e))
69
- }
70
- this.server = new U.ServerRequest()
71
- this.request = new U.Request()
72
-
73
- this.initLoader()
74
- this.name = this.options.name
75
- this.description = this.options.description
76
- this.demoTileInfos = this.options.demoTileInfos
77
- this.options.zoomControl = zoomControl !== undefined ? zoomControl : true
78
- this.options.fullscreenControl =
79
- fullscreenControl !== undefined ? fullscreenControl : true
80
-
81
- this.datalayersFromQueryString = L.Util.queryString('datalayers')
82
- if (this.datalayersFromQueryString) {
83
- this.datalayersFromQueryString = this.datalayersFromQueryString
84
- .toString()
85
- .split(',')
86
- }
87
-
88
- let editedFeature = null
89
- try {
90
- Object.defineProperty(this, 'editedFeature', {
91
- get: () => editedFeature,
92
- set: (feature) => {
93
- if (editedFeature && editedFeature !== feature) {
94
- editedFeature.endEdit()
95
- }
96
- editedFeature = feature
97
- this.fire('seteditedfeature')
98
- },
99
- })
100
- } catch (e) {
101
- // Certainly IE8, which has a limited version of defineProperty
102
- }
103
-
104
- // Retrocompat
105
- if (this.options.slideshow?.delay && this.options.slideshow.active === undefined) {
106
- this.options.slideshow.active = true
107
- }
108
- if (this.options.advancedFilterKey) {
109
- this.options.facetKey = this.options.advancedFilterKey
110
- delete this.options.advancedFilterKey
111
- }
112
-
113
- // Global storage for retrieving datalayers and features.
114
- this.datalayers = {} // All datalayers, including deleted.
115
- this.datalayers_index = [] // Datalayers actually on the map and ordered.
116
- this.features_index = {}
117
-
118
- // Needed for actions labels
119
- this.help = new U.Help(this)
120
-
121
- this.formatter = new U.Formatter(this)
122
-
123
- this.initControls()
124
- // Needs locate control and hash to exist
125
- this.initCenter()
126
- this.initTileLayers()
127
- // Needs tilelayer to exist for minimap
128
- this.renderControls()
129
- this.handleLimitBounds()
130
- this.initDataLayers()
131
-
132
- if (this.options.displayCaptionOnLoad) {
133
- // Retrocompat
134
- if (!this.options.onLoadPanel) {
135
- this.options.onLoadPanel = 'caption'
136
- }
137
- delete this.options.displayCaptionOnLoad
138
- }
139
- if (this.options.displayDataBrowserOnLoad) {
140
- // Retrocompat
141
- if (!this.options.onLoadPanel) {
142
- this.options.onLoadPanel = 'databrowser'
143
- }
144
- delete this.options.displayDataBrowserOnLoad
145
- }
146
- if (this.options.datalayersControl === 'expanded') {
147
- if (!this.options.onLoadPanel) {
148
- this.options.onLoadPanel = 'datalayers'
149
- }
150
- delete this.options.datalayersControl
151
- }
152
- if (this.options.onLoadPanel === 'facet') {
153
- this.options.onLoadPanel = 'datafilters'
154
- }
155
-
156
- let isDirty = false // self status
157
- try {
158
- Object.defineProperty(this, 'isDirty', {
159
- get: () => isDirty,
160
- set: function (status) {
161
- isDirty = status
162
- this.checkDirty()
163
- },
164
- })
165
- } catch (e) {
166
- // Certainly IE8, which has a limited version of defineProperty
167
- }
168
- this.on(
169
- 'baselayerchange',
170
- function (e) {
171
- if (this._controls.miniMap) this._controls.miniMap.onMainMapBaseLayerChange(e)
172
- },
173
- this
174
- )
175
-
176
- // Creation mode
177
- if (!this.options.umap_id) {
178
- if (!this.options.preview) {
179
- this.isDirty = true
180
- this.enableEdit()
181
- }
182
- this._default_extent = true
183
- this.options.name = L._('Untitled map')
184
- await this.loadDataFromQueryString()
185
- }
186
-
187
- this.slideshow = new U.Slideshow(this, this.options.slideshow)
188
- this.permissions = new U.MapPermissions(this)
189
- if (this.hasEditMode()) {
190
- this.editTools = new U.Editable(this)
191
- this.renderEditToolbar()
192
- }
193
-
194
- if (!this.options.noControl) {
195
- this.initShortcuts()
196
- this.initCaptionBar()
197
- this.on('contextmenu', this.onContextMenu)
198
- this.onceDataLoaded(this.setViewFromQueryString)
199
- this.on('click', this.closeInplaceToolbar)
200
- this.propagate()
201
- }
202
-
203
- window.onbeforeunload = () => (this.editEnabled && this.isDirty) || null
204
- this.backup()
205
- },
206
-
207
- initSyncEngine: async function () {
208
- if (this.options.websocketEnabled === false) return
209
- if (this.options.syncEnabled !== true) {
210
- this.sync.stop()
211
- } else {
212
- const ws_token_uri = this.urls.get('map_websocket_auth_token', {
213
- map_id: this.options.umap_id,
214
- })
215
- await this.sync.authenticate(ws_token_uri, this.options.websocketURI, this.server)
216
- }
217
- },
218
-
219
- getSyncMetadata: function () {
220
- return {
221
- engine: this.sync,
222
- subject: 'map',
223
- }
224
- },
225
-
226
- render: function (fields) {
227
- if (fields.includes('numberOfConnectedPeers')) {
228
- this.renderEditToolbar()
229
- this.propagate()
230
- }
231
-
232
- const impacts = U.Utils.getImpactsFromSchema(fields)
233
- for (const impact of impacts) {
234
- switch (impact) {
235
- case 'ui':
236
- this.initCaptionBar()
237
- this.renderEditToolbar()
238
- this.renderControls()
239
- this.browser.redraw()
240
- this.propagate()
241
- break
242
- case 'data':
243
- this.redrawVisibleDataLayers()
244
- break
245
- case 'datalayer-index':
246
- this.reindexDataLayers()
247
- break
248
- case 'background':
249
- this.initTileLayers()
250
- break
251
- case 'bounds':
252
- this.handleLimitBounds()
253
- break
254
- case 'sync':
255
- this.initSyncEngine()
256
- }
257
- }
258
- },
259
-
260
- reindexDataLayers: function () {
261
- this.eachDataLayer((datalayer) => datalayer.reindex())
262
- this.onDataLayersChanged()
263
- },
264
-
265
- redrawVisibleDataLayers: function () {
266
- this.eachVisibleDataLayer((datalayer) => {
267
- datalayer.redraw()
268
- })
269
- },
270
-
271
- setOptionsFromQueryString: (options) => {
272
- // This is not an editable option
273
- L.Util.setFromQueryString(options, 'editMode')
274
- // FIXME retrocompat
275
- L.Util.setBooleanFromQueryString(options, 'displayDataBrowserOnLoad')
276
- L.Util.setBooleanFromQueryString(options, 'displayCaptionOnLoad')
277
- for (const [key, schema] of Object.entries(U.SCHEMA)) {
278
- switch (schema.type) {
279
- case Boolean:
280
- if (schema.nullable) L.Util.setNullableBooleanFromQueryString(options, key)
281
- else L.Util.setBooleanFromQueryString(options, key)
282
- break
283
- case Number:
284
- L.Util.setNumberFromQueryString(options, key)
285
- break
286
- case String:
287
- L.Util.setFromQueryString(options, key)
288
- break
289
- }
290
- }
291
- // Specific case for datalayersControl
292
- // which accepts "expanded" value, on top of true/false/null
293
- if (L.Util.queryString('datalayersControl') === 'expanded') {
294
- if (!options.onLoadPanel) {
295
- options.onLoadPanel = 'datalayers'
296
- }
297
- }
298
- },
299
-
300
- loadDataFromQueryString: async function () {
301
- let data = L.Util.queryString('data', null)
302
- const url = new URL(window.location.href)
303
- const dataUrls = new URLSearchParams(url.search).getAll('dataUrl')
304
- const dataFormat = L.Util.queryString('dataFormat', 'geojson')
305
- if (dataUrls.length) {
306
- for (let dataUrl of dataUrls) {
307
- dataUrl = decodeURIComponent(dataUrl)
308
- dataUrl = this.localizeUrl(dataUrl)
309
- dataUrl = this.proxyUrl(dataUrl)
310
- const datalayer = this.createDataLayer()
311
- await datalayer.importFromUrl(dataUrl, dataFormat)
312
- }
313
- } else if (data) {
314
- data = decodeURIComponent(data)
315
- const datalayer = this.createDataLayer()
316
- await datalayer.importRaw(data, dataFormat)
317
- }
318
- },
319
-
320
- setViewFromQueryString: async function () {
321
- if (this.options.noControl) return
322
- if (L.Util.queryString('share')) {
323
- this.share.open()
324
- } else if (this.options.onLoadPanel === 'databrowser') {
325
- this.panel.setDefaultMode('expanded')
326
- this.openBrowser('data')
327
- } else if (this.options.onLoadPanel === 'datalayers') {
328
- this.panel.setDefaultMode('condensed')
329
- this.openBrowser('layers')
330
- } else if (this.options.onLoadPanel === 'datafilters') {
331
- this.panel.setDefaultMode('expanded')
332
- this.openBrowser('filters')
333
- } else if (this.options.onLoadPanel === 'caption') {
334
- this.panel.setDefaultMode('condensed')
335
- this.openCaption()
336
- }
337
- // Comes after default panels, so if it opens in a panel it will
338
- // take precedence.
339
- const slug = L.Util.queryString('feature')
340
- if (slug && this.features_index[slug]) this.features_index[slug].view()
341
- if (L.Util.queryString('edit')) {
342
- if (this.hasEditMode()) this.enableEdit()
343
- // Sometimes users share the ?edit link by mistake, let's remove
344
- // this search parameter from URL to prevent this
345
- const url = new URL(window.location)
346
- url.searchParams.delete('edit')
347
- history.pushState({}, '', url)
348
- }
349
- if (L.Util.queryString('download')) {
350
- const download_url = this.urls.get('map_download', {
351
- map_id: this.options.umap_id,
352
- })
353
- window.location = download_url
354
- }
355
- },
356
-
357
- // Merge the given schema with the default one
358
- // Missing keys inside the schema are merged with the default ones.
359
- overrideSchema: (schema) => {
360
- for (const [key, extra] of Object.entries(schema)) {
361
- U.SCHEMA[key] = L.extend({}, U.SCHEMA[key], extra)
362
- }
363
- },
364
-
365
- initControls: function () {
366
- this.helpMenuActions = {}
367
- this._controls = {}
368
-
369
- if (this.hasEditMode() && !this.options.noControl) {
370
- new U.EditControl(this).addTo(this)
371
-
372
- new U.DrawToolbar({ map: this }).addTo(this)
373
- const editActions = [
374
- U.EditCaptionAction,
375
- U.EditPropertiesAction,
376
- U.EditLayersAction,
377
- U.ChangeTileLayerAction,
378
- U.UpdateExtentAction,
379
- U.UpdatePermsAction,
380
- U.ImportAction,
381
- ]
382
- if (this.options.editMode === 'advanced') {
383
- new U.SettingsToolbar({ actions: editActions }).addTo(this)
384
- }
385
- }
386
- this._controls.zoom = new L.Control.Zoom({
387
- zoomInTitle: L._('Zoom in'),
388
- zoomOutTitle: L._('Zoom out'),
389
- })
390
- this._controls.datalayers = new U.DataLayersControl(this)
391
- this._controls.caption = new U.CaptionControl(this)
392
- this._controls.locate = new U.Locate(this, {
393
- strings: {
394
- title: L._('Center map on your location'),
395
- },
396
- showPopup: false,
397
- // We style this control in our own CSS for consistency with other controls,
398
- // but the control breaks if we don't specify a class here, so a fake class
399
- // will do.
400
- icon: 'umap-fake-class',
401
- iconLoading: 'umap-fake-class',
402
- flyTo: this.options.easing,
403
- onLocationError: (err) => U.Alert.error(err.message),
404
- })
405
- this._controls.fullscreen = new L.Control.Fullscreen({
406
- title: { false: L._('View Fullscreen'), true: L._('Exit Fullscreen') },
407
- })
408
- this._controls.search = new U.SearchControl()
409
- this._controls.embed = new L.Control.Embed(this)
410
- this._controls.tilelayersChooser = new U.TileLayerChooser(this)
411
- if (this.options.user?.id) this._controls.star = new U.StarControl(this)
412
- this._controls.editinosm = new L.Control.EditInOSM({
413
- position: 'topleft',
414
- widgetOptions: {
415
- helpText: L._(
416
- 'Open this map extent in a map editor to provide more accurate data to OpenStreetMap'
417
- ),
418
- },
419
- })
420
- this._controls.measure = new L.MeasureControl().initHandler(this)
421
- this._controls.more = new U.MoreControls()
422
- this._controls.scale = L.control.scale()
423
- this._controls.permanentCredit = new U.PermanentCreditsControl(this)
424
- if (this.options.scrollWheelZoom) this.scrollWheelZoom.enable()
425
- else this.scrollWheelZoom.disable()
426
- this.browser = new U.Browser(this)
427
- this.facets = new U.Facets(this)
428
- this.caption = new U.Caption(this)
429
- this.importer = new U.Importer(this)
430
- this.drop = new U.DropControl(this)
431
- this.share = new U.Share(this)
432
- this.rules = new U.Rules(this)
433
- this._controls.tilelayers = new U.TileLayerControl(this)
434
- },
435
-
436
- renderControls: function () {
437
- const hasSlideshow = Boolean(this.options.slideshow?.active)
438
- const barEnabled = this.options.captionBar || hasSlideshow
439
- document.body.classList.toggle('umap-caption-bar-enabled', barEnabled)
440
- document.body.classList.toggle('umap-slideshow-enabled', hasSlideshow)
441
- for (const control of Object.values(this._controls)) {
442
- this.removeControl(control)
443
- }
444
- if (this.options.noControl) return
445
-
446
- this._controls.attribution = new U.AttributionControl().addTo(this)
447
- if (this.options.miniMap) {
448
- this.whenReady(function () {
449
- if (this.selected_tilelayer) {
450
- this._controls.miniMap = new L.Control.MiniMap(this.selected_tilelayer, {
451
- aimingRectOptions: {
452
- color: this.getOption('color'),
453
- fillColor: this.getOption('fillColor'),
454
- stroke: this.getOption('stroke'),
455
- fill: this.getOption('fill'),
456
- weight: this.getOption('weight'),
457
- opacity: this.getOption('opacity'),
458
- fillOpacity: this.getOption('fillOpacity'),
459
- },
460
- }).addTo(this)
461
- this._controls.miniMap._miniMap.invalidateSize()
462
- }
463
- })
464
- }
465
- let name
466
- let status
467
- let control
468
- for (let i = 0; i < this.HIDDABLE_CONTROLS.length; i++) {
469
- name = this.HIDDABLE_CONTROLS[i]
470
- status = this.getOption(`${name}Control`)
471
- if (status === false) continue
472
- control = this._controls[name]
473
- if (!control) continue
474
- control.addTo(this)
475
- if (status === undefined || status === null)
476
- L.DomUtil.addClass(control._container, 'display-on-more')
477
- else L.DomUtil.removeClass(control._container, 'display-on-more')
478
- }
479
- if (this.getOption('permanentCredit')) this._controls.permanentCredit.addTo(this)
480
- if (this.getOption('moreControl')) this._controls.more.addTo(this)
481
- if (this.getOption('scaleControl')) this._controls.scale.addTo(this)
482
- this._controls.tilelayers.setLayers()
483
- },
484
-
485
- initDataLayers: async function (datalayers) {
486
- datalayers = datalayers || this.options.datalayers
487
- for (const options of datalayers) {
488
- // `false` to not propagate syncing elements served from uMap
489
- this.createDataLayer(options, false)
490
- }
491
- await this.loadDataLayers()
492
- },
493
-
494
- loadDataLayers: async function () {
495
- this.datalayersLoaded = true
496
- this.fire('datalayersloaded')
497
- for (const datalayer of this.datalayers_index) {
498
- if (datalayer.showAtLoad()) await datalayer.show()
499
- }
500
- this.dataloaded = true
501
- this.fire('dataloaded')
502
- },
503
-
504
- indexDatalayers: function () {
505
- const panes = this.getPane('overlayPane')
506
- let pane
507
- this.datalayers_index = []
508
- for (let i = 0; i < panes.children.length; i++) {
509
- pane = panes.children[i]
510
- if (!pane.dataset || !pane.dataset.id) continue
511
- this.datalayers_index.push(this.datalayers[pane.dataset.id])
512
- }
513
- this.onDataLayersChanged()
514
- },
515
-
516
- onDataLayersChanged: function () {
517
- if (this.browser) this.browser.update()
518
- this.caption.refresh()
519
- },
520
-
521
- ensurePanesOrder: function () {
522
- this.eachDataLayer((datalayer) => {
523
- datalayer.bringToTop()
524
- })
525
- },
526
-
527
- onceDatalayersLoaded: function (callback, context) {
528
- // Once datalayers **metadata** have been loaded
529
- if (this.datalayersLoaded) {
530
- callback.call(context || this, this)
531
- } else {
532
- this.once('datalayersloaded', callback, context)
533
- }
534
- return this
535
- },
536
-
537
- onceDataLoaded: function (callback, context) {
538
- // Once datalayers **data** have been loaded
539
- if (this.dataloaded) {
540
- callback.call(context || this, this)
541
- } else {
542
- this.once('dataloaded', callback, context)
543
- }
544
- return this
545
- },
546
-
547
- backupOptions: function () {
548
- this._backupOptions = L.extend({}, this.options)
549
- this._backupOptions.tilelayer = L.extend({}, this.options.tilelayer)
550
- this._backupOptions.limitBounds = L.extend({}, this.options.limitBounds)
551
- this._backupOptions.permissions = L.extend({}, this.permissions.options)
552
- },
553
-
554
- resetOptions: function () {
555
- this.options = L.extend({}, this._backupOptions)
556
- this.options.tilelayer = L.extend({}, this._backupOptions.tilelayer)
557
- this.permissions.options = L.extend({}, this._backupOptions.permissions)
558
- },
559
-
560
- initShortcuts: function () {
561
- const globalShortcuts = function (e) {
562
- if (e.key === 'Escape') {
563
- if (this.importer.dialog.visible) {
564
- this.importer.dialog.close()
565
- } else if (this.editEnabled && this.editTools.drawing()) {
566
- this.editTools.onEscape()
567
- } else if (this.measureTools.enabled()) {
568
- this.measureTools.stopDrawing()
569
- } else if (this.fullPanel?.isOpen()) {
570
- this.fullPanel?.close()
571
- } else if (this.editPanel?.isOpen()) {
572
- this.editPanel?.close()
573
- } else if (this.panel.isOpen()) {
574
- this.panel.close()
575
- }
576
- }
577
-
578
- // From now on, only ctrl/meta shortcut
579
- if (!(e.ctrlKey || e.metaKey) || e.shiftKey) return
580
-
581
- if (e.key === 'f') {
582
- L.DomEvent.stop(e)
583
- this.search()
584
- }
585
-
586
- /* Edit mode only shortcuts */
587
- if (!this.hasEditMode()) return
588
-
589
- // Edit mode Off
590
- if (!this.editEnabled) {
591
- switch (e.key) {
592
- case 'e':
593
- L.DomEvent.stop(e)
594
- this.enableEdit()
595
- break
596
- }
597
- return
598
- }
599
-
600
- // Edit mode on
601
- let used = true
602
- switch (e.key) {
603
- case 'e':
604
- if (!this.isDirty) this.disableEdit()
605
- break
606
- case 's':
607
- if (this.isDirty) this.save()
608
- break
609
- case 'z':
610
- if (this.isDirty) this.askForReset()
611
- break
612
- case 'm':
613
- this.editTools.startMarker()
614
- break
615
- case 'p':
616
- this.editTools.startPolygon()
617
- break
618
- case 'l':
619
- this.editTools.startPolyline()
620
- break
621
- case 'i':
622
- this.importer.open()
623
- break
624
- case 'o':
625
- this.importer.openFiles()
626
- break
627
- case 'h':
628
- this.help.show('edit')
629
- break
630
- default:
631
- used = false
632
- }
633
- if (used) L.DomEvent.stop(e)
634
- }
635
- L.DomEvent.addListener(document, 'keydown', globalShortcuts, this)
636
- },
637
-
638
- initTileLayers: function () {
639
- this.tilelayers = []
640
- for (const props of this.options.tilelayers) {
641
- const layer = this.createTileLayer(props)
642
- this.tilelayers.push(layer)
643
- if (
644
- this.options.tilelayer &&
645
- this.options.tilelayer.url_template === props.url_template
646
- ) {
647
- // Keep control over the displayed attribution for non custom tilelayers
648
- this.options.tilelayer.attribution = props.attribution
649
- }
650
- }
651
- if (this.options.tilelayer?.url_template && this.options.tilelayer.attribution) {
652
- this.customTilelayer = this.createTileLayer(this.options.tilelayer)
653
- this.selectTileLayer(this.customTilelayer)
654
- } else {
655
- this.selectTileLayer(this.tilelayers[0])
656
- }
657
- if (this._controls) this._controls.tilelayers.setLayers()
658
- },
659
-
660
- createTileLayer: (tilelayer) => new L.TileLayer(tilelayer.url_template, tilelayer),
661
-
662
- selectTileLayer: function (tilelayer) {
663
- if (tilelayer === this.selected_tilelayer) {
664
- return
665
- }
666
- try {
667
- this.addLayer(tilelayer)
668
- this.fire('baselayerchange', { layer: tilelayer })
669
- if (this.selected_tilelayer) {
670
- this.removeLayer(this.selected_tilelayer)
671
- }
672
- this.selected_tilelayer = tilelayer
673
- if (
674
- !Number.isNaN(this.selected_tilelayer.options.minZoom) &&
675
- this.getZoom() < this.selected_tilelayer.options.minZoom
676
- ) {
677
- this.setZoom(this.selected_tilelayer.options.minZoom)
678
- }
679
- if (
680
- !Number.isNaN(this.selected_tilelayer.options.maxZoom) &&
681
- this.getZoom() > this.selected_tilelayer.options.maxZoom
682
- ) {
683
- this.setZoom(this.selected_tilelayer.options.maxZoom)
684
- }
685
- } catch (e) {
686
- console.error(e)
687
- this.removeLayer(tilelayer)
688
- U.Alert.error(`${L._('Error in the tilelayer URL')}: ${tilelayer._url}`)
689
- // Users can put tilelayer URLs by hand, and if they add wrong {variable},
690
- // Leaflet throw an error, and then the map is no more editable
691
- }
692
- this.setOverlay()
693
- },
694
-
695
- eachTileLayer: function (callback, context) {
696
- const urls = []
697
- const callOne = (layer) => {
698
- // Prevent adding a duplicate background,
699
- // while adding selected/custom on top of the list
700
- const url = layer.options.url_template
701
- if (urls.indexOf(url) !== -1) return
702
- callback.call(context, layer)
703
- urls.push(url)
704
- }
705
- if (this.selected_tilelayer) callOne(this.selected_tilelayer)
706
- if (this.customTilelayer) callOne(this.customTilelayer)
707
- this.tilelayers.forEach(callOne)
708
- },
709
-
710
- setOverlay: function () {
711
- if (!this.options.overlay || !this.options.overlay.url_template) return
712
- const overlay = this.createTileLayer(this.options.overlay)
713
- try {
714
- this.addLayer(overlay)
715
- if (this.overlay) this.removeLayer(this.overlay)
716
- this.overlay = overlay
717
- } catch (e) {
718
- this.removeLayer(overlay)
719
- console.error(e)
720
- U.Alert.error(`${L._('Error in the overlay URL')}: ${overlay._url}`)
721
- }
722
- },
723
-
724
- _setDefaultCenter: function () {
725
- this.options.center = this.latLng(this.options.center)
726
- this.setView(this.options.center, this.options.zoom)
727
- },
728
-
729
- hasData: function () {
730
- for (const datalayer of this.datalayers_index) {
731
- if (datalayer.hasData()) return true
732
- }
733
- },
734
-
735
- hasLayers: function () {
736
- return Boolean(this.datalayers_index.length)
737
- },
738
-
739
- fitDataBounds: function () {
740
- const bounds = this.getLayersBounds()
741
- if (!this.hasData() || !bounds.isValid()) return false
742
- this.fitBounds(bounds)
743
- },
744
-
745
- initCenter: function () {
746
- this._setDefaultCenter()
747
- if (this.options.hash) this.addHash()
748
- if (this.options.hash && this._hash.parseHash(location.hash)) {
749
- // FIXME An invalid hash will cause the load to fail
750
- this._hash.update()
751
- } else if (this.options.defaultView === 'locate' && !this.options.noControl) {
752
- this._controls.locate.start()
753
- } else if (this.options.defaultView === 'data') {
754
- this.onceDataLoaded(this.fitDataBounds)
755
- } else if (this.options.defaultView === 'latest') {
756
- this.onceDataLoaded(() => {
757
- if (!this.hasData()) return
758
- const datalayer = this.firstVisibleDatalayer()
759
- let feature
760
- if (datalayer) {
761
- const feature = datalayer.getFeatureByIndex(-1)
762
- if (feature) {
763
- feature.zoomTo({ callback: this.options.noControl ? null : feature.view })
764
- return
765
- }
766
- }
767
- })
768
- }
769
- },
770
-
771
- latLng: (a, b, c) => {
772
- // manage geojson case and call original method
773
- if (!(a instanceof L.LatLng) && a.coordinates) {
774
- // Guess it's a geojson
775
- a = [a.coordinates[1], a.coordinates[0]]
776
- }
777
- return L.latLng(a, b, c)
778
- },
779
-
780
- handleLimitBounds: function () {
781
- const south = Number.parseFloat(this.options.limitBounds.south)
782
- const west = Number.parseFloat(this.options.limitBounds.west)
783
- const north = Number.parseFloat(this.options.limitBounds.north)
784
- const east = Number.parseFloat(this.options.limitBounds.east)
785
- if (
786
- !Number.isNaN(south) &&
787
- !Number.isNaN(west) &&
788
- !Number.isNaN(north) &&
789
- !Number.isNaN(east)
790
- ) {
791
- const bounds = L.latLngBounds([
792
- [south, west],
793
- [north, east],
794
- ])
795
- this.options.minZoom = this.getBoundsZoom(bounds, false)
796
- try {
797
- this.setMaxBounds(bounds)
798
- } catch (e) {
799
- // Unusable bounds, like -2 -2 -2 -2?
800
- console.error('Error limiting bounds', e)
801
- }
802
- } else {
803
- this.options.minZoom = 0
804
- this.setMaxBounds()
805
- }
806
- },
807
-
808
- setMaxBounds: function (bounds) {
809
- // Hack. Remove me when fix is released:
810
- // https://github.com/Leaflet/Leaflet/pull/4494
811
- bounds = L.latLngBounds(bounds)
812
-
813
- if (!bounds.isValid()) {
814
- this.options.maxBounds = null
815
- return this.off('moveend', this._panInsideMaxBounds)
816
- }
817
- return L.Map.prototype.setMaxBounds.call(this, bounds)
818
- },
819
-
820
- createDataLayer: function (options = {}, sync = true) {
821
- options.name = options.name || `${L._('Layer')} ${this.datalayers_index.length + 1}`
822
- const datalayer = new U.DataLayer(this, options, sync)
823
-
824
- if (sync !== false) {
825
- datalayer.sync.upsert(datalayer.options)
826
- }
827
- return datalayer
828
- },
829
-
830
- newDataLayer: function () {
831
- const datalayer = this.createDataLayer({})
832
- datalayer.edit()
833
- },
834
-
835
- getDefaultOption: (option) => U.SCHEMA[option]?.default,
836
-
837
- getOption: function (option, feature) {
838
- if (feature) {
839
- const value = this.rules.getOption(option, feature)
840
- if (value !== undefined) return value
841
- }
842
- if (U.Utils.usableOption(this.options, option)) return this.options[option]
843
- return this.getDefaultOption(option)
844
- },
845
-
846
- setCenterAndZoom: function () {
847
- this._setCenterAndZoom()
848
- U.Alert.success(L._('The zoom and center have been modified.'))
849
- },
850
-
851
- _setCenterAndZoom: function () {
852
- this.options.center = this.getCenter()
853
- this.options.zoom = this.getZoom()
854
- this.isDirty = true
855
- this._default_extent = false
856
- },
857
-
858
- updateTileLayers: function () {
859
- const callback = (tilelayer) => {
860
- this.options.tilelayer = tilelayer.toJSON()
861
- this.isDirty = true
862
- }
863
- if (this._controls.tilelayersChooser) {
864
- this._controls.tilelayersChooser.openSwitcher({ callback, edit: true })
865
- }
866
- },
867
-
868
- toGeoJSON: function () {
869
- let features = []
870
- this.eachDataLayer((datalayer) => {
871
- if (datalayer.isVisible()) {
872
- features = features.concat(datalayer.featuresToGeoJSON())
873
- }
874
- })
875
- const geojson = {
876
- type: 'FeatureCollection',
877
- features: features,
878
- }
879
- return geojson
880
- },
881
-
882
- eachFeature: function (callback, context) {
883
- this.eachBrowsableDataLayer((datalayer) => {
884
- if (datalayer.isVisible()) datalayer.eachFeature(callback, context)
885
- })
886
- },
887
-
888
- processFileToImport: function (file, layer, type) {
889
- type = type || U.Utils.detectFileType(file)
890
- if (!type) {
891
- U.Alert.error(
892
- L._('Unable to detect format of file {filename}', {
893
- filename: file.name,
894
- })
895
- )
896
- return
897
- }
898
- if (type === 'umap') {
899
- this.importFromFile(file, 'umap')
900
- } else {
901
- if (!layer) layer = this.createDataLayer({ name: file.name })
902
- layer.importFromFile(file, type)
903
- }
904
- },
905
-
906
- importFromUrl: async function (uri) {
907
- const response = await this.request.get(uri)
908
- if (response?.ok) {
909
- this.importRaw(await response.text())
910
- }
911
- },
912
-
913
- importRaw: function (rawData) {
914
- const importedData = JSON.parse(rawData)
915
-
916
- let mustReindex = false
917
-
918
- for (const option of Object.keys(U.SCHEMA)) {
919
- if (typeof importedData.properties[option] !== 'undefined') {
920
- this.options[option] = importedData.properties[option]
921
- if (option === 'sortKey') mustReindex = true
922
- }
923
- }
924
-
925
- if (importedData.geometry) this.options.center = this.latLng(importedData.geometry)
926
- importedData.layers.forEach((geojson) => {
927
- if (!geojson._umap_options && geojson._storage) {
928
- geojson._umap_options = geojson._storage
929
- delete geojson._storage
930
- }
931
- delete geojson._umap_options?.id // Never trust an id at this stage
932
- const dataLayer = this.createDataLayer(geojson._umap_options)
933
- dataLayer.fromUmapGeoJSON(geojson)
934
- })
935
-
936
- this.initTileLayers()
937
- this.renderControls()
938
- this.handleLimitBounds()
939
- this.eachDataLayer((datalayer) => {
940
- if (mustReindex) datalayer.reindex()
941
- datalayer.redraw()
942
- })
943
- this.propagate()
944
- this.fire('postsync')
945
- this.isDirty = true
946
- },
947
-
948
- importFromFile: function (file) {
949
- const reader = new FileReader()
950
- reader.readAsText(file)
951
- reader.onload = (e) => {
952
- const rawData = e.target.result
953
- try {
954
- this.importRaw(rawData)
955
- } catch (e) {
956
- console.error('Error importing data', e)
957
- U.Alert.error(L._('Invalid umap data in {filename}', { filename: file.name }))
958
- }
959
- }
960
- },
961
-
962
- openBrowser: function (mode) {
963
- this.onceDatalayersLoaded(() => this.browser.open(mode))
964
- },
965
-
966
- openCaption: function () {
967
- this.onceDatalayersLoaded(() => this.caption.open())
968
- },
969
-
970
- eachDataLayer: function (method, context) {
971
- for (let i = 0; i < this.datalayers_index.length; i++) {
972
- method.call(context, this.datalayers_index[i])
973
- }
974
- },
975
-
976
- eachDataLayerReverse: function (method, context, filter) {
977
- for (let i = this.datalayers_index.length - 1; i >= 0; i--) {
978
- if (filter && !filter.call(context, this.datalayers_index[i])) continue
979
- method.call(context, this.datalayers_index[i])
980
- }
981
- },
982
-
983
- eachBrowsableDataLayer: function (method, context) {
984
- this.eachDataLayerReverse(method, context, (d) => d.allowBrowse())
985
- },
986
-
987
- eachVisibleDataLayer: function (method, context) {
988
- this.eachDataLayerReverse(method, context, (d) => d.isVisible())
989
- },
990
-
991
- findDataLayer: function (method, context) {
992
- for (let i = this.datalayers_index.length - 1; i >= 0; i--) {
993
- if (method.call(context, this.datalayers_index[i]))
994
- return this.datalayers_index[i]
995
- }
996
- },
997
-
998
- backup: function () {
999
- this.backupOptions()
1000
- this._datalayers_index_bk = [].concat(this.datalayers_index)
1001
- },
1002
-
1003
- reset: function () {
1004
- if (this.editTools) this.editTools.stopDrawing()
1005
- this.resetOptions()
1006
- this.datalayers_index = [].concat(this._datalayers_index_bk)
1007
- // Iter over all datalayers, including deleted if any.
1008
- for (const datalayer of Object.values(this.datalayers)) {
1009
- if (datalayer.isDeleted) datalayer.connectToMap()
1010
- if (datalayer.isDirty) datalayer.reset()
1011
- }
1012
- this.ensurePanesOrder()
1013
- this.initTileLayers()
1014
- this.isDirty = false
1015
- this.onDataLayersChanged()
1016
- },
1017
-
1018
- checkDirty: function () {
1019
- this._container.classList.toggle('umap-is-dirty', this.isDirty)
1020
- },
1021
-
1022
- exportOptions: function () {
1023
- const properties = {}
1024
- for (const option of Object.keys(U.SCHEMA)) {
1025
- if (typeof this.options[option] !== 'undefined') {
1026
- properties[option] = this.options[option]
1027
- }
1028
- }
1029
- return properties
1030
- },
1031
-
1032
- saveSelf: async function () {
1033
- this.rules.commit()
1034
- const geojson = {
1035
- type: 'Feature',
1036
- geometry: this.geometry(),
1037
- properties: this.exportOptions(),
1038
- }
1039
- const formData = new FormData()
1040
- formData.append('name', this.options.name)
1041
- formData.append('center', JSON.stringify(this.geometry()))
1042
- formData.append('settings', JSON.stringify(geojson))
1043
- const uri = this.urls.get('map_save', { map_id: this.options.umap_id })
1044
- const [data, _, error] = await this.server.post(uri, {}, formData)
1045
- // FIXME: login_required response will not be an error, so it will not
1046
- // stop code while it should
1047
- if (error) {
1048
- return
1049
- }
1050
- if (data.login_required) {
1051
- window.onLogin = () => this.save()
1052
- window.open(data.login_required)
1053
- return
1054
- }
1055
- this.options.user = data.user
1056
- if (!this.options.umap_id) {
1057
- this.options.umap_id = data.id
1058
- this.permissions.setOptions(data.permissions)
1059
- this.permissions.commit()
1060
- if (data.permissions?.anonymous_edit_url) {
1061
- this.once('saved', () => {
1062
- U.AlertCreation.info(
1063
- L._('Your map has been created with an anonymous account!'),
1064
- Number.Infinity,
1065
- data.permissions.anonymous_edit_url,
1066
- this.options.urls.map_send_edit_link
1067
- ? this.sendEditLinkEmail.bind(this)
1068
- : null
1069
- )
1070
- })
1071
- } else {
1072
- this.once('saved', () => {
1073
- U.Alert.success(L._('Congratulations, your map has been created!'))
1074
- })
1075
- }
1076
- } else {
1077
- if (!this.permissions.isDirty) {
1078
- // Do not override local changes to permissions,
1079
- // but update in case some other editors changed them in the meantime.
1080
- this.permissions.setOptions(data.permissions)
1081
- this.permissions.commit()
1082
- }
1083
- this.once('saved', () => {
1084
- U.Alert.success(data.info || L._('Map has been saved!'))
1085
- })
1086
- }
1087
- // Update URL in case the name has changed.
1088
- if (history?.pushState) {
1089
- history.pushState({}, this.options.name, data.url)
1090
- } else {
1091
- window.location = data.url
1092
- }
1093
- return true
1094
- },
1095
-
1096
- save: async function () {
1097
- if (!this.isDirty) return
1098
- if (this._default_extent) this._setCenterAndZoom()
1099
- this.backup()
1100
- if (this.options.editMode === 'advanced') {
1101
- // Only save the map if the user has the rights to do so.
1102
- const ok = await this.saveSelf()
1103
- if (!ok) return
1104
- }
1105
- await this.permissions.save()
1106
- // Iter over all datalayers, including deleted.
1107
- for (const datalayer of Object.values(this.datalayers)) {
1108
- if (datalayer.isDirty) await datalayer.save()
1109
- }
1110
- this.isDirty = false
1111
- // Do a blind render for now, as we are not sure what could
1112
- // have changed, we'll be more subtil when we'll remove the
1113
- // save action
1114
- this.render(['name', 'user', 'permissions'])
1115
- this.fire('saved')
1116
- },
1117
-
1118
- propagate: function () {
1119
- let els = document.querySelectorAll('.map-name')
1120
- for (const el of els) {
1121
- el.textContent = this.getDisplayName()
1122
- }
1123
- const status = this.permissions.getShareStatusDisplay()
1124
- els = document.querySelectorAll('.share-status')
1125
- for (const el of els) {
1126
- if (status) {
1127
- el.textContent = L._('Visibility: {status}', {
1128
- status: status,
1129
- })
1130
- }
1131
- }
1132
- },
1133
-
1134
- star: async function () {
1135
- if (!this.options.umap_id) {
1136
- return U.Alert.error(L._('Please save the map first'))
1137
- }
1138
- const url = this.urls.get('map_star', { map_id: this.options.umap_id })
1139
- const [data, response, error] = await this.server.post(url)
1140
- if (error) {
1141
- return
1142
- }
1143
- this.options.starred = data.starred
1144
- U.Alert.success(
1145
- data.starred ? L._('Map has been starred') : L._('Map has been unstarred')
1146
- )
1147
- this.renderControls()
1148
- },
1149
-
1150
- geometry: function () {
1151
- /* Return a GeoJSON geometry Object */
1152
- const latlng = this.latLng(this.options.center || this.getCenter())
1153
- return {
1154
- type: 'Point',
1155
- coordinates: [latlng.lng, latlng.lat],
1156
- }
1157
- },
1158
-
1159
- firstVisibleDatalayer: function () {
1160
- return this.findDataLayer((datalayer) => {
1161
- if (datalayer.isVisible()) return true
1162
- })
1163
- },
1164
-
1165
- // TODO: allow to control the default datalayer
1166
- // (edit and viewing)
1167
- // cf https://github.com/umap-project/umap/issues/585
1168
- defaultEditDataLayer: function () {
1169
- let datalayer
1170
- let fallback
1171
- datalayer = this.lastUsedDataLayer
1172
- if (
1173
- datalayer &&
1174
- !datalayer.isDataReadOnly() &&
1175
- datalayer.isBrowsable() &&
1176
- datalayer.isVisible()
1177
- ) {
1178
- return datalayer
1179
- }
1180
- datalayer = this.findDataLayer((datalayer) => {
1181
- if (!datalayer.isDataReadOnly() && datalayer.isBrowsable()) {
1182
- fallback = datalayer
1183
- if (datalayer.isVisible()) return true
1184
- }
1185
- })
1186
- if (datalayer) return datalayer
1187
- if (fallback) {
1188
- // No datalayer visible, let's force one
1189
- fallback.show()
1190
- return fallback
1191
- }
1192
- return this.createDataLayer()
1193
- },
1194
-
1195
- getDataLayerByUmapId: function (umap_id) {
1196
- return this.findDataLayer((d) => d.umap_id === umap_id)
1197
- },
1198
-
1199
- _editControls: function (container) {
1200
- let UIFields = []
1201
- for (let i = 0; i < this.HIDDABLE_CONTROLS.length; i++) {
1202
- UIFields.push(`options.${this.HIDDABLE_CONTROLS[i]}Control`)
1203
- }
1204
- UIFields = UIFields.concat([
1205
- 'options.moreControl',
1206
- 'options.scrollWheelZoom',
1207
- 'options.miniMap',
1208
- 'options.scaleControl',
1209
- 'options.onLoadPanel',
1210
- 'options.defaultView',
1211
- 'options.displayPopupFooter',
1212
- 'options.captionBar',
1213
- 'options.captionMenus',
1214
- ])
1215
- builder = new U.FormBuilder(this, UIFields)
1216
- const controlsOptions = L.DomUtil.createFieldset(
1217
- container,
1218
- L._('User interface options')
1219
- )
1220
- controlsOptions.appendChild(builder.build())
1221
- },
1222
-
1223
- _editShapeProperties: function (container) {
1224
- const shapeOptions = [
1225
- 'options.color',
1226
- 'options.iconClass',
1227
- 'options.iconUrl',
1228
- 'options.iconOpacity',
1229
- 'options.opacity',
1230
- 'options.weight',
1231
- 'options.fill',
1232
- 'options.fillColor',
1233
- 'options.fillOpacity',
1234
- 'options.smoothFactor',
1235
- 'options.dashArray',
1236
- ]
1237
-
1238
- builder = new U.FormBuilder(this, shapeOptions)
1239
- const defaultShapeProperties = L.DomUtil.createFieldset(
1240
- container,
1241
- L._('Default shape properties')
1242
- )
1243
- defaultShapeProperties.appendChild(builder.build())
1244
- },
1245
-
1246
- _editDefaultProperties: function (container) {
1247
- const optionsFields = [
1248
- 'options.zoomTo',
1249
- 'options.easing',
1250
- 'options.labelKey',
1251
- 'options.sortKey',
1252
- 'options.filterKey',
1253
- 'options.facetKey',
1254
- 'options.slugKey',
1255
- ]
1256
-
1257
- builder = new U.FormBuilder(this, optionsFields)
1258
- const defaultProperties = L.DomUtil.createFieldset(
1259
- container,
1260
- L._('Default properties')
1261
- )
1262
- defaultProperties.appendChild(builder.build())
1263
- },
1264
-
1265
- _editInteractionsProperties: function (container) {
1266
- const popupFields = [
1267
- 'options.popupShape',
1268
- 'options.popupTemplate',
1269
- 'options.popupContentTemplate',
1270
- 'options.showLabel',
1271
- 'options.labelDirection',
1272
- 'options.labelInteractive',
1273
- 'options.outlinkTarget',
1274
- ]
1275
- builder = new U.FormBuilder(this, popupFields)
1276
- const popupFieldset = L.DomUtil.createFieldset(
1277
- container,
1278
- L._('Default interaction options')
1279
- )
1280
- popupFieldset.appendChild(builder.build())
1281
- },
1282
-
1283
- _editTilelayer: function (container) {
1284
- if (!U.Utils.isObject(this.options.tilelayer)) {
1285
- this.options.tilelayer = {}
1286
- }
1287
- const tilelayerFields = [
1288
- [
1289
- 'options.tilelayer.name',
1290
- { handler: 'BlurInput', placeholder: L._('display name') },
1291
- ],
1292
- [
1293
- 'options.tilelayer.url_template',
1294
- {
1295
- handler: 'BlurInput',
1296
- helpText: `${L._('Supported scheme')}: http://{s}.domain.com/{z}/{x}/{y}.png`,
1297
- placeholder: 'url',
1298
- type: 'url',
1299
- },
1300
- ],
1301
- [
1302
- 'options.tilelayer.maxZoom',
1303
- {
1304
- handler: 'BlurIntInput',
1305
- placeholder: L._('max zoom'),
1306
- min: 0,
1307
- max: this.options.maxZoomLimit,
1308
- },
1309
- ],
1310
- [
1311
- 'options.tilelayer.minZoom',
1312
- {
1313
- handler: 'BlurIntInput',
1314
- placeholder: L._('min zoom'),
1315
- min: 0,
1316
- max: this.options.maxZoomLimit,
1317
- },
1318
- ],
1319
- [
1320
- 'options.tilelayer.attribution',
1321
- { handler: 'BlurInput', placeholder: L._('attribution') },
1322
- ],
1323
- ['options.tilelayer.tms', { handler: 'Switch', label: L._('TMS format') }],
1324
- ]
1325
- const customTilelayer = L.DomUtil.createFieldset(
1326
- container,
1327
- L._('Custom background')
1328
- )
1329
- builder = new U.FormBuilder(this, tilelayerFields)
1330
- customTilelayer.appendChild(builder.build())
1331
- },
1332
-
1333
- _editOverlay: function (container) {
1334
- if (!U.Utils.isObject(this.options.overlay)) {
1335
- this.options.overlay = {}
1336
- }
1337
- const overlayFields = [
1338
- [
1339
- 'options.overlay.url_template',
1340
- {
1341
- handler: 'BlurInput',
1342
- helpText: `${L._('Supported scheme')}: http://{s}.domain.com/{z}/{x}/{y}.png`,
1343
- placeholder: 'url',
1344
- label: L._('Background overlay url'),
1345
- type: 'url',
1346
- },
1347
- ],
1348
- [
1349
- 'options.overlay.maxZoom',
1350
- {
1351
- handler: 'BlurIntInput',
1352
- placeholder: L._('max zoom'),
1353
- min: 0,
1354
- max: this.options.maxZoomLimit,
1355
- },
1356
- ],
1357
- [
1358
- 'options.overlay.minZoom',
1359
- {
1360
- handler: 'BlurIntInput',
1361
- placeholder: L._('min zoom'),
1362
- min: 0,
1363
- max: this.options.maxZoomLimit,
1364
- },
1365
- ],
1366
- [
1367
- 'options.overlay.attribution',
1368
- { handler: 'BlurInput', placeholder: L._('attribution') },
1369
- ],
1370
- [
1371
- 'options.overlay.opacity',
1372
- { handler: 'Range', min: 0, max: 1, step: 0.1, label: L._('Opacity') },
1373
- ],
1374
- ['options.overlay.tms', { handler: 'Switch', label: L._('TMS format') }],
1375
- ]
1376
- const overlay = L.DomUtil.createFieldset(container, L._('Custom overlay'))
1377
- builder = new U.FormBuilder(this, overlayFields)
1378
- overlay.appendChild(builder.build())
1379
- },
1380
-
1381
- _editBounds: function (container) {
1382
- if (!U.Utils.isObject(this.options.limitBounds)) {
1383
- this.options.limitBounds = {}
1384
- }
1385
- const limitBounds = L.DomUtil.createFieldset(container, L._('Limit bounds'))
1386
- const boundsFields = [
1387
- [
1388
- 'options.limitBounds.south',
1389
- { handler: 'BlurFloatInput', placeholder: L._('max South') },
1390
- ],
1391
- [
1392
- 'options.limitBounds.west',
1393
- { handler: 'BlurFloatInput', placeholder: L._('max West') },
1394
- ],
1395
- [
1396
- 'options.limitBounds.north',
1397
- { handler: 'BlurFloatInput', placeholder: L._('max North') },
1398
- ],
1399
- [
1400
- 'options.limitBounds.east',
1401
- { handler: 'BlurFloatInput', placeholder: L._('max East') },
1402
- ],
1403
- ]
1404
- const boundsBuilder = new U.FormBuilder(this, boundsFields)
1405
- limitBounds.appendChild(boundsBuilder.build())
1406
- const boundsButtons = L.DomUtil.create('div', 'button-bar half', limitBounds)
1407
- L.DomUtil.createButton(
1408
- 'button',
1409
- boundsButtons,
1410
- L._('Use current bounds'),
1411
- function () {
1412
- const bounds = this.getBounds()
1413
- this.options.limitBounds.south = L.Util.formatNum(bounds.getSouth())
1414
- this.options.limitBounds.west = L.Util.formatNum(bounds.getWest())
1415
- this.options.limitBounds.north = L.Util.formatNum(bounds.getNorth())
1416
- this.options.limitBounds.east = L.Util.formatNum(bounds.getEast())
1417
- boundsBuilder.fetchAll()
1418
-
1419
- this.sync.update(this, 'options.limitBounds', this.options.limitBounds)
1420
- this.isDirty = true
1421
- this.handleLimitBounds()
1422
- },
1423
- this
1424
- )
1425
- L.DomUtil.createButton(
1426
- 'button',
1427
- boundsButtons,
1428
- L._('Empty'),
1429
- function () {
1430
- this.options.limitBounds.south = null
1431
- this.options.limitBounds.west = null
1432
- this.options.limitBounds.north = null
1433
- this.options.limitBounds.east = null
1434
- boundsBuilder.fetchAll()
1435
- this.isDirty = true
1436
- this.handleLimitBounds()
1437
- },
1438
- this
1439
- )
1440
- },
1441
-
1442
- _editSlideshow: function (container) {
1443
- const slideshow = L.DomUtil.createFieldset(container, L._('Slideshow'))
1444
- const slideshowFields = [
1445
- [
1446
- 'options.slideshow.active',
1447
- { handler: 'Switch', label: L._('Activate slideshow mode') },
1448
- ],
1449
- [
1450
- 'options.slideshow.delay',
1451
- {
1452
- handler: 'SlideshowDelay',
1453
- helpText: L._('Delay between two transitions when in play mode'),
1454
- },
1455
- ],
1456
- [
1457
- 'options.slideshow.easing',
1458
- { handler: 'Switch', label: L._('Animated transitions'), inheritable: true },
1459
- ],
1460
- [
1461
- 'options.slideshow.autoplay',
1462
- { handler: 'Switch', label: L._('Autostart when map is loaded') },
1463
- ],
1464
- ]
1465
- const slideshowBuilder = new U.FormBuilder(this, slideshowFields, {
1466
- callback: () => this.slideshow.setOptions(this.options.slideshow),
1467
- })
1468
- slideshow.appendChild(slideshowBuilder.build())
1469
- },
1470
-
1471
- _editSync: function (container) {
1472
- const sync = L.DomUtil.createFieldset(container, L._('Real-time collaboration'))
1473
- const builder = new U.FormBuilder(this, ['options.syncEnabled'])
1474
- sync.appendChild(builder.build())
1475
- },
1476
-
1477
- _advancedActions: function (container) {
1478
- const advancedActions = L.DomUtil.createFieldset(container, L._('Advanced actions'))
1479
- const advancedButtons = L.DomUtil.create('div', 'button-bar half', advancedActions)
1480
- if (this.permissions.isOwner()) {
1481
- const deleteButton = U.Utils.loadTemplate(`
1482
- <button class="button" type="button">
1483
- <i class="icon icon-24 icon-delete"></i>${L._('Delete')}
1484
- </button>`)
1485
- deleteButton.addEventListener('click', () => this.del())
1486
- advancedButtons.appendChild(deleteButton)
1487
-
1488
- L.DomUtil.createButton(
1489
- 'button umap-empty',
1490
- advancedButtons,
1491
- L._('Clear data'),
1492
- this.emptyDataLayers,
1493
- this
1494
- )
1495
- L.DomUtil.createButton(
1496
- 'button umap-empty',
1497
- advancedButtons,
1498
- L._('Remove layers'),
1499
- this.removeDataLayers,
1500
- this
1501
- )
1502
- }
1503
- L.DomUtil.createButton(
1504
- 'button umap-clone',
1505
- advancedButtons,
1506
- L._('Clone this map'),
1507
- this.clone,
1508
- this
1509
- )
1510
- L.DomUtil.createButton(
1511
- 'button umap-download',
1512
- advancedButtons,
1513
- L._('Open share & download panel'),
1514
- this.share.open,
1515
- this.share
1516
- )
1517
- },
1518
-
1519
- editCaption: function () {
1520
- if (!this.editEnabled) return
1521
- if (this.options.editMode !== 'advanced') return
1522
- const container = L.DomUtil.create('div', 'umap-edit-container')
1523
- const metadataFields = ['options.name', 'options.description']
1524
-
1525
- L.DomUtil.createTitle(container, L._('Edit map details'), 'icon-caption')
1526
- const builder = new U.FormBuilder(this, metadataFields, {
1527
- className: 'map-metadata',
1528
- })
1529
- const form = builder.build()
1530
- container.appendChild(form)
1531
-
1532
- const credits = L.DomUtil.createFieldset(container, L._('Credits'))
1533
- const creditsFields = [
1534
- 'options.licence',
1535
- 'options.shortCredit',
1536
- 'options.longCredit',
1537
- 'options.permanentCredit',
1538
- 'options.permanentCreditBackground',
1539
- ]
1540
- const creditsBuilder = new U.FormBuilder(this, creditsFields)
1541
- credits.appendChild(creditsBuilder.build())
1542
- this.editPanel.open({ content: container })
1543
- },
1544
-
1545
- edit: function () {
1546
- if (!this.editEnabled) return
1547
- if (this.options.editMode !== 'advanced') return
1548
- const container = L.DomUtil.create('div')
1549
- L.DomUtil.createTitle(container, L._('Map advanced properties'), 'icon-settings')
1550
- this._editControls(container)
1551
- this._editShapeProperties(container)
1552
- this._editDefaultProperties(container)
1553
- this._editInteractionsProperties(container)
1554
- this.rules.edit(container)
1555
- this._editTilelayer(container)
1556
- this._editOverlay(container)
1557
- this._editBounds(container)
1558
- this._editSlideshow(container)
1559
- if (this.options.websocketEnabled) {
1560
- this._editSync(container)
1561
- }
1562
- this._advancedActions(container)
1563
-
1564
- this.editPanel.open({ content: container, className: 'dark' })
1565
- },
1566
-
1567
- enableEdit: function () {
1568
- L.DomUtil.addClass(document.body, 'umap-edit-enabled')
1569
- this.editEnabled = true
1570
- this.drop.enable()
1571
- this.fire('edit:enabled')
1572
- this.initSyncEngine()
1573
- },
1574
-
1575
- disableEdit: function () {
1576
- if (this.isDirty) return
1577
- this.drop.disable()
1578
- L.DomUtil.removeClass(document.body, 'umap-edit-enabled')
1579
- this.editedFeature = null
1580
- this.editEnabled = false
1581
- this.fire('edit:disabled')
1582
- this.editPanel.close()
1583
- this.fullPanel.close()
1584
- this.sync.stop()
1585
- this.closeInplaceToolbar()
1586
- },
1587
-
1588
- hasEditMode: function () {
1589
- return this.options.editMode === 'simple' || this.options.editMode === 'advanced'
1590
- },
1591
-
1592
- getDisplayName: function () {
1593
- return this.options.name || L._('Untitled map')
1594
- },
1595
-
1596
- initCaptionBar: function () {
1597
- const container = L.DomUtil.create(
1598
- 'div',
1599
- 'umap-caption-bar',
1600
- this._controlContainer
1601
- )
1602
- const name = L.DomUtil.create('h3', 'map-name', container)
1603
- L.DomEvent.disableClickPropagation(container)
1604
- this.addAuthorLink('span', container)
1605
- if (this.getOption('captionMenus')) {
1606
- L.DomUtil.createButton(
1607
- 'umap-about-link flat',
1608
- container,
1609
- L._('Open caption'),
1610
- this.openCaption,
1611
- this
1612
- )
1613
- L.DomUtil.createButton(
1614
- 'umap-open-browser-link flat',
1615
- container,
1616
- L._('Browse data'),
1617
- () => this.openBrowser('data')
1618
- )
1619
- if (this.options.facetKey) {
1620
- L.DomUtil.createButton(
1621
- 'umap-open-filter-link flat',
1622
- container,
1623
- L._('Filter data'),
1624
- () => this.openBrowser('filters')
1625
- )
1626
- }
1627
- }
1628
- this.onceDatalayersLoaded(function () {
1629
- this.slideshow.renderToolbox(container)
1630
- })
1631
- },
1632
-
1633
- askForReset: function (e) {
1634
- this.dialog
1635
- .confirm(L._('Are you sure you want to cancel your changes?'))
1636
- .then(() => {
1637
- this.reset()
1638
- this.disableEdit()
1639
- })
1640
- },
1641
-
1642
- startMarker: function () {
1643
- return this.editTools.startMarker()
1644
- },
1645
-
1646
- startPolyline: function () {
1647
- return this.editTools.startPolyline()
1648
- },
1649
-
1650
- startPolygon: function () {
1651
- return this.editTools.startPolygon()
1652
- },
1653
-
1654
- del: async function () {
1655
- this.dialog
1656
- .confirm(L._('Are you sure you want to delete this map?'))
1657
- .then(async () => {
1658
- const url = this.urls.get('map_delete', { map_id: this.options.umap_id })
1659
- const [data, response, error] = await this.server.post(url)
1660
- if (data.redirect) window.location = data.redirect
1661
- })
1662
- },
1663
-
1664
- clone: async function () {
1665
- this.dialog
1666
- .confirm(L._('Are you sure you want to clone this map and all its datalayers?'))
1667
- .then(async () => {
1668
- const url = this.urls.get('map_clone', { map_id: this.options.umap_id })
1669
- const [data, response, error] = await this.server.post(url)
1670
- if (data.redirect) window.location = data.redirect
1671
- })
1672
- },
1673
-
1674
- removeDataLayers: function () {
1675
- this.eachDataLayerReverse((datalayer) => {
1676
- datalayer._delete()
1677
- })
1678
- },
1679
-
1680
- emptyDataLayers: function () {
1681
- this.eachDataLayerReverse((datalayer) => {
1682
- datalayer.empty()
1683
- })
1684
- },
1685
-
1686
- initLoader: function () {
1687
- this.loader = new L.Control.Loading()
1688
- this.loader.onAdd(this)
1689
- },
1690
-
1691
- getOwnContextMenuItems: function (event) {
1692
- const items = []
1693
- if (this.hasEditMode()) {
1694
- if (this.editEnabled) {
1695
- if (!this.isDirty) {
1696
- items.push({
1697
- label: this.help.displayLabel('STOP_EDIT'),
1698
- action: () => this.disableEdit(),
1699
- })
1700
- }
1701
- if (this.options.enableMarkerDraw) {
1702
- items.push({
1703
- label: this.help.displayLabel('DRAW_MARKER'),
1704
- action: () => this.startMarker(event),
1705
- })
1706
- }
1707
- if (this.options.enablePolylineDraw) {
1708
- items.push({
1709
- label: this.help.displayLabel('DRAW_POLYGON'),
1710
- action: () => this.startPolygon(event),
1711
- })
1712
- }
1713
- if (this.options.enablePolygonDraw) {
1714
- items.push({
1715
- label: this.help.displayLabel('DRAW_LINE'),
1716
- action: () => this.startPolyline(event),
1717
- })
1718
- }
1719
- items.push('-')
1720
- items.push({
1721
- label: L._('Help'),
1722
- action: () => this.help.show('edit'),
1723
- })
1724
- } else {
1725
- items.push({
1726
- label: this.help.displayLabel('TOGGLE_EDIT'),
1727
- action: () => this.enableEdit(),
1728
- })
1729
- }
1730
- }
1731
- if (items.length) {
1732
- items.push('-')
1733
- }
1734
- items.push(
1735
- {
1736
- label: L._('Open browser'),
1737
- action: () => this.openBrowser('layers'),
1738
- },
1739
- {
1740
- label: L._('Browse data'),
1741
- action: () => this.openBrowser('data'),
1742
- }
1743
- )
1744
- if (this.options.facetKey) {
1745
- items.push({
1746
- label: L._('Filter data'),
1747
- action: () => this.openBrowser('filters'),
1748
- })
1749
- }
1750
- items.push(
1751
- {
1752
- label: L._('Open caption'),
1753
- action: () => this.openCaption(),
1754
- },
1755
- {
1756
- label: this.help.displayLabel('SEARCH'),
1757
- action: () => this.search(),
1758
- }
1759
- )
1760
- return items
1761
- },
1762
-
1763
- getContextMenuItems: function (event) {
1764
- const items = []
1765
- if (this.options.urls.routing) {
1766
- items.push('-', {
1767
- label: L._('Directions from here'),
1768
- action: () => this.openExternalRouting(event),
1769
- })
1770
- }
1771
- if (this.options.urls.edit_in_osm) {
1772
- items.push('-', {
1773
- label: L._('Edit in OpenStreetMap'),
1774
- action: () => this.editInOSM(event),
1775
- })
1776
- }
1777
- return items
1778
- },
1779
-
1780
- onContextMenu: function (event) {
1781
- const items = this.getOwnContextMenuItems(event).concat(
1782
- this.getContextMenuItems(event)
1783
- )
1784
- this.contextmenu.open(event.originalEvent, items)
1785
- },
1786
-
1787
- editInOSM: function (e) {
1788
- const url = this.urls.get('edit_in_osm', {
1789
- lat: e.latlng.lat,
1790
- lng: e.latlng.lng,
1791
- zoom: Math.max(this.getZoom(), 16),
1792
- })
1793
- if (url) window.open(url)
1794
- },
1795
-
1796
- openExternalRouting: function (e) {
1797
- const url = this.urls.get('routing', {
1798
- lat: e.latlng.lat,
1799
- lng: e.latlng.lng,
1800
- locale: L.getLocale(),
1801
- zoom: this.getZoom(),
1802
- })
1803
- if (url) window.open(url)
1804
- },
1805
-
1806
- getMap: function () {
1807
- return this
1808
- },
1809
-
1810
- getGeoContext: function () {
1811
- const context = {
1812
- bbox: this.getBounds().toBBoxString(),
1813
- north: this.getBounds().getNorthEast().lat,
1814
- east: this.getBounds().getNorthEast().lng,
1815
- south: this.getBounds().getSouthWest().lat,
1816
- west: this.getBounds().getSouthWest().lng,
1817
- lat: this.getCenter().lat,
1818
- lng: this.getCenter().lng,
1819
- zoom: this.getZoom(),
1820
- }
1821
- context.left = context.west
1822
- context.bottom = context.south
1823
- context.right = context.east
1824
- context.top = context.north
1825
- return context
1826
- },
1827
-
1828
- localizeUrl: function (url) {
1829
- return U.Utils.greedyTemplate(url, this.getGeoContext(), true)
1830
- },
1831
-
1832
- proxyUrl: function (url, ttl) {
1833
- if (this.options.urls.ajax_proxy) {
1834
- url = U.Utils.greedyTemplate(this.options.urls.ajax_proxy, {
1835
- url: encodeURIComponent(url),
1836
- ttl: ttl,
1837
- })
1838
- }
1839
- return url
1840
- },
1841
-
1842
- getFeatureById: function (id) {
1843
- let feature
1844
- for (const datalayer of this.datalayers_index) {
1845
- feature = datalayer.getFeatureById(id)
1846
- if (feature) return feature
1847
- }
1848
- },
1849
-
1850
- closeInplaceToolbar: function () {
1851
- const toolbar = this._toolbars[L.Toolbar.Popup._toolbar_class_id]
1852
- if (toolbar) toolbar.remove()
1853
- },
1854
-
1855
- search: function () {
1856
- if (this._controls.search) this._controls.search.open()
1857
- },
1858
-
1859
- getLayersBounds: function () {
1860
- const bounds = new L.latLngBounds()
1861
- this.eachBrowsableDataLayer((d) => {
1862
- if (d.isVisible()) bounds.extend(d.layer.getBounds())
1863
- })
1864
- return bounds
1865
- },
1866
-
1867
- sendEditLinkEmail: async function (formData) {
1868
- const sendLink =
1869
- this.options.urls.map_send_edit_link &&
1870
- this.urls.get('map_send_edit_link', {
1871
- map_id: this.options.umap_id,
1872
- })
1873
- await this.server.post(sendLink, {}, formData)
1874
- },
1875
-
1876
- allProperties: function () {
1877
- return [].concat(...this.datalayers_index.map((dl) => dl._propertiesIndex))
1878
- },
1879
-
1880
- sortedValues: function (property) {
1881
- return []
1882
- .concat(...this.datalayers_index.map((dl) => dl.sortedValues(property)))
1883
- .filter((val, idx, arr) => arr.indexOf(val) === idx)
1884
- .sort(U.Utils.naturalSort)
1885
- },
1886
-
1887
- addAuthorLink: function (element, container) {
1888
- if (this.options.author?.name) {
1889
- const authorContainer = L.DomUtil.add(
1890
- element,
1891
- 'umap-map-author',
1892
- container,
1893
- ` ${L._('by')} `
1894
- )
1895
- L.DomUtil.createLink(
1896
- '',
1897
- authorContainer,
1898
- this.options.author.name,
1899
- this.options.author.url
1900
- )
1901
- }
1902
- },
1903
- })