umap-project 3.1.2__py3-none-any.whl → 3.2.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/locale/en/LC_MESSAGES/django.po +21 -17
- umap/locale/fr/LC_MESSAGES/django.mo +0 -0
- umap/locale/fr/LC_MESSAGES/django.po +21 -17
- umap/management/commands/export_pictogram.py +29 -0
- umap/management/commands/migrate_to_S3.py +5 -1
- umap/management/commands/purge_old_versions.py +8 -6
- umap/settings/__init__.py +21 -0
- umap/settings/base.py +1 -0
- umap/static/umap/content.css +7 -2
- umap/static/umap/css/icon.css +77 -3
- umap/static/umap/css/panel.css +31 -1
- umap/static/umap/js/modules/browser.js +1 -1
- umap/static/umap/js/modules/data/features.js +14 -29
- umap/static/umap/js/modules/data/layer.js +248 -136
- umap/static/umap/js/modules/facets.js +2 -2
- umap/static/umap/js/modules/form/fields.js +56 -19
- umap/static/umap/js/modules/formatter.js +36 -8
- umap/static/umap/js/modules/importers/opendata.js +23 -6
- umap/static/umap/js/modules/managers.js +59 -0
- umap/static/umap/js/modules/rendering/icon.js +3 -5
- umap/static/umap/js/modules/rendering/layers/classified.js +8 -7
- umap/static/umap/js/modules/rendering/map.js +1 -1
- umap/static/umap/js/modules/rendering/ui.js +13 -0
- umap/static/umap/js/modules/rules.js +76 -23
- umap/static/umap/js/modules/schema.js +3 -0
- umap/static/umap/js/modules/slideshow.js +1 -1
- umap/static/umap/js/modules/sync/updaters.js +1 -6
- umap/static/umap/js/modules/tableeditor.js +13 -37
- umap/static/umap/js/modules/templates.js +7 -6
- umap/static/umap/js/modules/ui/panel.js +7 -0
- umap/static/umap/js/modules/umap.js +17 -6
- umap/static/umap/js/modules/utils.js +8 -7
- umap/static/umap/locale/am_ET.js +43 -6
- umap/static/umap/locale/am_ET.json +43 -6
- umap/static/umap/locale/ar.js +43 -6
- umap/static/umap/locale/ar.json +43 -6
- umap/static/umap/locale/ast.js +43 -6
- umap/static/umap/locale/ast.json +43 -6
- umap/static/umap/locale/bg.js +43 -6
- umap/static/umap/locale/bg.json +43 -6
- umap/static/umap/locale/br.js +30 -26
- umap/static/umap/locale/br.json +30 -26
- umap/static/umap/locale/ca.js +50 -13
- umap/static/umap/locale/ca.json +50 -13
- umap/static/umap/locale/cs_CZ.js +43 -6
- umap/static/umap/locale/cs_CZ.json +43 -6
- umap/static/umap/locale/da.js +10 -6
- umap/static/umap/locale/da.json +10 -6
- umap/static/umap/locale/de.js +10 -6
- umap/static/umap/locale/de.json +10 -6
- umap/static/umap/locale/el.js +20 -10
- umap/static/umap/locale/el.json +20 -10
- umap/static/umap/locale/en.js +10 -6
- umap/static/umap/locale/en.json +10 -6
- umap/static/umap/locale/en_US.json +43 -6
- umap/static/umap/locale/es.js +10 -6
- umap/static/umap/locale/es.json +10 -6
- umap/static/umap/locale/et.js +43 -6
- umap/static/umap/locale/et.json +43 -6
- umap/static/umap/locale/eu.js +43 -6
- umap/static/umap/locale/eu.json +43 -6
- umap/static/umap/locale/fa_IR.js +43 -6
- umap/static/umap/locale/fa_IR.json +43 -6
- umap/static/umap/locale/fi.js +43 -6
- umap/static/umap/locale/fi.json +43 -6
- umap/static/umap/locale/fr.js +10 -6
- umap/static/umap/locale/fr.json +10 -6
- umap/static/umap/locale/gl.js +43 -6
- umap/static/umap/locale/gl.json +43 -6
- umap/static/umap/locale/he.js +43 -6
- umap/static/umap/locale/he.json +43 -6
- umap/static/umap/locale/hr.js +43 -6
- umap/static/umap/locale/hr.json +43 -6
- umap/static/umap/locale/hu.js +34 -24
- umap/static/umap/locale/hu.json +34 -24
- umap/static/umap/locale/id.js +43 -6
- umap/static/umap/locale/id.json +43 -6
- umap/static/umap/locale/is.js +43 -6
- umap/static/umap/locale/is.json +43 -6
- umap/static/umap/locale/it.js +10 -6
- umap/static/umap/locale/it.json +10 -6
- umap/static/umap/locale/ja.js +43 -6
- umap/static/umap/locale/ja.json +43 -6
- umap/static/umap/locale/ko.js +43 -6
- umap/static/umap/locale/ko.json +43 -6
- umap/static/umap/locale/lt.js +43 -6
- umap/static/umap/locale/lt.json +43 -6
- umap/static/umap/locale/ms.js +43 -6
- umap/static/umap/locale/ms.json +43 -6
- umap/static/umap/locale/nl.js +10 -6
- umap/static/umap/locale/nl.json +10 -6
- umap/static/umap/locale/no.js +43 -6
- umap/static/umap/locale/no.json +43 -6
- umap/static/umap/locale/pl.js +43 -6
- umap/static/umap/locale/pl.json +43 -6
- umap/static/umap/locale/pl_PL.json +43 -6
- umap/static/umap/locale/pt.js +43 -6
- umap/static/umap/locale/pt.json +43 -6
- umap/static/umap/locale/pt_BR.js +53 -16
- umap/static/umap/locale/pt_BR.json +53 -16
- umap/static/umap/locale/pt_PT.js +43 -6
- umap/static/umap/locale/pt_PT.json +43 -6
- umap/static/umap/locale/ro.js +43 -6
- umap/static/umap/locale/ro.json +43 -6
- umap/static/umap/locale/ru.js +43 -6
- umap/static/umap/locale/ru.json +43 -6
- umap/static/umap/locale/sk_SK.js +43 -6
- umap/static/umap/locale/sk_SK.json +43 -6
- umap/static/umap/locale/sl.js +43 -6
- umap/static/umap/locale/sl.json +43 -6
- umap/static/umap/locale/sr.js +43 -6
- umap/static/umap/locale/sr.json +43 -6
- umap/static/umap/locale/sv.js +43 -6
- umap/static/umap/locale/sv.json +43 -6
- umap/static/umap/locale/th_TH.js +43 -6
- umap/static/umap/locale/th_TH.json +43 -6
- umap/static/umap/locale/tr.js +43 -6
- umap/static/umap/locale/tr.json +43 -6
- umap/static/umap/locale/uk_UA.js +43 -6
- umap/static/umap/locale/uk_UA.json +43 -6
- umap/static/umap/locale/vi.js +43 -6
- umap/static/umap/locale/vi.json +43 -6
- umap/static/umap/locale/vi_VN.json +43 -6
- umap/static/umap/locale/zh.js +43 -6
- umap/static/umap/locale/zh.json +43 -6
- umap/static/umap/locale/zh_CN.json +43 -6
- umap/static/umap/locale/zh_TW.Big5.json +43 -6
- umap/static/umap/locale/zh_TW.js +43 -6
- umap/static/umap/locale/zh_TW.json +43 -6
- umap/static/umap/map.css +239 -65
- umap/static/umap/vendors/betterknown/betterknown.mjs +287 -0
- umap/storage/fs.py +3 -2
- umap/templates/base.html +4 -1
- umap/tests/base.py +9 -1
- umap/tests/integration/test_basics.py +1 -1
- umap/tests/integration/test_conditional_rules.py +62 -20
- umap/tests/integration/test_edit_datalayer.py +1 -1
- umap/tests/integration/test_edit_marker.py +1 -1
- umap/tests/integration/test_export_map.py +10 -0
- umap/tests/integration/test_import.py +140 -0
- umap/tests/integration/test_optimistic_merge.py +72 -12
- umap/tests/integration/test_tableeditor.py +6 -3
- umap/utils.py +33 -0
- umap/views.py +16 -2
- umap_project-3.2.0.dist-info/METADATA +76 -0
- {umap_project-3.1.2.dist-info → umap_project-3.2.0.dist-info}/RECORD +150 -148
- umap_project-3.1.2.dist-info/METADATA +0 -68
- {umap_project-3.1.2.dist-info → umap_project-3.2.0.dist-info}/WHEEL +0 -0
- {umap_project-3.1.2.dist-info → umap_project-3.2.0.dist-info}/entry_points.txt +0 -0
- {umap_project-3.1.2.dist-info → umap_project-3.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -21,6 +21,8 @@ import TableEditor from '../tableeditor.js'
|
|
|
21
21
|
import * as Utils from '../utils.js'
|
|
22
22
|
import { LineString, Point, Polygon } from './features.js'
|
|
23
23
|
import Rules from '../rules.js'
|
|
24
|
+
import Orderable from '../orderable.js'
|
|
25
|
+
import { FeatureManager } from '../managers.js'
|
|
24
26
|
|
|
25
27
|
export const LAYER_TYPES = [
|
|
26
28
|
DefaultLayer,
|
|
@@ -40,8 +42,7 @@ export class DataLayer {
|
|
|
40
42
|
constructor(umap, leafletMap, data = {}) {
|
|
41
43
|
this._umap = umap
|
|
42
44
|
this.sync = umap.syncEngine.proxy(this)
|
|
43
|
-
this.
|
|
44
|
-
this._features = {}
|
|
45
|
+
this.features = new FeatureManager()
|
|
45
46
|
this._geojson = null
|
|
46
47
|
this._propertiesIndex = []
|
|
47
48
|
|
|
@@ -89,6 +90,12 @@ export class DataLayer {
|
|
|
89
90
|
if (!this.createdOnServer) {
|
|
90
91
|
if (this.showAtLoad()) this.show()
|
|
91
92
|
}
|
|
93
|
+
if (!this._needsFetch && !this._umap.fields.length) {
|
|
94
|
+
this.properties.fields = [
|
|
95
|
+
{ key: U.DEFAULT_LABEL_KEY, type: 'String' },
|
|
96
|
+
{ key: 'description', type: 'Text' },
|
|
97
|
+
]
|
|
98
|
+
}
|
|
92
99
|
|
|
93
100
|
// Only layers that are displayed on load must be hidden/shown
|
|
94
101
|
// Automatically, others will be shown manually, and thus will
|
|
@@ -140,6 +147,23 @@ export class DataLayer {
|
|
|
140
147
|
this.properties.rank = value
|
|
141
148
|
}
|
|
142
149
|
|
|
150
|
+
get fields() {
|
|
151
|
+
if (!this.properties.fields) this.properties.fields = []
|
|
152
|
+
return this.properties.fields
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
set fields(fields) {
|
|
156
|
+
this.properties.fields = fields
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
get fieldKeys() {
|
|
160
|
+
return this.fields.map((field) => field.key)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
get sortKey() {
|
|
164
|
+
return this.getProperty('sortKey') || U.DEFAULT_LABEL_KEY
|
|
165
|
+
}
|
|
166
|
+
|
|
143
167
|
getSyncMetadata() {
|
|
144
168
|
return {
|
|
145
169
|
subject: 'datalayer',
|
|
@@ -249,19 +273,14 @@ export class DataLayer {
|
|
|
249
273
|
const Class = LAYER_MAP[this.properties.type] || DefaultLayer
|
|
250
274
|
this.layer = new Class(this)
|
|
251
275
|
// Rendering layer changed, so let's force reset the feature rendering too.
|
|
252
|
-
this.
|
|
253
|
-
|
|
276
|
+
this.features.forEach((feature) => {
|
|
277
|
+
feature.makeUI()
|
|
278
|
+
this.showFeature(feature)
|
|
279
|
+
})
|
|
254
280
|
if (visible) this.show()
|
|
255
281
|
this.propagateRemote()
|
|
256
282
|
}
|
|
257
283
|
|
|
258
|
-
eachFeature(method, context) {
|
|
259
|
-
for (const idx of this._index) {
|
|
260
|
-
method.call(context || this, this._features[idx])
|
|
261
|
-
}
|
|
262
|
-
return this
|
|
263
|
-
}
|
|
264
|
-
|
|
265
284
|
async fetchData() {
|
|
266
285
|
if (!this.createdOnServer) return
|
|
267
286
|
if (this._loading) return
|
|
@@ -314,12 +333,6 @@ export class DataLayer {
|
|
|
314
333
|
}
|
|
315
334
|
}
|
|
316
335
|
|
|
317
|
-
reindex() {
|
|
318
|
-
const features = Object.values(this._features)
|
|
319
|
-
this.sortFeatures(features)
|
|
320
|
-
this._index = features.map((feature) => stamp(feature))
|
|
321
|
-
}
|
|
322
|
-
|
|
323
336
|
showAtZoom() {
|
|
324
337
|
const from = Number.parseInt(this.properties.fromZoom, 10)
|
|
325
338
|
const to = Number.parseInt(this.properties.toZoom, 10)
|
|
@@ -423,22 +436,20 @@ export class DataLayer {
|
|
|
423
436
|
}
|
|
424
437
|
|
|
425
438
|
addFeature(feature) {
|
|
426
|
-
const id = stamp(feature)
|
|
427
439
|
feature.connectToDataLayer(this)
|
|
428
|
-
this.
|
|
429
|
-
this._features[id] = feature
|
|
430
|
-
this.indexProperties(feature)
|
|
440
|
+
this.features.add(feature)
|
|
431
441
|
this._umap.featuresIndex[feature.getSlug()] = feature
|
|
442
|
+
// TODO: quid for remote data ?
|
|
443
|
+
this.inferFields(feature)
|
|
432
444
|
this.showFeature(feature)
|
|
433
445
|
this.dataChanged()
|
|
434
446
|
}
|
|
435
447
|
|
|
436
448
|
removeFeature(feature, sync) {
|
|
437
|
-
const id = stamp(feature)
|
|
438
449
|
// This feature was not yet added, may be after
|
|
439
450
|
// hitting Escape while drawing a new line or
|
|
440
451
|
// polygon, not yet valid (not enough points)
|
|
441
|
-
if (!this.
|
|
452
|
+
if (!this.features.has(feature.id)) return
|
|
442
453
|
if (sync !== false) {
|
|
443
454
|
const oldValue = feature.toGeoJSON()
|
|
444
455
|
feature.sync.delete(oldValue)
|
|
@@ -446,57 +457,66 @@ export class DataLayer {
|
|
|
446
457
|
this.hideFeature(feature)
|
|
447
458
|
delete this._umap.featuresIndex[feature.getSlug()]
|
|
448
459
|
feature.disconnectFromDataLayer(this)
|
|
449
|
-
this.
|
|
450
|
-
delete this._features[id]
|
|
460
|
+
this.features.del(feature)
|
|
451
461
|
if (this.isVisible()) this.dataChanged()
|
|
452
462
|
}
|
|
453
463
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
this._propertiesIndex.push(name)
|
|
464
|
-
this._propertiesIndex.sort()
|
|
464
|
+
inferFields(feature) {
|
|
465
|
+
if (!this.properties.fields) this.properties.fields = []
|
|
466
|
+
const keys = this.fieldKeys
|
|
467
|
+
for (const key in feature.properties) {
|
|
468
|
+
if (typeof feature.properties[key] !== 'object') {
|
|
469
|
+
if (key.indexOf('_') === 0) continue
|
|
470
|
+
if (keys.includes(key)) continue
|
|
471
|
+
this.properties.fields.push({ key, type: 'String' })
|
|
472
|
+
}
|
|
465
473
|
}
|
|
466
474
|
}
|
|
467
475
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
this
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
+
async confirmDeleteProperty(property) {
|
|
477
|
+
return this._umap.dialog
|
|
478
|
+
.confirm(
|
|
479
|
+
translate('Are you sure you want to delete this field on all the features?')
|
|
480
|
+
)
|
|
481
|
+
.then(() => {
|
|
482
|
+
this.deleteProperty(property)
|
|
483
|
+
})
|
|
476
484
|
}
|
|
477
485
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
486
|
+
async askForRenameProperty(property) {
|
|
487
|
+
return this._umap.dialog
|
|
488
|
+
.prompt(translate('Please enter the new name of this field'))
|
|
489
|
+
.then(({ prompt }) => {
|
|
490
|
+
if (!prompt || !this.validateName(prompt)) return
|
|
491
|
+
this.renameProperty(property, prompt)
|
|
492
|
+
})
|
|
481
493
|
}
|
|
482
494
|
|
|
483
495
|
renameProperty(oldName, newName) {
|
|
484
496
|
this.sync.startBatch()
|
|
485
|
-
this.
|
|
497
|
+
const oldFields = Utils.CopyJSON(this.fields)
|
|
498
|
+
for (const field of this.fields) {
|
|
499
|
+
if (field.key === oldName) {
|
|
500
|
+
field.key = newName
|
|
501
|
+
break
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
this.sync.update('properties.fields', this.fields, oldFields)
|
|
505
|
+
this.features.forEach((feature) => {
|
|
486
506
|
feature.renameProperty(oldName, newName)
|
|
487
507
|
})
|
|
488
508
|
this.sync.commitBatch()
|
|
489
|
-
this.deindexProperty(oldName)
|
|
490
|
-
this.indexProperty(newName)
|
|
491
509
|
}
|
|
492
510
|
|
|
493
511
|
deleteProperty(property) {
|
|
494
512
|
this.sync.startBatch()
|
|
495
|
-
this.
|
|
513
|
+
const oldFields = Utils.CopyJSON(this.fields)
|
|
514
|
+
this.fields = this.fields.filter((field) => field.key !== property)
|
|
515
|
+
this.sync.update('properties.fields', this.fields, oldFields)
|
|
516
|
+
this.features.forEach((feature) => {
|
|
496
517
|
feature.deleteProperty(property)
|
|
497
518
|
})
|
|
498
519
|
this.sync.commitBatch()
|
|
499
|
-
this.deindexProperty(property)
|
|
500
520
|
}
|
|
501
521
|
|
|
502
522
|
addProperty() {
|
|
@@ -508,7 +528,7 @@ export class DataLayer {
|
|
|
508
528
|
.prompt(translate('Please enter the name of the property'))
|
|
509
529
|
.then(({ prompt }) => {
|
|
510
530
|
if (!prompt || !this.validateName(prompt)) return
|
|
511
|
-
this.
|
|
531
|
+
this.properties.fields.push({ key: prompt, type: 'String' })
|
|
512
532
|
resolve()
|
|
513
533
|
})
|
|
514
534
|
return promise
|
|
@@ -519,19 +539,15 @@ export class DataLayer {
|
|
|
519
539
|
Alert.error(translate('Name “{name}” should not contain a dot.', { name }))
|
|
520
540
|
return false
|
|
521
541
|
}
|
|
522
|
-
if (this.
|
|
542
|
+
if (this.fieldKeys.includes(name)) {
|
|
523
543
|
Alert.error(translate('This name already exists: “{name}”', { name }))
|
|
524
544
|
return false
|
|
525
545
|
}
|
|
526
546
|
return true
|
|
527
547
|
}
|
|
528
548
|
|
|
529
|
-
allProperties() {
|
|
530
|
-
return this._propertiesIndex
|
|
531
|
-
}
|
|
532
|
-
|
|
533
549
|
sortedValues(property) {
|
|
534
|
-
return
|
|
550
|
+
return Array.from(this.features.values())
|
|
535
551
|
.map((feature) => feature.properties[property])
|
|
536
552
|
.filter((val, idx, arr) => arr.indexOf(val) === idx)
|
|
537
553
|
.sort(Utils.naturalSort)
|
|
@@ -549,11 +565,6 @@ export class DataLayer {
|
|
|
549
565
|
}
|
|
550
566
|
}
|
|
551
567
|
|
|
552
|
-
sortFeatures(collection) {
|
|
553
|
-
const sortKeys = this.getProperty('sortKey') || U.DEFAULT_LABEL_KEY
|
|
554
|
-
return Utils.sortFeatures(collection, sortKeys, U.lang)
|
|
555
|
-
}
|
|
556
|
-
|
|
557
568
|
makeFeatures(geojson = {}, sync = true) {
|
|
558
569
|
if (geojson.type === 'Feature' || geojson.coordinates) {
|
|
559
570
|
geojson = [geojson]
|
|
@@ -563,7 +574,7 @@ export class DataLayer {
|
|
|
563
574
|
: geojson.features || geojson.geometries
|
|
564
575
|
if (!collection) return
|
|
565
576
|
const features = []
|
|
566
|
-
|
|
577
|
+
Utils.sortFeatures(collection, this.sortKey, U.lang)
|
|
567
578
|
for (const featureJson of collection) {
|
|
568
579
|
if (featureJson.geometry?.type === 'GeometryCollection') {
|
|
569
580
|
for (const geometry of featureJson.geometry.geometries) {
|
|
@@ -592,6 +603,14 @@ export class DataLayer {
|
|
|
592
603
|
// FIXME: deal with MultiPoint
|
|
593
604
|
feature = new Point(this._umap, this, geojson, id)
|
|
594
605
|
break
|
|
606
|
+
case 'MultiPoint':
|
|
607
|
+
if (geometry.coordinates?.length === 1) {
|
|
608
|
+
geojson.geometry.coordinates = geojson.geometry.coordinates[0]
|
|
609
|
+
feature = new Point(this._umap, this, geojson, id)
|
|
610
|
+
} else if (this._umap.editEnabled) {
|
|
611
|
+
Alert.error(translate('Cannot process MultiPoint'))
|
|
612
|
+
}
|
|
613
|
+
break
|
|
595
614
|
case 'MultiLineString':
|
|
596
615
|
case 'LineString':
|
|
597
616
|
feature = new LineString(this._umap, this, geojson, id)
|
|
@@ -602,11 +621,13 @@ export class DataLayer {
|
|
|
602
621
|
break
|
|
603
622
|
default:
|
|
604
623
|
console.debug(geojson)
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
624
|
+
if (this._umap.editEnabled) {
|
|
625
|
+
Alert.error(
|
|
626
|
+
translate('Skipping unknown geometry.type: {type}', {
|
|
627
|
+
type: geometry.type || 'undefined',
|
|
628
|
+
})
|
|
629
|
+
)
|
|
630
|
+
}
|
|
610
631
|
}
|
|
611
632
|
if (feature && !feature.isEmpty()) {
|
|
612
633
|
this.addFeature(feature)
|
|
@@ -713,9 +734,7 @@ export class DataLayer {
|
|
|
713
734
|
}
|
|
714
735
|
|
|
715
736
|
clear(sync = true) {
|
|
716
|
-
|
|
717
|
-
feature.del(sync)
|
|
718
|
-
}
|
|
737
|
+
this.features.forEach((feature) => feature.del(sync))
|
|
719
738
|
this.dataChanged()
|
|
720
739
|
}
|
|
721
740
|
|
|
@@ -731,14 +750,17 @@ export class DataLayer {
|
|
|
731
750
|
|
|
732
751
|
redraw() {
|
|
733
752
|
if (!this.isVisible()) return
|
|
734
|
-
this.
|
|
753
|
+
this.features.forEach((feature) => feature.redraw())
|
|
735
754
|
}
|
|
736
755
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
756
|
+
reindex() {
|
|
757
|
+
this.features.sort(this.sortKey)
|
|
758
|
+
if (this.isBrowsable()) {
|
|
759
|
+
this.resetLayer(true)
|
|
740
760
|
}
|
|
741
|
-
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
_editMetadata(container) {
|
|
742
764
|
const metadataFields = [
|
|
743
765
|
'properties.name',
|
|
744
766
|
'properties.description',
|
|
@@ -768,29 +790,35 @@ export class DataLayer {
|
|
|
768
790
|
],
|
|
769
791
|
]
|
|
770
792
|
DomUtil.createTitle(container, translate('Layer properties'), 'icon-layers')
|
|
771
|
-
|
|
793
|
+
const builder = new MutatingForm(this, metadataFields)
|
|
772
794
|
builder.on('set', ({ detail }) => {
|
|
773
795
|
this._umap.onDataLayersChanged()
|
|
774
796
|
if (detail.helper.field === 'properties.type') {
|
|
775
|
-
this.edit()
|
|
797
|
+
this.edit().then((panel) => panel.scrollTo('details#layer-properties'))
|
|
776
798
|
}
|
|
777
799
|
})
|
|
778
800
|
container.appendChild(builder.build())
|
|
801
|
+
}
|
|
779
802
|
|
|
803
|
+
_editLayerProperties(container) {
|
|
780
804
|
const layerFields = this.layer.getEditableProperties()
|
|
781
805
|
|
|
782
806
|
if (layerFields.length) {
|
|
783
|
-
builder = new MutatingForm(this, layerFields
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
807
|
+
const builder = new MutatingForm(this, layerFields)
|
|
808
|
+
const template = `
|
|
809
|
+
<details id="layer-properties">
|
|
810
|
+
<summary>${this.layer.getName()}: ${translate('settings')}</summary>
|
|
811
|
+
<fieldset data-ref=fieldset></fieldset>
|
|
812
|
+
</details>
|
|
813
|
+
`
|
|
814
|
+
const [details, { fieldset }] = Utils.loadTemplateWithRefs(template)
|
|
815
|
+
container.appendChild(details)
|
|
816
|
+
fieldset.appendChild(builder.build())
|
|
791
817
|
}
|
|
818
|
+
}
|
|
792
819
|
|
|
793
|
-
|
|
820
|
+
_editShapeProperties(container) {
|
|
821
|
+
const fields = [
|
|
794
822
|
'properties.color',
|
|
795
823
|
'properties.iconClass',
|
|
796
824
|
'properties.iconUrl',
|
|
@@ -803,7 +831,7 @@ export class DataLayer {
|
|
|
803
831
|
'properties.fillOpacity',
|
|
804
832
|
]
|
|
805
833
|
|
|
806
|
-
builder = new MutatingForm(this,
|
|
834
|
+
const builder = new MutatingForm(this, fields, {
|
|
807
835
|
id: 'datalayer-advanced-properties',
|
|
808
836
|
})
|
|
809
837
|
const shapeFieldset = DomUtil.createFieldset(
|
|
@@ -811,8 +839,10 @@ export class DataLayer {
|
|
|
811
839
|
translate('Shape properties')
|
|
812
840
|
)
|
|
813
841
|
shapeFieldset.appendChild(builder.build())
|
|
842
|
+
}
|
|
814
843
|
|
|
815
|
-
|
|
844
|
+
_editAdvancedProperties(container) {
|
|
845
|
+
const fields = [
|
|
816
846
|
'properties.smoothFactor',
|
|
817
847
|
'properties.dashArray',
|
|
818
848
|
'properties.zoomTo',
|
|
@@ -821,7 +851,7 @@ export class DataLayer {
|
|
|
821
851
|
'properties.sortKey',
|
|
822
852
|
]
|
|
823
853
|
|
|
824
|
-
builder = new MutatingForm(this,
|
|
854
|
+
const builder = new MutatingForm(this, fields, {
|
|
825
855
|
id: 'datalayer-advanced-properties',
|
|
826
856
|
})
|
|
827
857
|
builder.on('set', ({ detail }) => {
|
|
@@ -834,8 +864,10 @@ export class DataLayer {
|
|
|
834
864
|
translate('Advanced properties')
|
|
835
865
|
)
|
|
836
866
|
advancedFieldset.appendChild(builder.build())
|
|
867
|
+
}
|
|
837
868
|
|
|
838
|
-
|
|
869
|
+
_editInteractionProperties(container) {
|
|
870
|
+
const fields = [
|
|
839
871
|
'properties.popupShape',
|
|
840
872
|
'properties.popupTemplate',
|
|
841
873
|
'properties.popupContentTemplate',
|
|
@@ -845,14 +877,16 @@ export class DataLayer {
|
|
|
845
877
|
'properties.outlinkTarget',
|
|
846
878
|
'properties.interactive',
|
|
847
879
|
]
|
|
848
|
-
builder = new MutatingForm(this,
|
|
880
|
+
const builder = new MutatingForm(this, fields)
|
|
849
881
|
const popupFieldset = DomUtil.createFieldset(
|
|
850
882
|
container,
|
|
851
883
|
translate('Interaction options')
|
|
852
884
|
)
|
|
853
885
|
popupFieldset.appendChild(builder.build())
|
|
886
|
+
}
|
|
854
887
|
|
|
855
|
-
|
|
888
|
+
_editTextPathProperties(container) {
|
|
889
|
+
const fields = [
|
|
856
890
|
'properties.textPath',
|
|
857
891
|
'properties.textPathColor',
|
|
858
892
|
'properties.textPathRepeat',
|
|
@@ -861,17 +895,77 @@ export class DataLayer {
|
|
|
861
895
|
'properties.textPathOffset',
|
|
862
896
|
'properties.textPathPosition',
|
|
863
897
|
]
|
|
864
|
-
builder = new MutatingForm(this,
|
|
898
|
+
const builder = new MutatingForm(this, fields)
|
|
865
899
|
const fieldset = DomUtil.createFieldset(container, translate('Line decoration'))
|
|
866
900
|
fieldset.appendChild(builder.build())
|
|
901
|
+
}
|
|
867
902
|
|
|
903
|
+
_editFields(container) {
|
|
904
|
+
const template = `
|
|
905
|
+
<details id="fields">
|
|
906
|
+
<summary>${translate('Manage Fields')}</summary>
|
|
907
|
+
<fieldset>
|
|
908
|
+
<ul data-ref=ul></ul>
|
|
909
|
+
<button type="button" data-ref=add><i class="icon icon-16 icon-add"></i>${translate('Add a new field')}</button>
|
|
910
|
+
</fieldset>
|
|
911
|
+
</details>
|
|
912
|
+
`
|
|
913
|
+
const [fieldset, { ul, add }] = Utils.loadTemplateWithRefs(template)
|
|
914
|
+
add.addEventListener('click', () => {
|
|
915
|
+
this.addProperty().then(() => {
|
|
916
|
+
this.edit().then((panel) => {
|
|
917
|
+
panel.scrollTo('details#fields')
|
|
918
|
+
})
|
|
919
|
+
})
|
|
920
|
+
})
|
|
921
|
+
container.appendChild(fieldset)
|
|
922
|
+
for (const field of this.fields) {
|
|
923
|
+
const [row, { rename, del }] = Utils.loadTemplateWithRefs(
|
|
924
|
+
`<li class="orderable" data-key="${field.key}">
|
|
925
|
+
<button class="icon icon-16 icon-edit" title="${translate('Rename this field')}" data-ref=rename></button>
|
|
926
|
+
<button class="icon icon-16 icon-delete" title="${translate('Delete this field')}" data-ref=del></button>
|
|
927
|
+
<i class="icon icon-16 icon-drag" title="${translate('Drag to reorder')}"></i>
|
|
928
|
+
${field.key}
|
|
929
|
+
</li>`
|
|
930
|
+
)
|
|
931
|
+
ul.appendChild(row)
|
|
932
|
+
rename.addEventListener('click', () => {
|
|
933
|
+
this.askForRenameProperty(field.key).then(() => {
|
|
934
|
+
this.edit().then((panel) => {
|
|
935
|
+
panel.scrollTo('details#fields')
|
|
936
|
+
})
|
|
937
|
+
})
|
|
938
|
+
})
|
|
939
|
+
del.addEventListener('click', () => {
|
|
940
|
+
this.confirmDeleteProperty(field.key).then(() => {
|
|
941
|
+
this.edit().then((panel) => {
|
|
942
|
+
panel.scrollTo('details#fields')
|
|
943
|
+
})
|
|
944
|
+
})
|
|
945
|
+
})
|
|
946
|
+
}
|
|
947
|
+
const onReorder = (src, dst, initialIndex, finalIndex) => {
|
|
948
|
+
const orderedKeys = Array.from(ul.querySelectorAll('li')).map(
|
|
949
|
+
(el) => el.dataset.key
|
|
950
|
+
)
|
|
951
|
+
const oldFields = Utils.CopyJSON(this.properties.fields)
|
|
952
|
+
this.properties.fields.sort(
|
|
953
|
+
(fieldA, fieldB) =>
|
|
954
|
+
orderedKeys.indexOf(fieldA.key) > orderedKeys.indexOf(fieldB.key)
|
|
955
|
+
)
|
|
956
|
+
this.sync.update('properties.fields', this.properties.fields, oldFields)
|
|
957
|
+
}
|
|
958
|
+
const orderable = new Orderable(ul, onReorder)
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
_editRemoteDataProperties(container) {
|
|
868
962
|
// XXX I'm not sure **why** this is needed (as it's set during `this.initialize`)
|
|
869
963
|
// but apparently it's needed.
|
|
870
964
|
if (!Utils.isObject(this.properties.remoteData)) {
|
|
871
965
|
this.properties.remoteData = {}
|
|
872
966
|
}
|
|
873
967
|
|
|
874
|
-
const
|
|
968
|
+
const fields = [
|
|
875
969
|
[
|
|
876
970
|
'properties.remoteData.url',
|
|
877
971
|
{ handler: 'Url', label: translate('Url'), helpEntries: ['formatURL'] },
|
|
@@ -899,7 +993,7 @@ export class DataLayer {
|
|
|
899
993
|
],
|
|
900
994
|
]
|
|
901
995
|
if (this._umap.properties.urls.ajax_proxy) {
|
|
902
|
-
|
|
996
|
+
fields.push([
|
|
903
997
|
'properties.remoteData.proxy',
|
|
904
998
|
{
|
|
905
999
|
handler: 'Switch',
|
|
@@ -907,14 +1001,14 @@ export class DataLayer {
|
|
|
907
1001
|
helpEntries: ['proxyRemoteData'],
|
|
908
1002
|
},
|
|
909
1003
|
])
|
|
910
|
-
|
|
1004
|
+
fields.push('properties.remoteData.ttl')
|
|
911
1005
|
}
|
|
912
1006
|
|
|
913
1007
|
const remoteDataContainer = DomUtil.createFieldset(
|
|
914
1008
|
container,
|
|
915
1009
|
translate('Remote data')
|
|
916
1010
|
)
|
|
917
|
-
builder = new MutatingForm(this,
|
|
1011
|
+
const builder = new MutatingForm(this, fields)
|
|
918
1012
|
remoteDataContainer.appendChild(builder.build())
|
|
919
1013
|
DomUtil.createButton(
|
|
920
1014
|
'button umap-verify',
|
|
@@ -923,12 +1017,9 @@ export class DataLayer {
|
|
|
923
1017
|
() => this.fetchRemoteData(true),
|
|
924
1018
|
this
|
|
925
1019
|
)
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
if (this._umap.properties.urls.datalayer_versions) {
|
|
929
|
-
this.buildVersionsFieldset(container)
|
|
930
|
-
}
|
|
1020
|
+
}
|
|
931
1021
|
|
|
1022
|
+
_buildAdvancedActions(container) {
|
|
932
1023
|
const advancedActions = DomUtil.createFieldset(
|
|
933
1024
|
container,
|
|
934
1025
|
translate('Advanced actions')
|
|
@@ -963,6 +1054,31 @@ export class DataLayer {
|
|
|
963
1054
|
}
|
|
964
1055
|
clone.addEventListener('click', () => this.clone().edit())
|
|
965
1056
|
if (this.createdOnServer) download.hidden = false
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
edit() {
|
|
1060
|
+
if (!this._umap.editEnabled) {
|
|
1061
|
+
return
|
|
1062
|
+
}
|
|
1063
|
+
const container = DomUtil.create('div', 'umap-layer-properties-container')
|
|
1064
|
+
this._editMetadata(container)
|
|
1065
|
+
this._editLayerProperties(container)
|
|
1066
|
+
this._editShapeProperties(container)
|
|
1067
|
+
this._editAdvancedProperties(container)
|
|
1068
|
+
this._editInteractionProperties(container)
|
|
1069
|
+
this._editTextPathProperties(container)
|
|
1070
|
+
this._editRemoteDataProperties(container)
|
|
1071
|
+
if (!this.isRemoteLayer()) {
|
|
1072
|
+
this._editFields(container)
|
|
1073
|
+
}
|
|
1074
|
+
this.rules.edit(container)
|
|
1075
|
+
|
|
1076
|
+
if (this._umap.properties.urls.datalayer_versions) {
|
|
1077
|
+
this.buildVersionsFieldset(container)
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
this._buildAdvancedActions(container)
|
|
1081
|
+
|
|
966
1082
|
const backButton = DomUtil.createButtonIcon(
|
|
967
1083
|
undefined,
|
|
968
1084
|
'icon-back',
|
|
@@ -973,7 +1089,7 @@ export class DataLayer {
|
|
|
973
1089
|
DomEvent.disableClickPropagation(backButton)
|
|
974
1090
|
DomEvent.on(backButton, 'click', this._umap.editDatalayers, this._umap)
|
|
975
1091
|
|
|
976
|
-
this._umap.editPanel.open({
|
|
1092
|
+
return this._umap.editPanel.open({
|
|
977
1093
|
content: container,
|
|
978
1094
|
highlight: 'layers',
|
|
979
1095
|
actions: [backButton],
|
|
@@ -1062,7 +1178,7 @@ export class DataLayer {
|
|
|
1062
1178
|
|
|
1063
1179
|
featuresToGeoJSON() {
|
|
1064
1180
|
const features = []
|
|
1065
|
-
this.
|
|
1181
|
+
this.features.forEach((feature) => features.push(feature.toGeoJSON()))
|
|
1066
1182
|
return features
|
|
1067
1183
|
}
|
|
1068
1184
|
|
|
@@ -1123,46 +1239,23 @@ export class DataLayer {
|
|
|
1123
1239
|
}
|
|
1124
1240
|
|
|
1125
1241
|
count() {
|
|
1126
|
-
return this.
|
|
1242
|
+
return this.features.count()
|
|
1127
1243
|
}
|
|
1128
1244
|
|
|
1129
1245
|
hasData() {
|
|
1130
|
-
return !!this.
|
|
1246
|
+
return !!this.count()
|
|
1131
1247
|
}
|
|
1132
1248
|
|
|
1133
1249
|
isVisible() {
|
|
1134
1250
|
return Boolean(this.layer && this._leafletMap.hasLayer(this.layer))
|
|
1135
1251
|
}
|
|
1136
1252
|
|
|
1137
|
-
getFeatureByIndex(index) {
|
|
1138
|
-
if (index === -1) index = this._index.length - 1
|
|
1139
|
-
const id = this._index[index]
|
|
1140
|
-
return this._features[id]
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
// TODO Add an index
|
|
1144
|
-
// For now, iterate on all the features.
|
|
1145
|
-
getFeatureById(id) {
|
|
1146
|
-
return Object.values(this._features).find((feature) => feature.id === id)
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
1253
|
getNextFeature(feature) {
|
|
1150
|
-
|
|
1151
|
-
const nextId = this._index[id + 1]
|
|
1152
|
-
return nextId
|
|
1153
|
-
? this._features[nextId]
|
|
1154
|
-
: this.getNextBrowsable().getFeatureByIndex(0)
|
|
1254
|
+
return this.features.next(feature) || this.getNextBrowsable().features.first()
|
|
1155
1255
|
}
|
|
1156
1256
|
|
|
1157
1257
|
getPreviousFeature(feature) {
|
|
1158
|
-
|
|
1159
|
-
return null
|
|
1160
|
-
}
|
|
1161
|
-
const id = this._index.indexOf(stamp(feature))
|
|
1162
|
-
const previousId = this._index[id - 1]
|
|
1163
|
-
return previousId
|
|
1164
|
-
? this._features[previousId]
|
|
1165
|
-
: this.getPreviousBrowsable().getFeatureByIndex(-1)
|
|
1258
|
+
return this.features.prev(feature) || this.getPreviousBrowsable().features.last()
|
|
1166
1259
|
}
|
|
1167
1260
|
|
|
1168
1261
|
getPreviousBrowsable() {
|
|
@@ -1322,8 +1415,27 @@ export class DataLayer {
|
|
|
1322
1415
|
)) {
|
|
1323
1416
|
container.innerHTML = ''
|
|
1324
1417
|
if (this.layer.renderLegend) return this.layer.renderLegend(container)
|
|
1325
|
-
const
|
|
1326
|
-
|
|
1418
|
+
const keys = new Set(this.fieldKeys)
|
|
1419
|
+
const rules = new Map()
|
|
1420
|
+
for (const rule of this.rules) {
|
|
1421
|
+
rules.set(rule.condition, rule)
|
|
1422
|
+
}
|
|
1423
|
+
for (const rule of this._umap.rules) {
|
|
1424
|
+
if (!rules.has(rule.condition) && keys.has(rule.key)) {
|
|
1425
|
+
rules.set(rule.condition, rule)
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
if (rules.size) {
|
|
1429
|
+
const ul = Utils.loadTemplate('<ul class="rules-caption"></ul>')
|
|
1430
|
+
container.appendChild(ul)
|
|
1431
|
+
for (const [_, rule] of rules) {
|
|
1432
|
+
rule.renderLegend(ul)
|
|
1433
|
+
}
|
|
1434
|
+
} else {
|
|
1435
|
+
const color = Utils.loadTemplate('<span class="datalayer-color"></span>')
|
|
1436
|
+
color.style.backgroundColor = this.getColor()
|
|
1437
|
+
container.appendChild(color)
|
|
1438
|
+
}
|
|
1327
1439
|
}
|
|
1328
1440
|
}
|
|
1329
1441
|
|