umap-project 3.1.2__py3-none-any.whl → 3.3.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.mo +0 -0
- umap/locale/en/LC_MESSAGES/django.po +22 -18
- 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 +3 -0
- umap/static/umap/content.css +7 -2
- umap/static/umap/css/contextmenu.css +58 -2
- umap/static/umap/css/form.css +175 -45
- umap/static/umap/css/icon.css +97 -3
- umap/static/umap/css/panel.css +31 -1
- umap/static/umap/img/16-white.svg +21 -40
- umap/static/umap/img/16.svg +1 -1
- umap/static/umap/img/24-white.svg +9 -9
- umap/static/umap/img/24.svg +23 -10
- umap/static/umap/img/source/16-white.svg +23 -41
- umap/static/umap/img/source/16.svg +1 -1
- umap/static/umap/img/source/24-white.svg +11 -11
- umap/static/umap/img/source/24.svg +25 -12
- umap/static/umap/js/modules/browser.js +1 -1
- umap/static/umap/js/modules/caption.js +8 -0
- umap/static/umap/js/modules/data/features.js +331 -202
- umap/static/umap/js/modules/data/layer.js +263 -152
- umap/static/umap/js/modules/facets.js +2 -2
- umap/static/umap/js/modules/form/builder.js +11 -7
- umap/static/umap/js/modules/form/fields.js +66 -26
- umap/static/umap/js/modules/formatter.js +78 -28
- umap/static/umap/js/modules/importer.js +6 -1
- umap/static/umap/js/modules/importers/opendata.js +138 -33
- umap/static/umap/js/modules/importers/openrouteservice.js +140 -0
- umap/static/umap/js/modules/managers.js +67 -0
- umap/static/umap/js/modules/printer.js +107 -0
- umap/static/umap/js/modules/rendering/controls.js +78 -2
- umap/static/umap/js/modules/rendering/icon.js +116 -87
- umap/static/umap/js/modules/rendering/layers/classified.js +8 -7
- umap/static/umap/js/modules/rendering/layers/cluster.js +199 -63
- umap/static/umap/js/modules/rendering/map.js +6 -2
- umap/static/umap/js/modules/rendering/template.js +71 -1
- umap/static/umap/js/modules/rendering/ui.js +111 -34
- umap/static/umap/js/modules/rules.js +76 -23
- umap/static/umap/js/modules/schema.js +27 -0
- umap/static/umap/js/modules/share.js +19 -12
- 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/bar.js +6 -1
- umap/static/umap/js/modules/ui/base.js +24 -9
- umap/static/umap/js/modules/ui/contextmenu.js +17 -7
- umap/static/umap/js/modules/ui/dialog.js +7 -4
- umap/static/umap/js/modules/ui/panel.js +7 -0
- umap/static/umap/js/modules/umap.js +84 -67
- umap/static/umap/js/modules/utils.js +8 -7
- umap/static/umap/js/umap.controls.js +22 -57
- umap/static/umap/locale/am_ET.js +81 -9
- umap/static/umap/locale/am_ET.json +81 -9
- umap/static/umap/locale/ar.js +81 -9
- umap/static/umap/locale/ar.json +81 -9
- umap/static/umap/locale/ast.js +81 -9
- umap/static/umap/locale/ast.json +81 -9
- umap/static/umap/locale/bg.js +81 -9
- umap/static/umap/locale/bg.json +81 -9
- umap/static/umap/locale/br.js +68 -29
- umap/static/umap/locale/br.json +68 -29
- umap/static/umap/locale/ca.js +88 -16
- umap/static/umap/locale/ca.json +88 -16
- umap/static/umap/locale/cs_CZ.js +81 -9
- umap/static/umap/locale/cs_CZ.json +81 -9
- umap/static/umap/locale/da.js +48 -9
- umap/static/umap/locale/da.json +48 -9
- umap/static/umap/locale/de.js +48 -9
- umap/static/umap/locale/de.json +48 -9
- umap/static/umap/locale/el.js +58 -13
- umap/static/umap/locale/el.json +58 -13
- umap/static/umap/locale/en.js +48 -9
- umap/static/umap/locale/en.json +48 -9
- umap/static/umap/locale/en_US.json +81 -9
- umap/static/umap/locale/es.js +48 -9
- umap/static/umap/locale/es.json +48 -9
- umap/static/umap/locale/et.js +81 -9
- umap/static/umap/locale/et.json +81 -9
- umap/static/umap/locale/eu.js +97 -25
- umap/static/umap/locale/eu.json +97 -25
- umap/static/umap/locale/fa_IR.js +81 -9
- umap/static/umap/locale/fa_IR.json +81 -9
- umap/static/umap/locale/fi.js +81 -9
- umap/static/umap/locale/fi.json +81 -9
- umap/static/umap/locale/fr.js +48 -9
- umap/static/umap/locale/fr.json +48 -9
- umap/static/umap/locale/gl.js +81 -9
- umap/static/umap/locale/gl.json +81 -9
- umap/static/umap/locale/he.js +81 -9
- umap/static/umap/locale/he.json +81 -9
- umap/static/umap/locale/hr.js +81 -9
- umap/static/umap/locale/hr.json +81 -9
- umap/static/umap/locale/hu.js +72 -27
- umap/static/umap/locale/hu.json +72 -27
- umap/static/umap/locale/id.js +81 -9
- umap/static/umap/locale/id.json +81 -9
- umap/static/umap/locale/is.js +81 -9
- umap/static/umap/locale/is.json +81 -9
- umap/static/umap/locale/it.js +48 -9
- umap/static/umap/locale/it.json +48 -9
- umap/static/umap/locale/ja.js +81 -9
- umap/static/umap/locale/ja.json +81 -9
- umap/static/umap/locale/ko.js +81 -9
- umap/static/umap/locale/ko.json +81 -9
- umap/static/umap/locale/lt.js +81 -9
- umap/static/umap/locale/lt.json +81 -9
- umap/static/umap/locale/ms.js +81 -9
- umap/static/umap/locale/ms.json +81 -9
- umap/static/umap/locale/nl.js +48 -9
- umap/static/umap/locale/nl.json +48 -9
- umap/static/umap/locale/no.js +81 -9
- umap/static/umap/locale/no.json +81 -9
- umap/static/umap/locale/pl.js +81 -9
- umap/static/umap/locale/pl.json +81 -9
- umap/static/umap/locale/pl_PL.json +81 -9
- umap/static/umap/locale/pt.js +81 -9
- umap/static/umap/locale/pt.json +81 -9
- umap/static/umap/locale/pt_BR.js +91 -19
- umap/static/umap/locale/pt_BR.json +91 -19
- umap/static/umap/locale/pt_PT.js +81 -9
- umap/static/umap/locale/pt_PT.json +81 -9
- umap/static/umap/locale/ro.js +81 -9
- umap/static/umap/locale/ro.json +81 -9
- umap/static/umap/locale/ru.js +81 -9
- umap/static/umap/locale/ru.json +81 -9
- umap/static/umap/locale/sk_SK.js +81 -9
- umap/static/umap/locale/sk_SK.json +81 -9
- umap/static/umap/locale/sl.js +81 -9
- umap/static/umap/locale/sl.json +81 -9
- umap/static/umap/locale/sr.js +81 -9
- umap/static/umap/locale/sr.json +81 -9
- umap/static/umap/locale/sv.js +81 -9
- umap/static/umap/locale/sv.json +81 -9
- umap/static/umap/locale/th_TH.js +81 -9
- umap/static/umap/locale/th_TH.json +81 -9
- umap/static/umap/locale/tr.js +81 -9
- umap/static/umap/locale/tr.json +81 -9
- umap/static/umap/locale/uk_UA.js +81 -9
- umap/static/umap/locale/uk_UA.json +81 -9
- umap/static/umap/locale/vi.js +81 -9
- umap/static/umap/locale/vi.json +81 -9
- umap/static/umap/locale/vi_VN.json +81 -9
- umap/static/umap/locale/zh.js +81 -9
- umap/static/umap/locale/zh.json +81 -9
- umap/static/umap/locale/zh_CN.json +81 -9
- umap/static/umap/locale/zh_TW.Big5.json +81 -9
- umap/static/umap/locale/zh_TW.js +98 -26
- umap/static/umap/locale/zh_TW.json +98 -26
- umap/static/umap/map.css +325 -102
- umap/static/umap/vars.css +1 -0
- umap/static/umap/vendors/betterknown/betterknown.mjs +287 -0
- umap/static/umap/vendors/editable/Leaflet.Editable.js +3 -1
- umap/static/umap/vendors/openrouteservice/ors-js-client.js +521 -0
- umap/static/umap/vendors/openrouteservice/ors-js-client.js.map +1 -0
- umap/static/umap/vendors/simple-elevation-chart/elevation.js +63 -0
- umap/static/umap/vendors/simple-elevation-chart/elevation.svg +8 -0
- umap/static/umap/vendors/snapdom/snapdom.min.mjs +3 -0
- umap/storage/fs.py +3 -2
- umap/storage/staticfiles.py +12 -0
- umap/templates/base.html +4 -1
- umap/templates/umap/css.html +0 -4
- umap/templates/umap/js.html +1 -3
- umap/tests/base.py +9 -1
- umap/tests/integration/test_basics.py +3 -1
- umap/tests/integration/test_conditional_rules.py +79 -37
- umap/tests/integration/test_datalayer.py +1 -1
- umap/tests/integration/test_draw_polygon.py +3 -5
- umap/tests/integration/test_draw_polyline.py +4 -6
- umap/tests/integration/test_draw_route.py +178 -0
- umap/tests/integration/test_edit_datalayer.py +1 -1
- umap/tests/integration/test_edit_map.py +1 -1
- umap/tests/integration/test_edit_marker.py +8 -8
- umap/tests/integration/test_edit_polygon.py +2 -2
- umap/tests/integration/test_export_map.py +84 -10
- umap/tests/integration/test_import.py +140 -0
- umap/tests/integration/test_map_preview.py +1 -1
- umap/tests/integration/test_optimistic_merge.py +72 -12
- umap/tests/integration/test_share.py +1 -1
- umap/tests/integration/test_tableeditor.py +10 -7
- umap/tests/integration/test_websocket_sync.py +4 -4
- umap/utils.py +37 -0
- umap/views.py +18 -2
- umap_project-3.3.0.dist-info/METADATA +76 -0
- {umap_project-3.1.2.dist-info → umap_project-3.3.0.dist-info}/RECORD +194 -188
- umap/static/umap/vendors/markercluster/MarkerCluster.Default.css +0 -60
- umap/static/umap/vendors/markercluster/MarkerCluster.css +0 -14
- umap/static/umap/vendors/markercluster/leaflet.markercluster.js +0 -2
- umap/static/umap/vendors/markercluster/leaflet.markercluster.js.map +0 -1
- umap_project-3.1.2.dist-info/METADATA +0 -68
- {umap_project-3.1.2.dist-info → umap_project-3.3.0.dist-info}/WHEEL +0 -0
- {umap_project-3.1.2.dist-info → umap_project-3.3.0.dist-info}/entry_points.txt +0 -0
- {umap_project-3.1.2.dist-info → umap_project-3.3.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
|
|
@@ -70,7 +70,11 @@ export class Form extends Utils.WithEvents {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
setter(field, value) {
|
|
73
|
-
|
|
73
|
+
if ('setter' in this.obj) {
|
|
74
|
+
this.obj.setter(field, value)
|
|
75
|
+
} else {
|
|
76
|
+
Utils.setObjectValue(this.obj, field, value)
|
|
77
|
+
}
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
restoreField(field) {
|
|
@@ -104,9 +108,13 @@ export class Form extends Utils.WithEvents {
|
|
|
104
108
|
finish() {}
|
|
105
109
|
|
|
106
110
|
getTemplate(helper) {
|
|
111
|
+
let tpl = helper.getTemplate()
|
|
112
|
+
if (helper.properties.label && !tpl.includes(helper.properties.label)) {
|
|
113
|
+
tpl = `<label>${helper.properties.label}${tpl}</label>`
|
|
114
|
+
}
|
|
107
115
|
return `
|
|
108
116
|
<div class="formbox" data-ref=container>
|
|
109
|
-
${
|
|
117
|
+
${tpl}
|
|
110
118
|
<small class="help-text" data-ref=helpText></small>
|
|
111
119
|
</div>`
|
|
112
120
|
}
|
|
@@ -171,11 +179,7 @@ export class MutatingForm extends Form {
|
|
|
171
179
|
|
|
172
180
|
setter(field, value) {
|
|
173
181
|
const oldValue = this.getter(field)
|
|
174
|
-
|
|
175
|
-
this.obj.setter(field, value)
|
|
176
|
-
} else {
|
|
177
|
-
super.setter(field, value)
|
|
178
|
-
}
|
|
182
|
+
super.setter(field, value)
|
|
179
183
|
if ('render' in this.obj) {
|
|
180
184
|
this.obj.render([field], this)
|
|
181
185
|
}
|
|
@@ -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() {
|
|
@@ -560,6 +582,9 @@ Fields.SlideshowDelay = class extends Fields.IntSelect {
|
|
|
560
582
|
Fields.DataLayerSwitcher = class extends Fields.Select {
|
|
561
583
|
getOptions() {
|
|
562
584
|
const options = []
|
|
585
|
+
if (this.properties.allowEmpty) {
|
|
586
|
+
options.push([null, translate('Import in a new layer')])
|
|
587
|
+
}
|
|
563
588
|
this.builder._umap.datalayers.reverse().map((datalayer) => {
|
|
564
589
|
if (
|
|
565
590
|
datalayer.isLoaded() &&
|
|
@@ -573,7 +598,7 @@ Fields.DataLayerSwitcher = class extends Fields.Select {
|
|
|
573
598
|
}
|
|
574
599
|
|
|
575
600
|
toHTML() {
|
|
576
|
-
return this.obj.datalayer
|
|
601
|
+
return this.obj.datalayer?.id
|
|
577
602
|
}
|
|
578
603
|
|
|
579
604
|
toJS() {
|
|
@@ -582,7 +607,7 @@ Fields.DataLayerSwitcher = class extends Fields.Select {
|
|
|
582
607
|
|
|
583
608
|
set() {
|
|
584
609
|
this.builder._umap.lastUsedDataLayer = this.toJS()
|
|
585
|
-
this.
|
|
610
|
+
this.builder.setter(this.field, this.toJS())
|
|
586
611
|
}
|
|
587
612
|
}
|
|
588
613
|
|
|
@@ -654,7 +679,7 @@ Fields.PropertyInput = class extends Fields.BlurInput {
|
|
|
654
679
|
super.build()
|
|
655
680
|
const autocomplete = new AutocompleteDatalist(this.input)
|
|
656
681
|
// Will be used on Umap and DataLayer
|
|
657
|
-
const properties = this.builder.obj.
|
|
682
|
+
const properties = this.builder.obj.fieldKeys
|
|
658
683
|
autocomplete.suggestions = properties
|
|
659
684
|
}
|
|
660
685
|
}
|
|
@@ -691,10 +716,10 @@ Fields.IconUrl = class extends Fields.BlurInput {
|
|
|
691
716
|
|
|
692
717
|
async onDefine() {
|
|
693
718
|
this.footer.innerHTML = ''
|
|
694
|
-
const [{
|
|
719
|
+
const [{ data }, response, error] = await this.builder._umap.server.get(
|
|
695
720
|
this.builder._umap.properties.urls.pictogram_list_json
|
|
696
721
|
)
|
|
697
|
-
if (!error) this.
|
|
722
|
+
if (!error) this.pictogramCollections = data
|
|
698
723
|
this.buildTabs()
|
|
699
724
|
const value = this.value()
|
|
700
725
|
if (Icon.RECENT.length) this.showRecentTab()
|
|
@@ -807,36 +832,52 @@ Fields.IconUrl = class extends Fields.BlurInput {
|
|
|
807
832
|
this.updatePreview()
|
|
808
833
|
}
|
|
809
834
|
|
|
810
|
-
addCategory(items, name) {
|
|
835
|
+
addCategory(items, name, parent, attribution = null) {
|
|
811
836
|
const hidden = name ? '' : ' hidden'
|
|
812
|
-
const [
|
|
837
|
+
const [container, { grid }] = Utils.loadTemplateWithRefs(`
|
|
813
838
|
<div class="umap-pictogram-category">
|
|
814
839
|
<h6${hidden}>${name}</h6>
|
|
815
840
|
<div class="umap-pictogram-grid" data-ref=grid></div>
|
|
816
841
|
</div>
|
|
817
842
|
`)
|
|
818
843
|
let hasIcons = false
|
|
819
|
-
|
|
844
|
+
const sorted = items.sort((a, b) => Utils.naturalSort(a.name, b.name, U.lang))
|
|
845
|
+
for (const item of sorted) {
|
|
846
|
+
item.attribution ??= attribution
|
|
820
847
|
hasIcons = this.addIconPreview(item, grid) || hasIcons
|
|
821
848
|
}
|
|
822
|
-
if (hasIcons)
|
|
849
|
+
if (hasIcons) parent.appendChild(container)
|
|
823
850
|
}
|
|
824
851
|
|
|
825
852
|
buildSymbolsList() {
|
|
826
853
|
this.grid.innerHTML = ''
|
|
827
854
|
const categories = {}
|
|
828
855
|
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)
|
|
856
|
+
const collectionsNames = Object.keys(this.pictogramCollections)
|
|
857
|
+
const [container, { select, icons }] = Utils.loadTemplateWithRefs(
|
|
858
|
+
'<div><select data-ref="select"></select><div data-ref="icons"></div></div>'
|
|
836
859
|
)
|
|
837
|
-
for (const
|
|
838
|
-
|
|
860
|
+
for (const name of collectionsNames) {
|
|
861
|
+
const option = Utils.loadTemplate(`<option value="${name}">${name}</option>`)
|
|
862
|
+
select.appendChild(option)
|
|
863
|
+
}
|
|
864
|
+
this.grid.appendChild(container)
|
|
865
|
+
select.hidden = collectionsNames.length === 1
|
|
866
|
+
const loadCollection = (name) => {
|
|
867
|
+
icons.innerHTML = ''
|
|
868
|
+
const collection = this.pictogramCollections[name || collectionsNames[0]]
|
|
869
|
+
if (!collection) return
|
|
870
|
+
const sorted = Object.entries(collection.categories).sort(([a], [b]) =>
|
|
871
|
+
Utils.naturalSort(a, b, U.lang)
|
|
872
|
+
)
|
|
873
|
+
for (const [name, items] of sorted) {
|
|
874
|
+
this.addCategory(items, name, icons, collection.attribution)
|
|
875
|
+
}
|
|
839
876
|
}
|
|
877
|
+
loadCollection()
|
|
878
|
+
select.addEventListener('change', (event) => {
|
|
879
|
+
loadCollection(event.target.value)
|
|
880
|
+
})
|
|
840
881
|
}
|
|
841
882
|
|
|
842
883
|
buildRecentList() {
|
|
@@ -844,7 +885,7 @@ Fields.IconUrl = class extends Fields.BlurInput {
|
|
|
844
885
|
const items = U.Icon.RECENT.map((src) => ({
|
|
845
886
|
src,
|
|
846
887
|
}))
|
|
847
|
-
this.addCategory(items)
|
|
888
|
+
this.addCategory(items, null, this.grid)
|
|
848
889
|
}
|
|
849
890
|
|
|
850
891
|
isDefault() {
|
|
@@ -1266,11 +1307,10 @@ Fields.Range = class extends Fields.FloatInput {
|
|
|
1266
1307
|
const step = this.properties.step || 1
|
|
1267
1308
|
const digits = step < 1 ? 1 : 0
|
|
1268
1309
|
const id = `range-${this.properties.label || this.name}`
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
) {
|
|
1310
|
+
const range = this.properties.max - this.properties.min
|
|
1311
|
+
const ticks = this.properties.ticks || Math.min(20, range / step)
|
|
1312
|
+
const tickStep = range / ticks
|
|
1313
|
+
for (let i = this.properties.min; i <= this.properties.max; i += tickStep) {
|
|
1274
1314
|
const ii = i.toFixed(digits)
|
|
1275
1315
|
options += `<option value="${ii}" label="${ii}"></option>`
|
|
1276
1316
|
}
|
|
@@ -2,35 +2,33 @@ 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
|
-
formatter: async (umap) => JSON.stringify(umap.toGeoJSON(), null, 2),
|
|
8
20
|
ext: '.geojson',
|
|
9
21
|
filetype: 'application/json',
|
|
10
22
|
},
|
|
11
23
|
gpx: {
|
|
12
|
-
formatter: async (umap) => await umap.formatter.toGPX(umap.toGeoJSON()),
|
|
13
24
|
ext: '.gpx',
|
|
14
25
|
filetype: 'application/gpx+xml',
|
|
15
26
|
},
|
|
16
27
|
kml: {
|
|
17
|
-
formatter: async (umap) => await umap.formatter.toKML(umap.toGeoJSON()),
|
|
18
28
|
ext: '.kml',
|
|
19
29
|
filetype: 'application/vnd.google-earth.kml+xml',
|
|
20
30
|
},
|
|
21
31
|
csv: {
|
|
22
|
-
formatter: async (umap) => {
|
|
23
|
-
const table = []
|
|
24
|
-
umap.eachFeature((feature) => {
|
|
25
|
-
const row = feature.toGeoJSON().properties
|
|
26
|
-
const center = feature.center
|
|
27
|
-
delete row._umap_options
|
|
28
|
-
row.Latitude = center.lat
|
|
29
|
-
row.Longitude = center.lng
|
|
30
|
-
table.push(row)
|
|
31
|
-
})
|
|
32
|
-
return csv2geojson.dsv.csvFormat(table)
|
|
33
|
-
},
|
|
34
32
|
ext: '.csv',
|
|
35
33
|
filetype: 'text/csv',
|
|
36
34
|
},
|
|
@@ -81,15 +79,30 @@ export class Formatter {
|
|
|
81
79
|
sexagesimal: false,
|
|
82
80
|
parseLatLon: (raw) => Number.parseFloat(raw.toString().replace(',', '.')),
|
|
83
81
|
},
|
|
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.
|
|
82
|
+
async (err, result) => {
|
|
88
83
|
if (result?.features.length) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
84
|
+
const first = result.features[0]
|
|
85
|
+
if (first.geometry === null) {
|
|
86
|
+
const geomFields = ['geom', 'geometry', 'wkt', 'geojson']
|
|
87
|
+
for (const field of geomFields) {
|
|
88
|
+
if (first.properties[field]) {
|
|
89
|
+
for (const feature of result.features) {
|
|
90
|
+
feature.geometry = await parseTextGeom(feature.properties[field])
|
|
91
|
+
delete feature.properties[field]
|
|
92
|
+
}
|
|
93
|
+
break
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (first.geometry === null) {
|
|
97
|
+
// csv2geojson fallback to null geometries when it cannot determine
|
|
98
|
+
// lat or lon columns. This is valid geojson, but unwanted from a user
|
|
99
|
+
// point of view.
|
|
100
|
+
err = {
|
|
101
|
+
type: 'Error',
|
|
102
|
+
message: translate(
|
|
103
|
+
'No geo column found: must be either `lat(itude)` and `lon(gitude)` or `geom(etry)`.'
|
|
104
|
+
),
|
|
105
|
+
}
|
|
93
106
|
}
|
|
94
107
|
}
|
|
95
108
|
}
|
|
@@ -152,17 +165,54 @@ export class Formatter {
|
|
|
152
165
|
}
|
|
153
166
|
}
|
|
154
167
|
|
|
155
|
-
async
|
|
168
|
+
async stringify(features, format) {
|
|
169
|
+
switch (format) {
|
|
170
|
+
case 'csv':
|
|
171
|
+
return await this.toCSV(features)
|
|
172
|
+
case 'gpx':
|
|
173
|
+
return await this.toGPX(features)
|
|
174
|
+
case 'kml':
|
|
175
|
+
return await this.toKML(features)
|
|
176
|
+
case 'geojson':
|
|
177
|
+
return await this.toGeoJSON(features)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async toGPX(features) {
|
|
156
182
|
const togpx = await import('../../vendors/geojson-to-gpx/index.js')
|
|
157
|
-
for (const feature of
|
|
183
|
+
for (const feature of features) {
|
|
158
184
|
feature.properties.desc = feature.properties.description
|
|
159
185
|
}
|
|
160
|
-
const gpx = togpx.default(
|
|
186
|
+
const gpx = togpx.default(this.toFeatureCollection(features))
|
|
161
187
|
return new XMLSerializer().serializeToString(gpx)
|
|
162
188
|
}
|
|
163
189
|
|
|
164
|
-
async toKML(
|
|
190
|
+
async toKML(features) {
|
|
165
191
|
const tokml = await import('../../vendors/tokml/tokml.es.js')
|
|
166
|
-
return tokml.toKML(
|
|
192
|
+
return tokml.toKML(this.toFeatureCollection(features))
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
toFeatureCollection(features) {
|
|
196
|
+
return {
|
|
197
|
+
type: 'FeatureCollection',
|
|
198
|
+
features: features.map((f) => f.toGeoJSON()),
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async toGeoJSON(features) {
|
|
203
|
+
return JSON.stringify(this.toFeatureCollection(features), null, 2)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async toCSV(features) {
|
|
207
|
+
const table = []
|
|
208
|
+
for (const feature of features) {
|
|
209
|
+
const row = feature.toGeoJSON().properties
|
|
210
|
+
const center = feature.center
|
|
211
|
+
delete row._umap_options
|
|
212
|
+
row.Latitude = center.lat
|
|
213
|
+
row.Longitude = center.lng
|
|
214
|
+
table.push(row)
|
|
215
|
+
}
|
|
216
|
+
return csv2geojson.dsv.csvFormat(table)
|
|
167
217
|
}
|
|
168
218
|
}
|
|
@@ -17,7 +17,7 @@ const TEMPLATE = `
|
|
|
17
17
|
<input type="file" multiple autofocus onchange />
|
|
18
18
|
<textarea onchange placeholder="${translate('Paste your data here')}"></textarea>
|
|
19
19
|
<input class="highlightable" type="url" placeholder="${translate('Provide an URL here')}" onchange />
|
|
20
|
-
<button class="
|
|
20
|
+
<button type=button class="importers" hidden data-ref="importersButton"><i class="icon icon-16 icon-magic"></i>${translate('Import helpers')}</button>
|
|
21
21
|
</fieldset>
|
|
22
22
|
<fieldset class="formbox">
|
|
23
23
|
<legend class="counter" data-help="importFormats">${translate(
|
|
@@ -169,8 +169,13 @@ export default class Importer extends Utils.WithTemplate {
|
|
|
169
169
|
this.onChange()
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
set layer(layer) {
|
|
173
|
+
this._layer = layer
|
|
174
|
+
}
|
|
175
|
+
|
|
172
176
|
get layer() {
|
|
173
177
|
return (
|
|
178
|
+
this._layer ||
|
|
174
179
|
this._umap.datalayers[this.layerId] ||
|
|
175
180
|
this._umap.createDirtyDataLayer({ name: this.layerName })
|
|
176
181
|
)
|
|
@@ -8,11 +8,21 @@ const PORTALS = [
|
|
|
8
8
|
url: 'https://data.ampmetropole.fr',
|
|
9
9
|
platform: 'opendatasoft',
|
|
10
10
|
},
|
|
11
|
+
{
|
|
12
|
+
name: 'Auverge-Rhône-Alpes',
|
|
13
|
+
url: 'https://admin.open-datara.fr',
|
|
14
|
+
platform: 'prodige',
|
|
15
|
+
},
|
|
11
16
|
{
|
|
12
17
|
name: 'Bordeaux Métropole',
|
|
13
18
|
url: 'https://opendata.bordeaux-metropole.fr',
|
|
14
19
|
platform: 'opendatasoft',
|
|
15
20
|
},
|
|
21
|
+
{
|
|
22
|
+
name: 'Nouvelle Aquitaine',
|
|
23
|
+
url: 'https://admin.sigena.fr',
|
|
24
|
+
platform: 'prodige',
|
|
25
|
+
},
|
|
16
26
|
{
|
|
17
27
|
name: 'Région Centre-Val de Loire',
|
|
18
28
|
url: 'https://data.centrevaldeloire.fr',
|
|
@@ -33,6 +43,21 @@ const PORTALS = [
|
|
|
33
43
|
url: 'https://data.iledefrance.fr',
|
|
34
44
|
platform: 'opendatasoft',
|
|
35
45
|
},
|
|
46
|
+
{
|
|
47
|
+
name: 'Martinique',
|
|
48
|
+
url: 'https://admin.geomartinique.fr',
|
|
49
|
+
platform: 'prodige',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'Région Pays de la Loire',
|
|
53
|
+
url: 'https://admin.sigloire.fr',
|
|
54
|
+
platform: 'prodige',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'Saint-Pierre et Miquelon',
|
|
58
|
+
url: 'https://admin.geospm.com',
|
|
59
|
+
platform: 'prodige',
|
|
60
|
+
},
|
|
36
61
|
{
|
|
37
62
|
name: 'Toulouse Métropole',
|
|
38
63
|
url: 'https://data.toulouse-metropole.fr',
|
|
@@ -57,11 +82,84 @@ const TEMPLATE = `
|
|
|
57
82
|
<option disabled selected value="">${translate('Choose a dataset')}</option>
|
|
58
83
|
</select>
|
|
59
84
|
<input type="hidden" name="geofield" data-ref="geofield">
|
|
60
|
-
<label><input type="checkbox" name="in_bbox">${translate('Limit results to current map view')}</label>
|
|
85
|
+
<label data-ref="in_bbox" hidden><input type="checkbox" name="in_bbox">${translate('Limit results to current map view')}</label>
|
|
61
86
|
</div>
|
|
62
87
|
</div>
|
|
63
88
|
`
|
|
64
89
|
|
|
90
|
+
class Connector {
|
|
91
|
+
constructor(umap, baseUrl) {
|
|
92
|
+
this.umap = umap
|
|
93
|
+
this.baseUrl = baseUrl
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
class Prodige extends Connector {
|
|
98
|
+
async datasets() {
|
|
99
|
+
const datasets = []
|
|
100
|
+
const endpoint = this.umap.proxyUrl(
|
|
101
|
+
`${this.baseUrl}/api/ogc-features/collections.json`,
|
|
102
|
+
3600
|
|
103
|
+
)
|
|
104
|
+
const response = await this.umap.request.get(endpoint)
|
|
105
|
+
if (!response?.ok) return datasets
|
|
106
|
+
const data = await response.json()
|
|
107
|
+
for (const dataset of data.collections) {
|
|
108
|
+
let url
|
|
109
|
+
for (const link of dataset.links) {
|
|
110
|
+
if (link.type === 'application/geo+json') url = link.href
|
|
111
|
+
}
|
|
112
|
+
if (!url) continue
|
|
113
|
+
datasets.push({
|
|
114
|
+
label: dataset.title,
|
|
115
|
+
url: this.umap.proxyUrl(url, 3600),
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
return datasets.sort((a, b) => Utils.naturalSort(a.label, b.label, U.lang))
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
class OpenDataSoft extends Connector {
|
|
123
|
+
async datasets() {
|
|
124
|
+
let results = []
|
|
125
|
+
let total = null
|
|
126
|
+
const hardLimit = 500
|
|
127
|
+
while (total === null || results.length < total) {
|
|
128
|
+
const offset = results.length
|
|
129
|
+
const response = await this.umap.request.get(
|
|
130
|
+
`${this.baseUrl}/api/explore/v2.1/catalog/datasets?where=features%20in%20%28%22geo%22%29&limit=100&offset=${offset}&order_by=title asc`
|
|
131
|
+
)
|
|
132
|
+
if (!response?.ok) break
|
|
133
|
+
const data = await response.json()
|
|
134
|
+
if (total === null) {
|
|
135
|
+
total = data.total_count
|
|
136
|
+
}
|
|
137
|
+
results = results.concat(data.results)
|
|
138
|
+
if (total === null || results.length > hardLimit) break
|
|
139
|
+
}
|
|
140
|
+
const datasets = []
|
|
141
|
+
for (const result of results) {
|
|
142
|
+
const fields = result.fields.filter((field) => field.type === 'geo_point_2d')
|
|
143
|
+
if (!fields.length) {
|
|
144
|
+
console.debug('No geofield found for', result)
|
|
145
|
+
continue
|
|
146
|
+
}
|
|
147
|
+
if (fields.length > 1) {
|
|
148
|
+
console.debug('More than one geofield found for', result)
|
|
149
|
+
}
|
|
150
|
+
const url = `${this.baseUrl}/api/explore/v2.1/catalog/datasets/${result.dataset_id}/exports/geojson?select=%2A&limit=-1&timezone=UTC&use_labels=false&epsg=4326`
|
|
151
|
+
const bbox_url = `${url}&where=in_bbox%28${fields[0].name}%2C%20{south},{west},{north},{east}%29`
|
|
152
|
+
datasets.push({
|
|
153
|
+
id: result.dataset_id,
|
|
154
|
+
label: `${result.metas.default.title} (${result.metas.default.records_count})`,
|
|
155
|
+
url,
|
|
156
|
+
bbox_url,
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
return datasets
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
65
163
|
export class Importer {
|
|
66
164
|
constructor(umap, options = {}) {
|
|
67
165
|
this.umap = umap
|
|
@@ -70,48 +168,54 @@ export class Importer {
|
|
|
70
168
|
this.portals = options.choices || PORTALS
|
|
71
169
|
}
|
|
72
170
|
|
|
171
|
+
resetSelect(select) {
|
|
172
|
+
Array.from(select.children).forEach((option) => {
|
|
173
|
+
if (!option.disabled) {
|
|
174
|
+
option.remove()
|
|
175
|
+
} else {
|
|
176
|
+
option.selected = true
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
|
|
73
181
|
async open(importer) {
|
|
74
|
-
|
|
75
|
-
const [container, { portals, datasets, geofield }] =
|
|
182
|
+
const [container, { portals, datasets, in_bbox }] =
|
|
76
183
|
Utils.loadTemplateWithRefs(TEMPLATE)
|
|
184
|
+
datasets.addEventListener('change', (event) => {
|
|
185
|
+
const select = event.target
|
|
186
|
+
const selected = select.options[select.selectedIndex]
|
|
187
|
+
const bbox_url = selected.dataset.bbox_url
|
|
188
|
+
in_bbox.checked = false
|
|
189
|
+
in_bbox.hidden = !bbox_url
|
|
190
|
+
})
|
|
77
191
|
portals.addEventListener('change', async (event) => {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
console.debug('No geofield found for', result)
|
|
95
|
-
continue
|
|
96
|
-
}
|
|
97
|
-
if (fields.length > 1) {
|
|
98
|
-
console.debug('More than one geofield found for', result)
|
|
99
|
-
}
|
|
100
|
-
fields_map[result.dataset_id] = fields[0].name
|
|
192
|
+
const select = event.target
|
|
193
|
+
const selected = select.options[select.selectedIndex]
|
|
194
|
+
const platform = selected.dataset.platform
|
|
195
|
+
let connector
|
|
196
|
+
if (platform === 'opendatasoft') {
|
|
197
|
+
connector = new OpenDataSoft(this.umap, selected.value)
|
|
198
|
+
} else if (platform === 'prodige') {
|
|
199
|
+
connector = new Prodige(this.umap, selected.value)
|
|
200
|
+
} else {
|
|
201
|
+
console.error('Unknown platform', platform)
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
const results = await connector.datasets(event.target.value)
|
|
205
|
+
if (results) {
|
|
206
|
+
this.resetSelect(datasets)
|
|
207
|
+
for (const result of results) {
|
|
101
208
|
const el = Utils.loadTemplate(
|
|
102
|
-
`<option value="${result.
|
|
209
|
+
`<option value="${result.url}" data-url="${result.url}" data-bbox_url="${result.bbox_url || ''}">${result.label}</option>`
|
|
103
210
|
)
|
|
104
211
|
datasets.appendChild(el)
|
|
105
212
|
}
|
|
106
213
|
datasets.hidden = false
|
|
107
214
|
}
|
|
108
215
|
})
|
|
109
|
-
datasets.addEventListener('change', (event) => {
|
|
110
|
-
geofield.value = fields_map[event.target.value]
|
|
111
|
-
})
|
|
112
216
|
for (const instance of this.portals) {
|
|
113
217
|
const el = Utils.loadTemplate(
|
|
114
|
-
`<option value="${instance.url}">${instance.name}</option>`
|
|
218
|
+
`<option value="${instance.url}" data-platform="${instance.platform}">${instance.name}</option>`
|
|
115
219
|
)
|
|
116
220
|
portals.appendChild(el)
|
|
117
221
|
}
|
|
@@ -121,9 +225,10 @@ export class Importer {
|
|
|
121
225
|
Alert.error(translate('Please choose an instance first.'))
|
|
122
226
|
return
|
|
123
227
|
}
|
|
124
|
-
let url =
|
|
228
|
+
let url = form.dataset
|
|
125
229
|
if (form.in_bbox) {
|
|
126
|
-
|
|
230
|
+
const selected = datasets.options[datasets.selectedIndex]
|
|
231
|
+
url = selected.dataset.bbox_url
|
|
127
232
|
}
|
|
128
233
|
importer.url = url
|
|
129
234
|
importer.format = 'geojson'
|