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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DomEvent, DomUtil
|
|
1
|
+
import { DomEvent, DomUtil } from '../../vendors/leaflet/leaflet-src.esm.js'
|
|
2
2
|
import { translate } from './i18n.js'
|
|
3
3
|
import * as Utils from './utils.js'
|
|
4
4
|
|
|
@@ -25,7 +25,7 @@ export default class Facets {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
this._umap.datalayers.browsable().map((datalayer) => {
|
|
28
|
-
datalayer.
|
|
28
|
+
datalayer.features.forEach((feature) => {
|
|
29
29
|
for (const name of names) {
|
|
30
30
|
let value = feature.properties[name]
|
|
31
31
|
const type = defined.get(name).type
|
|
@@ -131,12 +131,13 @@ class BaseElement {
|
|
|
131
131
|
|
|
132
132
|
Fields.Textarea = class extends BaseElement {
|
|
133
133
|
getTemplate() {
|
|
134
|
-
return `<textarea placeholder="${this.properties.placeholder || ''}" data-ref=textarea></textarea>`
|
|
134
|
+
return `<textarea placeholder="${this.properties.placeholder || ''}" name="${this.name}" data-ref=textarea></textarea>`
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
build() {
|
|
138
138
|
super.build()
|
|
139
139
|
this.textarea = this.elements.textarea
|
|
140
|
+
this.input = this.textarea
|
|
140
141
|
this.fetch()
|
|
141
142
|
this.textarea.addEventListener(
|
|
142
143
|
'input',
|
|
@@ -363,6 +364,9 @@ Fields.Select = class extends BaseElement {
|
|
|
363
364
|
}
|
|
364
365
|
|
|
365
366
|
getOptions() {
|
|
367
|
+
if (this.properties.getOptions) {
|
|
368
|
+
return this.properties.getOptions()
|
|
369
|
+
}
|
|
366
370
|
return this.properties.selectOptions
|
|
367
371
|
}
|
|
368
372
|
|
|
@@ -421,7 +425,7 @@ Fields.IntSelect = class extends Fields.Select {
|
|
|
421
425
|
|
|
422
426
|
Fields.EditableText = class extends BaseElement {
|
|
423
427
|
getTemplate() {
|
|
424
|
-
return `<span
|
|
428
|
+
return `<span class="${this.properties.className || ''}" data-ref=input></span>`
|
|
425
429
|
}
|
|
426
430
|
|
|
427
431
|
buildTemplate() {
|
|
@@ -435,6 +439,24 @@ Fields.EditableText = class extends BaseElement {
|
|
|
435
439
|
this.fetch()
|
|
436
440
|
this.input.addEventListener('input', () => this.sync())
|
|
437
441
|
this.input.addEventListener('keypress', (event) => this.onKeyPress(event))
|
|
442
|
+
this.input.addEventListener('dblclick', () => {
|
|
443
|
+
if (this.input.contentEditable !== true) {
|
|
444
|
+
this.input.contentEditable = true
|
|
445
|
+
this.input.focus()
|
|
446
|
+
}
|
|
447
|
+
})
|
|
448
|
+
this.input.addEventListener('blur', () => {
|
|
449
|
+
this.input.contentEditable = false
|
|
450
|
+
})
|
|
451
|
+
this.input.addEventListener('mouseover', () => {
|
|
452
|
+
this.builder._umap.tooltip.open({
|
|
453
|
+
content: translate('Double click to edit the name'),
|
|
454
|
+
anchor: this.input,
|
|
455
|
+
position: 'bottom',
|
|
456
|
+
delay: 500,
|
|
457
|
+
duration: 5000,
|
|
458
|
+
})
|
|
459
|
+
})
|
|
438
460
|
}
|
|
439
461
|
|
|
440
462
|
value() {
|
|
@@ -654,7 +676,7 @@ Fields.PropertyInput = class extends Fields.BlurInput {
|
|
|
654
676
|
super.build()
|
|
655
677
|
const autocomplete = new AutocompleteDatalist(this.input)
|
|
656
678
|
// Will be used on Umap and DataLayer
|
|
657
|
-
const properties = this.builder.obj.
|
|
679
|
+
const properties = this.builder.obj.fieldKeys
|
|
658
680
|
autocomplete.suggestions = properties
|
|
659
681
|
}
|
|
660
682
|
}
|
|
@@ -691,10 +713,10 @@ Fields.IconUrl = class extends Fields.BlurInput {
|
|
|
691
713
|
|
|
692
714
|
async onDefine() {
|
|
693
715
|
this.footer.innerHTML = ''
|
|
694
|
-
const [{
|
|
716
|
+
const [{ data }, response, error] = await this.builder._umap.server.get(
|
|
695
717
|
this.builder._umap.properties.urls.pictogram_list_json
|
|
696
718
|
)
|
|
697
|
-
if (!error) this.
|
|
719
|
+
if (!error) this.pictogramCollections = data
|
|
698
720
|
this.buildTabs()
|
|
699
721
|
const value = this.value()
|
|
700
722
|
if (Icon.RECENT.length) this.showRecentTab()
|
|
@@ -807,36 +829,51 @@ Fields.IconUrl = class extends Fields.BlurInput {
|
|
|
807
829
|
this.updatePreview()
|
|
808
830
|
}
|
|
809
831
|
|
|
810
|
-
addCategory(items, name) {
|
|
832
|
+
addCategory(items, name, parent, attribution = null) {
|
|
811
833
|
const hidden = name ? '' : ' hidden'
|
|
812
|
-
const [
|
|
834
|
+
const [container, { grid }] = Utils.loadTemplateWithRefs(`
|
|
813
835
|
<div class="umap-pictogram-category">
|
|
814
836
|
<h6${hidden}>${name}</h6>
|
|
815
837
|
<div class="umap-pictogram-grid" data-ref=grid></div>
|
|
816
838
|
</div>
|
|
817
839
|
`)
|
|
818
840
|
let hasIcons = false
|
|
819
|
-
|
|
841
|
+
const sorted = items.sort((a, b) => Utils.naturalSort(a.name, b.name, U.lang))
|
|
842
|
+
for (const item of sorted) {
|
|
843
|
+
item.attribution ??= attribution
|
|
820
844
|
hasIcons = this.addIconPreview(item, grid) || hasIcons
|
|
821
845
|
}
|
|
822
|
-
if (hasIcons)
|
|
846
|
+
if (hasIcons) parent.appendChild(container)
|
|
823
847
|
}
|
|
824
848
|
|
|
825
849
|
buildSymbolsList() {
|
|
826
850
|
this.grid.innerHTML = ''
|
|
827
851
|
const categories = {}
|
|
828
852
|
let category
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
categories[category].push(props)
|
|
833
|
-
}
|
|
834
|
-
const sorted = Object.entries(categories).sort(([a], [b]) =>
|
|
835
|
-
Utils.naturalSort(a, b, U.lang)
|
|
853
|
+
const collectionsNames = Object.keys(this.pictogramCollections)
|
|
854
|
+
const [container, { select, icons }] = Utils.loadTemplateWithRefs(
|
|
855
|
+
'<div><select data-ref="select"></select><div data-ref="icons"></div></div>'
|
|
836
856
|
)
|
|
837
|
-
for (const
|
|
838
|
-
|
|
857
|
+
for (const name of collectionsNames) {
|
|
858
|
+
const option = Utils.loadTemplate(`<option value="${name}">${name}</option>`)
|
|
859
|
+
select.appendChild(option)
|
|
839
860
|
}
|
|
861
|
+
this.grid.appendChild(container)
|
|
862
|
+
select.hidden = collectionsNames.length === 1
|
|
863
|
+
const loadCollection = (name) => {
|
|
864
|
+
icons.innerHTML = ''
|
|
865
|
+
const collection = this.pictogramCollections[name || collectionsNames[0]]
|
|
866
|
+
const sorted = Object.entries(collection.categories).sort(([a], [b]) =>
|
|
867
|
+
Utils.naturalSort(a, b, U.lang)
|
|
868
|
+
)
|
|
869
|
+
for (const [name, items] of sorted) {
|
|
870
|
+
this.addCategory(items, name, icons, collection.attribution)
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
loadCollection()
|
|
874
|
+
select.addEventListener('change', (event) => {
|
|
875
|
+
loadCollection(event.target.value)
|
|
876
|
+
})
|
|
840
877
|
}
|
|
841
878
|
|
|
842
879
|
buildRecentList() {
|
|
@@ -844,7 +881,7 @@ Fields.IconUrl = class extends Fields.BlurInput {
|
|
|
844
881
|
const items = U.Icon.RECENT.map((src) => ({
|
|
845
882
|
src,
|
|
846
883
|
}))
|
|
847
|
-
this.addCategory(items)
|
|
884
|
+
this.addCategory(items, null, this.grid)
|
|
848
885
|
}
|
|
849
886
|
|
|
850
887
|
isDefault() {
|
|
@@ -2,6 +2,19 @@ import { uMapAlert as Alert } from '../components/alerts/alert.js'
|
|
|
2
2
|
/* Uses globals for: csv2geojson, osmtogeojson (not available as ESM) */
|
|
3
3
|
import { translate } from './i18n.js'
|
|
4
4
|
|
|
5
|
+
const parseTextGeom = async (geom) => {
|
|
6
|
+
try {
|
|
7
|
+
return JSON.parse(geom)
|
|
8
|
+
} catch (e) {
|
|
9
|
+
try {
|
|
10
|
+
const betterknown = await import('../../vendors/betterknown/betterknown.mjs')
|
|
11
|
+
return betterknown.wktToGeoJSON(geom)
|
|
12
|
+
} catch {
|
|
13
|
+
return null
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
5
18
|
export const EXPORT_FORMATS = {
|
|
6
19
|
geojson: {
|
|
7
20
|
formatter: async (umap) => JSON.stringify(umap.toGeoJSON(), null, 2),
|
|
@@ -81,15 +94,30 @@ export class Formatter {
|
|
|
81
94
|
sexagesimal: false,
|
|
82
95
|
parseLatLon: (raw) => Number.parseFloat(raw.toString().replace(',', '.')),
|
|
83
96
|
},
|
|
84
|
-
(err, result) => {
|
|
85
|
-
// csv2geojson fallback to null geometries when it cannot determine
|
|
86
|
-
// lat or lon columns. This is valid geojson, but unwanted from a user
|
|
87
|
-
// point of view.
|
|
97
|
+
async (err, result) => {
|
|
88
98
|
if (result?.features.length) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
99
|
+
const first = result.features[0]
|
|
100
|
+
if (first.geometry === null) {
|
|
101
|
+
const geomFields = ['geom', 'geometry', 'wkt', 'geojson']
|
|
102
|
+
for (const field of geomFields) {
|
|
103
|
+
if (first.properties[field]) {
|
|
104
|
+
for (const feature of result.features) {
|
|
105
|
+
feature.geometry = await parseTextGeom(feature.properties[field])
|
|
106
|
+
delete feature.properties[field]
|
|
107
|
+
}
|
|
108
|
+
break
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (first.geometry === null) {
|
|
112
|
+
// csv2geojson fallback to null geometries when it cannot determine
|
|
113
|
+
// lat or lon columns. This is valid geojson, but unwanted from a user
|
|
114
|
+
// point of view.
|
|
115
|
+
err = {
|
|
116
|
+
type: 'Error',
|
|
117
|
+
message: translate(
|
|
118
|
+
'No geo column found: must be either `lat(itude)` and `lon(gitude)` or `geom(etry)`.'
|
|
119
|
+
),
|
|
120
|
+
}
|
|
93
121
|
}
|
|
94
122
|
}
|
|
95
123
|
}
|
|
@@ -70,15 +70,33 @@ export class Importer {
|
|
|
70
70
|
this.portals = options.choices || PORTALS
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
async fetchDatasets(baseUrl) {
|
|
74
|
+
let results = []
|
|
75
|
+
let total = null
|
|
76
|
+
const hardLimit = 500
|
|
77
|
+
while (total === null || results.length < total) {
|
|
78
|
+
const offset = results.length
|
|
79
|
+
const response = await this.umap.request.get(
|
|
80
|
+
`${baseUrl}/api/explore/v2.1/catalog/datasets?where=features%20in%20%28%22geo%22%29&limit=100&offset=${offset}&order_by=title asc`
|
|
81
|
+
)
|
|
82
|
+
if (!response.ok) break
|
|
83
|
+
const data = await response.json()
|
|
84
|
+
if (total === null) {
|
|
85
|
+
total = data.total_count
|
|
86
|
+
}
|
|
87
|
+
results = results.concat(data.results)
|
|
88
|
+
if (total === null || results.length > hardLimit) break
|
|
89
|
+
}
|
|
90
|
+
return results
|
|
91
|
+
}
|
|
92
|
+
|
|
73
93
|
async open(importer) {
|
|
74
94
|
let fields_map = {}
|
|
75
95
|
const [container, { portals, datasets, geofield }] =
|
|
76
96
|
Utils.loadTemplateWithRefs(TEMPLATE)
|
|
77
97
|
portals.addEventListener('change', async (event) => {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
)
|
|
81
|
-
if (response.ok) {
|
|
98
|
+
const results = await this.fetchDatasets(event.target.value)
|
|
99
|
+
if (results) {
|
|
82
100
|
fields_map = {}
|
|
83
101
|
Array.from(datasets.children).forEach((option) => {
|
|
84
102
|
if (!option.disabled) {
|
|
@@ -87,8 +105,7 @@ export class Importer {
|
|
|
87
105
|
option.selected = true
|
|
88
106
|
}
|
|
89
107
|
})
|
|
90
|
-
const
|
|
91
|
-
for (const result of data.results) {
|
|
108
|
+
for (const result of results) {
|
|
92
109
|
const fields = result.fields.filter((field) => field.type === 'geo_point_2d')
|
|
93
110
|
if (!fields.length) {
|
|
94
111
|
console.debug('No geofield found for', result)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as Utils from './utils.js'
|
|
2
|
+
|
|
1
3
|
export class DataLayerManager extends Object {
|
|
2
4
|
add(datalayer) {
|
|
3
5
|
this[datalayer.id] = datalayer
|
|
@@ -43,4 +45,61 @@ export class DataLayerManager extends Object {
|
|
|
43
45
|
if (!next.canBrowse()) return this.next(next)
|
|
44
46
|
return next
|
|
45
47
|
}
|
|
48
|
+
first() {
|
|
49
|
+
return this.active()[0]
|
|
50
|
+
}
|
|
51
|
+
last() {
|
|
52
|
+
const layers = this.active()
|
|
53
|
+
return layers[layers.length - 1]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class FeatureManager extends Map {
|
|
58
|
+
add(feature) {
|
|
59
|
+
if (this.has(feature.id)) {
|
|
60
|
+
console.error('Duplicate id', feature, this.get(feature.id))
|
|
61
|
+
feature.id = Utils.generateId()
|
|
62
|
+
}
|
|
63
|
+
this.set(feature.id, feature)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
del(feature) {
|
|
67
|
+
this.delete(feature.id)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
count() {
|
|
71
|
+
return this.size
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
sort(by) {
|
|
75
|
+
const features = Array.from(this.values())
|
|
76
|
+
Utils.sortFeatures(features, by, U.lang)
|
|
77
|
+
this.clear()
|
|
78
|
+
for (const feature of features) {
|
|
79
|
+
this.set(feature.id, feature)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
getIndex(feature) {
|
|
84
|
+
const entries = Array.from(this)
|
|
85
|
+
return entries.findIndex(([id]) => id === feature.id)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
first() {
|
|
89
|
+
return this.values().next().value
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
last() {
|
|
93
|
+
return Array.from(this.values())[this.size - 1]
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
next(feature) {
|
|
97
|
+
const index = this.getIndex(feature)
|
|
98
|
+
return Array.from(this.values())[index + 1]
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
prev(feature) {
|
|
102
|
+
const index = this.getIndex(feature)
|
|
103
|
+
return Array.from(this.values())[index - 1]
|
|
104
|
+
}
|
|
46
105
|
}
|
|
@@ -249,11 +249,9 @@ export function isImg(src) {
|
|
|
249
249
|
export function makeElement(src, parent) {
|
|
250
250
|
let icon
|
|
251
251
|
if (isImg(src)) {
|
|
252
|
-
icon =
|
|
253
|
-
icon.src = src
|
|
252
|
+
icon = Utils.loadTemplate(`<img loading="lazy" src="${src}">`)
|
|
254
253
|
} else {
|
|
255
|
-
icon =
|
|
256
|
-
icon.textContent = src
|
|
254
|
+
icon = Utils.loadTemplate(`<span>${src}</span>`)
|
|
257
255
|
}
|
|
258
256
|
parent.appendChild(icon)
|
|
259
257
|
return icon
|
|
@@ -273,7 +271,7 @@ export function setContrast(icon, parent, src, bgcolor) {
|
|
|
273
271
|
if (DomUtil.contrastedColor(parent, bgcolor)) {
|
|
274
272
|
// Decide whether to switch svg to white or not, but do it
|
|
275
273
|
// only for internal SVG, as invert could do weird things
|
|
276
|
-
if (
|
|
274
|
+
if (src.endsWith('.svg') && src !== SCHEMA.iconUrl.default) {
|
|
277
275
|
// Must be called after icon container is added to the DOM
|
|
278
276
|
// An image
|
|
279
277
|
icon.style.filter = 'invert(1)'
|
|
@@ -67,7 +67,7 @@ const ClassifiedMixin = {
|
|
|
67
67
|
|
|
68
68
|
getValues: function () {
|
|
69
69
|
const values = []
|
|
70
|
-
this.datalayer.
|
|
70
|
+
this.datalayer.features.forEach((feature) => {
|
|
71
71
|
const value = this._getValue(feature)
|
|
72
72
|
if (value !== undefined) values.push(value)
|
|
73
73
|
})
|
|
@@ -198,7 +198,7 @@ export const Choropleth = FeatureGroup.extend({
|
|
|
198
198
|
'properties.choropleth.property',
|
|
199
199
|
{
|
|
200
200
|
handler: 'Select',
|
|
201
|
-
selectOptions: this.datalayer.
|
|
201
|
+
selectOptions: this.datalayer.fieldKeys,
|
|
202
202
|
label: translate('Choropleth property value'),
|
|
203
203
|
},
|
|
204
204
|
],
|
|
@@ -307,7 +307,7 @@ export const Circles = FeatureGroup.extend({
|
|
|
307
307
|
'properties.circles.property',
|
|
308
308
|
{
|
|
309
309
|
handler: 'Select',
|
|
310
|
-
selectOptions: this.datalayer.
|
|
310
|
+
selectOptions: this.datalayer.fieldKeys,
|
|
311
311
|
label: translate('Property name to compute circles'),
|
|
312
312
|
},
|
|
313
313
|
],
|
|
@@ -384,8 +384,7 @@ export const Categorized = FeatureGroup.extend({
|
|
|
384
384
|
|
|
385
385
|
_getValue: function (feature) {
|
|
386
386
|
const key =
|
|
387
|
-
this.datalayer.properties.categorized.property ||
|
|
388
|
-
this.datalayer.allProperties()[0]
|
|
387
|
+
this.datalayer.properties.categorized.property || this.datalayer.fieldKeys[0]
|
|
389
388
|
return feature.properties[key]
|
|
390
389
|
},
|
|
391
390
|
|
|
@@ -438,7 +437,7 @@ export const Categorized = FeatureGroup.extend({
|
|
|
438
437
|
'properties.categorized.property',
|
|
439
438
|
{
|
|
440
439
|
handler: 'Select',
|
|
441
|
-
selectOptions: this.datalayer.
|
|
440
|
+
selectOptions: this.datalayer.fieldKeys,
|
|
442
441
|
label: translate('Category property'),
|
|
443
442
|
},
|
|
444
443
|
],
|
|
@@ -447,7 +446,7 @@ export const Categorized = FeatureGroup.extend({
|
|
|
447
446
|
{
|
|
448
447
|
handler: 'Select',
|
|
449
448
|
label: translate('Color palette'),
|
|
450
|
-
|
|
449
|
+
getOptions: () => this.getColorSchemes(this._classes),
|
|
451
450
|
},
|
|
452
451
|
],
|
|
453
452
|
[
|
|
@@ -481,6 +480,8 @@ export const Categorized = FeatureGroup.extend({
|
|
|
481
480
|
if (builder) builder.helpers['properties.categorized.mode'].fetch()
|
|
482
481
|
}
|
|
483
482
|
this.compute()
|
|
483
|
+
// Rebuild list of color palettes when aggregation property changes.
|
|
484
|
+
builder?.helpers['properties.categorized.brewer']?.fetch()
|
|
484
485
|
// If user changes the mode
|
|
485
486
|
// then update the categories input value
|
|
486
487
|
if (field === 'properties.categorized.mode') {
|
|
@@ -335,7 +335,7 @@ export const LeafletMap = BaseMap.extend({
|
|
|
335
335
|
const datalayer = this._umap.datalayers.visible()[0]
|
|
336
336
|
let feature
|
|
337
337
|
if (datalayer) {
|
|
338
|
-
const feature = datalayer.
|
|
338
|
+
const feature = datalayer.features.last()
|
|
339
339
|
if (feature) {
|
|
340
340
|
feature.zoomTo({ callback: this.options.noControl ? null : feature.view })
|
|
341
341
|
return
|
|
@@ -238,6 +238,14 @@ export const LeafletMarker = Marker.extend({
|
|
|
238
238
|
this._redraw()
|
|
239
239
|
this._resetZIndex()
|
|
240
240
|
},
|
|
241
|
+
|
|
242
|
+
_resetZIndex() {
|
|
243
|
+
// Override Leaflet default behaviour, which set the zIndex
|
|
244
|
+
// according to feature's y coordinate, and group features
|
|
245
|
+
// zIndex by their datalayer order
|
|
246
|
+
this._zIndex = this.feature.datalayer.getDOMOrder()
|
|
247
|
+
this._updateZIndex(0)
|
|
248
|
+
},
|
|
241
249
|
})
|
|
242
250
|
|
|
243
251
|
const PathMixin = {
|
|
@@ -296,6 +304,11 @@ const PathMixin = {
|
|
|
296
304
|
if (this._tooltip) this._tooltip.setLatLng(this.getCenter())
|
|
297
305
|
},
|
|
298
306
|
|
|
307
|
+
beforeAdd: function (map) {
|
|
308
|
+
this.options.renderer = this.feature.datalayer.renderer
|
|
309
|
+
this.parentClass.prototype.beforeAdd.call(this, map)
|
|
310
|
+
},
|
|
311
|
+
|
|
299
312
|
onAdd: function (map) {
|
|
300
313
|
this._container = null
|
|
301
314
|
FeatureMixin.onAdd.call(this, map)
|
|
@@ -4,6 +4,8 @@ import { MutatingForm } from './form/builder.js'
|
|
|
4
4
|
import { translate } from './i18n.js'
|
|
5
5
|
import Orderable from './orderable.js'
|
|
6
6
|
import * as Utils from './utils.js'
|
|
7
|
+
import * as Icon from './rendering/icon.js'
|
|
8
|
+
import { SCHEMA } from './schema.js'
|
|
7
9
|
|
|
8
10
|
const EMPTY_VALUES = ['', undefined, null]
|
|
9
11
|
|
|
@@ -12,12 +14,16 @@ class Rule {
|
|
|
12
14
|
return this._condition
|
|
13
15
|
}
|
|
14
16
|
|
|
17
|
+
get label() {
|
|
18
|
+
return this.name || this.condition
|
|
19
|
+
}
|
|
20
|
+
|
|
15
21
|
set condition(value) {
|
|
16
22
|
this._condition = value
|
|
17
23
|
this.parse()
|
|
18
24
|
}
|
|
19
25
|
|
|
20
|
-
constructor(umap, parent, condition = '',
|
|
26
|
+
constructor(umap, parent, condition = '', name = '', properties = {}) {
|
|
21
27
|
// TODO make this public properties when browser coverage is ok
|
|
22
28
|
// cf https://caniuse.com/?search=public%20class%20field
|
|
23
29
|
this._condition = null
|
|
@@ -32,8 +38,9 @@ class Rule {
|
|
|
32
38
|
this.parent = parent
|
|
33
39
|
this._umap = umap
|
|
34
40
|
this.active = true
|
|
35
|
-
this.
|
|
41
|
+
this.properties = properties
|
|
36
42
|
this.condition = condition
|
|
43
|
+
this.name = name
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
render(fields) {
|
|
@@ -95,7 +102,7 @@ class Rule {
|
|
|
95
102
|
}
|
|
96
103
|
|
|
97
104
|
getOption(option) {
|
|
98
|
-
return this.
|
|
105
|
+
return this.properties[option]
|
|
99
106
|
}
|
|
100
107
|
|
|
101
108
|
edit() {
|
|
@@ -108,23 +115,24 @@ class Rule {
|
|
|
108
115
|
placeholder: translate('key=value or key!=value'),
|
|
109
116
|
},
|
|
110
117
|
],
|
|
111
|
-
'
|
|
112
|
-
'
|
|
113
|
-
'
|
|
114
|
-
'
|
|
115
|
-
'
|
|
116
|
-
'
|
|
117
|
-
'
|
|
118
|
-
'
|
|
119
|
-
'
|
|
120
|
-
'
|
|
121
|
-
'
|
|
118
|
+
'name',
|
|
119
|
+
'properties.color',
|
|
120
|
+
'properties.iconClass',
|
|
121
|
+
'properties.iconUrl',
|
|
122
|
+
'properties.iconOpacity',
|
|
123
|
+
'properties.opacity',
|
|
124
|
+
'properties.weight',
|
|
125
|
+
'properties.fill',
|
|
126
|
+
'properties.fillColor',
|
|
127
|
+
'properties.fillOpacity',
|
|
128
|
+
'properties.smoothFactor',
|
|
129
|
+
'properties.dashArray',
|
|
122
130
|
]
|
|
123
131
|
const builder = new MutatingForm(this, options)
|
|
124
132
|
const container = document.createElement('div')
|
|
125
133
|
container.appendChild(builder.build())
|
|
126
134
|
const autocomplete = new AutocompleteDatalist(builder.helpers.condition.input)
|
|
127
|
-
const properties = this.parent.
|
|
135
|
+
const properties = this.parent.fieldKeys
|
|
128
136
|
autocomplete.suggestions = properties
|
|
129
137
|
autocomplete.input.addEventListener('input', (event) => {
|
|
130
138
|
const value = event.target.value
|
|
@@ -143,7 +151,7 @@ class Rule {
|
|
|
143
151
|
</button>`)
|
|
144
152
|
backButton.addEventListener('click', () =>
|
|
145
153
|
this.parent.edit().then((panel) => {
|
|
146
|
-
panel.
|
|
154
|
+
panel.scrollTo('details#rules')
|
|
147
155
|
})
|
|
148
156
|
)
|
|
149
157
|
|
|
@@ -160,7 +168,7 @@ class Rule {
|
|
|
160
168
|
<button class="icon icon-16 icon-eye" title="${translate('Toggle rule')}" data-ref=toggle></button>
|
|
161
169
|
<button class="icon icon-16 icon-edit show-on-edit" title="${translate('Edit')}" data-ref=edit></button>
|
|
162
170
|
<button class="icon icon-16 icon-delete show-on-edit" title="${translate('Delete rule')}" data-ref=remove></button>
|
|
163
|
-
<span>${this.
|
|
171
|
+
<span>${this.label || translate('empty rule')}</span>
|
|
164
172
|
<i class="icon icon-16 icon-drag" title="${translate('Drag to reorder')}"></i>
|
|
165
173
|
</li>
|
|
166
174
|
`
|
|
@@ -171,7 +179,7 @@ class Rule {
|
|
|
171
179
|
remove.addEventListener('click', () => {
|
|
172
180
|
if (!confirm(translate('Are you sure you want to delete this rule?'))) return
|
|
173
181
|
this._delete()
|
|
174
|
-
this.
|
|
182
|
+
this.parent.edit().then((panel) => panel.scrollTo('details#rules'))
|
|
175
183
|
})
|
|
176
184
|
toggle.addEventListener('click', () => {
|
|
177
185
|
this.active = !this.active
|
|
@@ -181,8 +189,11 @@ class Rule {
|
|
|
181
189
|
}
|
|
182
190
|
|
|
183
191
|
_delete() {
|
|
192
|
+
// TODO refactor this call to update
|
|
193
|
+
const oldRules = Utils.CopyJSON(this.parent.properties.rules || {})
|
|
184
194
|
this.parent.rules.rules = this.parent.rules.rules.filter((rule) => rule !== this)
|
|
185
195
|
this.parent.rules.commit()
|
|
196
|
+
this.parent.sync.update('properties.rules', this.parent.properties.rules, oldRules)
|
|
186
197
|
}
|
|
187
198
|
|
|
188
199
|
setter(key, value) {
|
|
@@ -191,6 +202,20 @@ class Rule {
|
|
|
191
202
|
this.parent.rules.commit()
|
|
192
203
|
this.parent.sync.update('properties.rules', this.parent.properties.rules, oldRules)
|
|
193
204
|
}
|
|
205
|
+
|
|
206
|
+
renderLegend(ul) {
|
|
207
|
+
const [li, { colorBox }] = Utils.loadTemplateWithRefs(
|
|
208
|
+
`<li><span class="color-box" data-ref=colorBox></span>${this.label}</li>`
|
|
209
|
+
)
|
|
210
|
+
const bgcolor = this.properties.color || this.parent.getColor()
|
|
211
|
+
const symbol = this.properties.iconUrl
|
|
212
|
+
colorBox.style.backgroundColor = bgcolor
|
|
213
|
+
if (symbol && symbol !== SCHEMA.iconUrl.default) {
|
|
214
|
+
const icon = Icon.makeElement(symbol, colorBox)
|
|
215
|
+
Icon.setContrast(icon, colorBox, symbol, bgcolor)
|
|
216
|
+
}
|
|
217
|
+
ul.appendChild(li)
|
|
218
|
+
}
|
|
194
219
|
}
|
|
195
220
|
|
|
196
221
|
export default class Rules {
|
|
@@ -203,9 +228,17 @@ export default class Rules {
|
|
|
203
228
|
load() {
|
|
204
229
|
this.rules = []
|
|
205
230
|
if (!this.parent.properties.rules?.length) return
|
|
206
|
-
for (const { condition, options } of this.parent.properties
|
|
231
|
+
for (const { condition, name, properties, options } of this.parent.properties
|
|
232
|
+
.rules) {
|
|
207
233
|
if (!condition) continue
|
|
208
|
-
|
|
234
|
+
const rule = new Rule(
|
|
235
|
+
this._umap,
|
|
236
|
+
this.parent,
|
|
237
|
+
condition,
|
|
238
|
+
name,
|
|
239
|
+
properties || options
|
|
240
|
+
)
|
|
241
|
+
this.rules.push(rule)
|
|
209
242
|
}
|
|
210
243
|
}
|
|
211
244
|
|
|
@@ -250,6 +283,19 @@ export default class Rules {
|
|
|
250
283
|
container.appendChild(body)
|
|
251
284
|
}
|
|
252
285
|
|
|
286
|
+
count() {
|
|
287
|
+
return this.rules.length
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
renderLegend(container, keys = new Set()) {
|
|
291
|
+
const ul = Utils.loadTemplate('<ul class="rules-caption"></ul>')
|
|
292
|
+
container.appendChild(ul)
|
|
293
|
+
for (const rule of this.rules) {
|
|
294
|
+
if (keys.size && !keys.has(rule.key)) continue
|
|
295
|
+
rule.renderLegend(ul)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
253
299
|
addRule() {
|
|
254
300
|
const rule = new Rule(this._umap, this.parent)
|
|
255
301
|
this.rules.push(rule)
|
|
@@ -259,17 +305,24 @@ export default class Rules {
|
|
|
259
305
|
commit() {
|
|
260
306
|
this.parent.properties.rules = this.rules.map((rule) => {
|
|
261
307
|
return {
|
|
308
|
+
name: rule.name,
|
|
262
309
|
condition: rule.condition,
|
|
263
|
-
|
|
310
|
+
properties: rule.properties,
|
|
264
311
|
}
|
|
265
312
|
})
|
|
266
313
|
}
|
|
267
314
|
|
|
268
|
-
getOption(
|
|
315
|
+
getOption(name, feature) {
|
|
269
316
|
for (const rule of this.rules) {
|
|
270
317
|
if (rule.match(feature.properties)) {
|
|
271
|
-
if (Utils.usableOption(rule.
|
|
318
|
+
if (Utils.usableOption(rule.properties, name)) return rule.properties[name]
|
|
272
319
|
}
|
|
273
320
|
}
|
|
274
321
|
}
|
|
322
|
+
|
|
323
|
+
*[Symbol.iterator]() {
|
|
324
|
+
for (const rule of this.rules) {
|
|
325
|
+
yield rule
|
|
326
|
+
}
|
|
327
|
+
}
|
|
275
328
|
}
|