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