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