umap-project 2.7.0b2__py3-none-any.whl → 2.7.1__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/asgi.py +15 -0
- umap/locale/es/LC_MESSAGES/django.mo +0 -0
- umap/locale/es/LC_MESSAGES/django.po +134 -128
- umap/models.py +2 -1
- umap/settings/base.py +1 -1
- umap/static/umap/css/importers.css +4 -0
- umap/static/umap/img/16.svg +1 -184
- umap/static/umap/img/24-white.svg +1 -0
- umap/static/umap/img/24.svg +1 -0
- umap/static/umap/img/importers/cadastrefr.svg +23 -0
- umap/static/umap/img/source/16.svg +753 -200
- umap/static/umap/img/source/24-white.svg +3 -2
- umap/static/umap/img/source/24.svg +3 -2
- umap/static/umap/js/modules/browser.js +47 -0
- umap/static/umap/js/modules/caption.js +10 -4
- umap/static/umap/js/modules/data/layer.js +26 -14
- umap/static/umap/js/modules/importer.js +3 -0
- umap/static/umap/js/modules/importers/cadastrefr.js +62 -0
- umap/static/umap/js/modules/importers/communesfr.js +2 -2
- umap/static/umap/js/modules/rendering/layers/classified.js +2 -0
- umap/static/umap/js/modules/rendering/ui.js +30 -3
- umap/static/umap/js/modules/share.js +1 -3
- umap/static/umap/js/modules/slideshow.js +1 -1
- umap/static/umap/js/modules/sync/engine.js +14 -8
- umap/static/umap/js/modules/sync/hlc.js +3 -3
- umap/static/umap/js/modules/sync/updaters.js +14 -2
- umap/static/umap/js/modules/ui/contextmenu.js +5 -0
- umap/static/umap/js/modules/ui/panel.js +12 -1
- umap/static/umap/js/modules/utils.js +24 -4
- umap/static/umap/js/umap.controls.js +46 -21
- umap/static/umap/js/umap.core.js +1 -1
- umap/static/umap/js/umap.js +29 -20
- umap/static/umap/locale/ca.js +8 -4
- umap/static/umap/locale/ca.json +8 -4
- umap/static/umap/locale/en.js +5 -1
- umap/static/umap/locale/en.json +5 -1
- umap/static/umap/locale/es.js +330 -319
- umap/static/umap/locale/es.json +330 -319
- umap/static/umap/locale/fr.js +5 -1
- umap/static/umap/locale/fr.json +5 -1
- umap/static/umap/map.css +37 -7
- umap/static/umap/unittests/hlc.js +10 -3
- umap/static/umap/unittests/utils.js +24 -0
- umap/static/umap/vars.css +2 -1
- umap/static/umap/vendors/colorbrewer/colorbrewer.js +309 -317
- umap/static/umap/vendors/dompurify/purify.es.js +15 -16
- umap/static/umap/vendors/dompurify/purify.es.mjs.map +1 -1
- umap/static/umap/vendors/locatecontrol/L.Control.Locate.min.js +2 -2
- umap/static/umap/vendors/locatecontrol/L.Control.Locate.min.js.map +1 -1
- umap/static/umap/vendors/simple-statistics/simple-statistics.min.js +1 -1
- umap/static/umap/vendors/simple-statistics/simple-statistics.min.js.map +1 -1
- umap/templates/umap/js.html +0 -1
- umap/templates/umap/map_detail.html +2 -2
- umap/tests/fixtures/test_upload_data.csv +2 -2
- umap/tests/integration/test_browser.py +69 -7
- umap/tests/integration/test_caption.py +3 -3
- umap/tests/integration/test_datalayer.py +1 -5
- umap/tests/integration/test_edit_datalayer.py +1 -2
- umap/tests/integration/test_edit_map.py +1 -1
- umap/tests/integration/test_edit_marker.py +1 -1
- umap/tests/integration/test_facets_browser.py +3 -3
- umap/tests/integration/test_import.py +0 -4
- umap/tests/integration/test_map.py +0 -4
- umap/tests/integration/test_view_marker.py +63 -0
- umap/tests/test_map_views.py +19 -0
- {umap_project-2.7.0b2.dist-info → umap_project-2.7.1.dist-info}/METADATA +14 -10
- {umap_project-2.7.0b2.dist-info → umap_project-2.7.1.dist-info}/RECORD +71 -68
- {umap_project-2.7.0b2.dist-info → umap_project-2.7.1.dist-info}/WHEEL +0 -0
- {umap_project-2.7.0b2.dist-info → umap_project-2.7.1.dist-info}/entry_points.txt +0 -0
- {umap_project-2.7.0b2.dist-info → umap_project-2.7.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
2
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
3
|
|
|
4
|
-
<svg width="252" height="252" viewBox="0 0 66.674992 66.674992" version="1.1" id="svg2876" inkscape:version="1.
|
|
5
|
-
<sodipodi:namedview id="namedview2878" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:showpageshadow="2" inkscape:pageopacity="0.0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" inkscape:document-units="px" showgrid="true" showguides="true" inkscape:zoom="
|
|
4
|
+
<svg width="252" height="252" viewBox="0 0 66.674992 66.674992" version="1.1" id="svg2876" inkscape:version="1.4 (e7c3feb100, 2024-10-09)" sodipodi:docname="24-white.svg" inkscape:export-filename="../24-white.svg" inkscape:export-xdpi="7.52" inkscape:export-ydpi="7.52" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
|
5
|
+
<sodipodi:namedview id="namedview2878" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:showpageshadow="2" inkscape:pageopacity="0.0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" inkscape:document-units="px" showgrid="true" showguides="true" inkscape:zoom="5.3566812" inkscape:cx="50.030978" inkscape:cy="170.06799" inkscape:window-width="1920" inkscape:window-height="1011" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" inkscape:current-layer="layer1">
|
|
6
6
|
<inkscape:grid type="xygrid" id="grid2997" empspacing="6" originx="0" originy="0" spacingy="0.2645833" spacingx="0.2645833" units="px" visible="true" />
|
|
7
7
|
<inkscape:grid id="grid1" units="px" originx="0" originy="0" spacingx="9.5249989" spacingy="9.5249989" empcolor="#3f3fff" empopacity="0.25098039" color="#3f3fff" opacity="0.1254902" empspacing="1" dotted="false" gridanglex="30" gridanglez="30" visible="true" />
|
|
8
8
|
</sodipodi:namedview>
|
|
@@ -74,5 +74,6 @@
|
|
|
74
74
|
<g id="info" transform="matrix(0.33072916,0,0,0.33072916,-11.906249,-256.10415)" style="stroke-width:0.8">
|
|
75
75
|
<path id="path3762" style="fill:#f2f2f2;fill-opacity:1;stroke:#999999;stroke-width:0.2;stroke-dasharray:none;stroke-opacity:1" d="m 107.99999,838.36217 a 8,8 0 0 0 -7.999998,8 8,8 0 0 0 7.999998,8 8,8 0 0 0 8,-8 8,8 0 0 0 -8,-8 z m 0,2.5 a 1.5,1.5 0 0 1 1.5,1.5 1.5,1.5 0 0 1 -1.5,1.5 1.5,1.5 0 0 1 -1.5,-1.5 1.5,1.5 0 0 1 1.5,-1.5 z m -1,4.5 h 2 v 6 h -2 z" />
|
|
76
76
|
</g>
|
|
77
|
+
<path d="m 5.6444435,59.43432 v 1.034991 h 1.0541546 z m 1.1759257,1.612262 h -1.1759257 c -0.3247228,0 -0.5879626,-0.258453 -0.5879626,-0.577271 v -1.154545 h -2.3518514 v 5.195451 h 4.1157397 z m -4.1157397,-2.309089 h 3.0615854 l 1.6421175,1.612259 v 4.160465 c 0,0.318822 -0.2632403,0.577275 -0.5879632,0.577275 h -4.1157397 c -0.3247231,0 -0.5879631,-0.258453 -0.5879631,-0.577275 v -5.195451 c 0,-0.31882 0.26324,-0.577273 0.5879631,-0.577273 z m 1.7638882,3.92135 v -1.323625 h 0.5879632 v 1.323625 l 0.3800861,-0.373177 0.415753,0.408196 -1.0898206,1.070004 -1.0898208,-1.070004 0.4157526,-0.408196 z" fill-rule="evenodd" id="downloadfile" style="fill:#f2f2f2;fill-opacity:1;stroke-width:0.06614583;stroke:#999999;stroke-opacity:0.50980395;stroke-dasharray:none" />
|
|
77
78
|
</g>
|
|
78
79
|
</svg>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
2
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
3
|
|
|
4
|
-
<svg width="252" height="252" viewBox="0 0 66.674999 66.674999" version="1.1" id="svg6237" inkscape:version="1.
|
|
5
|
-
<sodipodi:namedview id="namedview6239" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:showpageshadow="2" inkscape:pageopacity="0.0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" inkscape:document-units="px" showgrid="true" showguides="true" inkscape:zoom="3.1596124" inkscape:cx="
|
|
4
|
+
<svg width="252" height="252" viewBox="0 0 66.674999 66.674999" version="1.1" id="svg6237" inkscape:version="1.4 (e7c3feb100, 2024-10-09)" sodipodi:docname="24.svg" inkscape:export-filename="../24.svg" inkscape:export-xdpi="7.52" inkscape:export-ydpi="7.52" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
|
5
|
+
<sodipodi:namedview id="namedview6239" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:showpageshadow="2" inkscape:pageopacity="0.0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" inkscape:document-units="px" showgrid="true" showguides="true" inkscape:zoom="3.1596124" inkscape:cx="118.68544" inkscape:cy="166.47612" inkscape:window-width="1920" inkscape:window-height="1011" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" inkscape:current-layer="layer1">
|
|
6
6
|
<inkscape:grid type="xygrid" id="grid6358" empspacing="6" originx="0" originy="0" spacingy="0.26458333" spacingx="0.26458333" units="px" visible="true" />
|
|
7
7
|
<inkscape:grid id="grid1" units="px" originx="0" originy="0" spacingx="9.5249999" spacingy="9.5249999" empcolor="#3f3fff" empopacity="0.25098039" color="#ff0000" opacity="0.83529412" empspacing="0" dotted="false" gridanglex="30" gridanglez="30" visible="true" />
|
|
8
8
|
</sodipodi:namedview>
|
|
@@ -98,5 +98,6 @@
|
|
|
98
98
|
<g id="info" transform="matrix(0.33072916,0,0,0.33072916,-11.90625,-256.10415)" style="fill:#4d4d4d;fill-opacity:1;stroke-width:0.8">
|
|
99
99
|
<path id="path3762" style="fill:#4d4d4d;fill-opacity:1;stroke:none;stroke-width:0.8" d="m 108,838.36217 a 8,8 0 0 0 -8,8 8,8 0 0 0 8,8 8,8 0 0 0 8,-8 8,8 0 0 0 -8,-8 z m 0,2.5 a 1.5,1.5 0 0 1 1.5,1.5 1.5,1.5 0 0 1 -1.5,1.5 1.5,1.5 0 0 1 -1.5,-1.5 1.5,1.5 0 0 1 1.5,-1.5 z m -1,4.5 h 2 v 6 h -2 z" />
|
|
100
100
|
</g>
|
|
101
|
+
<path d="m 5.6444435,59.43432 v 1.034991 h 1.0541546 z m 1.1759257,1.612262 h -1.1759257 c -0.3247228,0 -0.5879626,-0.258453 -0.5879626,-0.577271 v -1.154545 h -2.3518514 v 5.195451 h 4.1157397 z m -4.1157397,-2.309089 h 3.0615854 l 1.6421175,1.612259 v 4.160465 c 0,0.318822 -0.2632403,0.577275 -0.5879632,0.577275 h -4.1157397 c -0.3247231,0 -0.5879631,-0.258453 -0.5879631,-0.577275 v -5.195451 c 0,-0.31882 0.26324,-0.577273 0.5879631,-0.577273 z m 1.7638882,3.92135 v -1.323625 h 0.5879632 v 1.323625 l 0.3800861,-0.373177 0.415753,0.408196 -1.0898206,1.070004 -1.0898208,-1.070004 0.4157526,-0.408196 z" fill-rule="evenodd" id="downloadfile" style="fill:#464646;fill-opacity:1;stroke-width:0.192424" />
|
|
101
102
|
</g>
|
|
102
103
|
</svg>
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { DomEvent, DomUtil, stamp } from '../../vendors/leaflet/leaflet-src.esm.js'
|
|
2
2
|
import { translate } from './i18n.js'
|
|
3
3
|
import * as Icon from './rendering/icon.js'
|
|
4
|
+
import * as Utils from './utils.js'
|
|
5
|
+
import { EXPORT_FORMATS } from './formatter.js'
|
|
6
|
+
import ContextMenu from './ui/contextmenu.js'
|
|
4
7
|
|
|
5
8
|
export default class Browser {
|
|
6
9
|
constructor(map) {
|
|
@@ -98,6 +101,7 @@ export default class Browser {
|
|
|
98
101
|
datalayer.eachFeature((feature) => this.addFeature(feature, container))
|
|
99
102
|
|
|
100
103
|
const total = datalayer.count()
|
|
104
|
+
if (!total) return
|
|
101
105
|
const current = container.querySelectorAll('li').length
|
|
102
106
|
const count = total === current ? total : `${current}/${total}`
|
|
103
107
|
const counter = DomUtil.create('span', 'datalayer-counter', headline)
|
|
@@ -164,6 +168,7 @@ export default class Browser {
|
|
|
164
168
|
})
|
|
165
169
|
this.filtersTitle = container.querySelector('summary')
|
|
166
170
|
this.toggleBadge()
|
|
171
|
+
this.addMainToolbox(container)
|
|
167
172
|
this.dataContainer = DomUtil.create('div', '', container)
|
|
168
173
|
|
|
169
174
|
let fields = [
|
|
@@ -215,6 +220,48 @@ export default class Browser {
|
|
|
215
220
|
}
|
|
216
221
|
}
|
|
217
222
|
|
|
223
|
+
addMainToolbox(container) {
|
|
224
|
+
const [toolbox, { toggle, fitBounds, download }] = Utils.loadTemplateWithRefs(`
|
|
225
|
+
<div class="main-toolbox">
|
|
226
|
+
<i class="icon icon-16 icon-eye" title="${translate('show/hide all layers')}" data-ref="toggle"></i>
|
|
227
|
+
<i class="icon icon-16 icon-zoom" title="${translate('zoom to data extent')}" data-ref="fitBounds"></i>
|
|
228
|
+
<i class="icon icon-16 icon-download" title="${translate('download visible data')}" data-ref="download"></i>
|
|
229
|
+
</div>
|
|
230
|
+
`)
|
|
231
|
+
container.appendChild(toolbox)
|
|
232
|
+
toggle.addEventListener('click', () => this.toggleLayers())
|
|
233
|
+
fitBounds.addEventListener('click', () => this.map.fitDataBounds())
|
|
234
|
+
download.addEventListener('click', () => this.downloadVisible(download))
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
downloadVisible(element) {
|
|
238
|
+
const menu = new ContextMenu({ fixed: true })
|
|
239
|
+
const items = []
|
|
240
|
+
for (const format of Object.keys(EXPORT_FORMATS)) {
|
|
241
|
+
items.push({
|
|
242
|
+
label: format,
|
|
243
|
+
action: () => this.map.share.download(format),
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
menu.openBelow(element, items)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
toggleLayers() {
|
|
250
|
+
// If at least one layer is shown, hide it
|
|
251
|
+
// otherwise show all
|
|
252
|
+
let allHidden = true
|
|
253
|
+
this.map.eachBrowsableDataLayer((datalayer) => {
|
|
254
|
+
if (datalayer.isVisible()) allHidden = false
|
|
255
|
+
})
|
|
256
|
+
this.map.eachBrowsableDataLayer((datalayer) => {
|
|
257
|
+
if (allHidden) {
|
|
258
|
+
datalayer.show()
|
|
259
|
+
} else {
|
|
260
|
+
if (datalayer.isVisible()) datalayer.hide()
|
|
261
|
+
}
|
|
262
|
+
})
|
|
263
|
+
}
|
|
264
|
+
|
|
218
265
|
static backButton(map) {
|
|
219
266
|
const button = DomUtil.createButtonIcon(
|
|
220
267
|
DomUtil.create('li', '', undefined),
|
|
@@ -40,15 +40,21 @@ export default class Caption {
|
|
|
40
40
|
)
|
|
41
41
|
const creditsContainer = DomUtil.create('div', 'credits-container', container)
|
|
42
42
|
this.addCredits(creditsContainer)
|
|
43
|
-
this.map.panel.open({ content: container })
|
|
43
|
+
this.map.panel.open({ content: container }).then(() => {
|
|
44
|
+
// Create the legend when the panel is actually on the DOM
|
|
45
|
+
this.map.eachDataLayerReverse((datalayer) => datalayer.renderLegend())
|
|
46
|
+
})
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
addDataLayer(datalayer, container) {
|
|
47
50
|
if (!datalayer.options.inCaption) return
|
|
48
|
-
const p = DomUtil.create(
|
|
49
|
-
|
|
51
|
+
const p = DomUtil.create(
|
|
52
|
+
'p',
|
|
53
|
+
`caption-item ${datalayer.cssId}`,
|
|
54
|
+
container
|
|
55
|
+
)
|
|
56
|
+
const legend = DomUtil.create('span', 'datalayer-legend', p)
|
|
50
57
|
const headline = DomUtil.create('strong', '', p)
|
|
51
|
-
datalayer.renderLegend(legend)
|
|
52
58
|
if (datalayer.options.description) {
|
|
53
59
|
DomUtil.element({
|
|
54
60
|
tagName: 'span',
|
|
@@ -115,6 +115,10 @@ export class DataLayer {
|
|
|
115
115
|
return this._isDeleted
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
get cssId() {
|
|
119
|
+
return `datalayer-${stamp(this)}`
|
|
120
|
+
}
|
|
121
|
+
|
|
118
122
|
getSyncMetadata() {
|
|
119
123
|
return {
|
|
120
124
|
subject: 'datalayer',
|
|
@@ -235,6 +239,7 @@ export class DataLayer {
|
|
|
235
239
|
}
|
|
236
240
|
|
|
237
241
|
dataChanged() {
|
|
242
|
+
if (!this.hasDataLoaded()) return
|
|
238
243
|
this.map.onDataLayersChanged()
|
|
239
244
|
this.layer.dataChanged()
|
|
240
245
|
}
|
|
@@ -242,10 +247,15 @@ export class DataLayer {
|
|
|
242
247
|
fromGeoJSON(geojson, sync = true) {
|
|
243
248
|
this.addData(geojson, sync)
|
|
244
249
|
this._geojson = geojson
|
|
245
|
-
this.
|
|
250
|
+
this.onDataLoaded()
|
|
246
251
|
this.dataChanged()
|
|
247
252
|
}
|
|
248
253
|
|
|
254
|
+
onDataLoaded() {
|
|
255
|
+
this._dataloaded = true
|
|
256
|
+
this.renderLegend()
|
|
257
|
+
}
|
|
258
|
+
|
|
249
259
|
async fromUmapGeoJSON(geojson) {
|
|
250
260
|
if (geojson._storage) geojson._umap_options = geojson._storage // Retrocompat
|
|
251
261
|
if (geojson._umap_options) this.setOptions(geojson._umap_options)
|
|
@@ -384,7 +394,7 @@ export class DataLayer {
|
|
|
384
394
|
this.indexProperties(feature)
|
|
385
395
|
this.map.features_index[feature.getSlug()] = feature
|
|
386
396
|
this.showFeature(feature)
|
|
387
|
-
|
|
397
|
+
this.dataChanged()
|
|
388
398
|
}
|
|
389
399
|
|
|
390
400
|
removeFeature(feature, sync) {
|
|
@@ -395,7 +405,7 @@ export class DataLayer {
|
|
|
395
405
|
feature.disconnectFromDataLayer(this)
|
|
396
406
|
this._index.splice(this._index.indexOf(id), 1)
|
|
397
407
|
delete this._features[id]
|
|
398
|
-
if (this.
|
|
408
|
+
if (this.isVisible()) this.dataChanged()
|
|
399
409
|
}
|
|
400
410
|
|
|
401
411
|
indexProperties(feature) {
|
|
@@ -802,13 +812,12 @@ export class DataLayer {
|
|
|
802
812
|
this
|
|
803
813
|
)
|
|
804
814
|
if (this.umap_id) {
|
|
805
|
-
const
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
)
|
|
815
|
+
const filename = `${Utils.slugify(this.options.name)}.geojson`
|
|
816
|
+
const download = Utils.loadTemplate(`
|
|
817
|
+
<a class="button" href="${this._dataUrl()}" download="${filename}">
|
|
818
|
+
<i class="icon icon-24 icon-download"></i>${translate('Download')}
|
|
819
|
+
</a>`)
|
|
820
|
+
advancedButtons.appendChild(download)
|
|
812
821
|
}
|
|
813
822
|
const backButton = DomUtil.createButtonIcon(
|
|
814
823
|
undefined,
|
|
@@ -1120,10 +1129,13 @@ export class DataLayer {
|
|
|
1120
1129
|
return 'displayName'
|
|
1121
1130
|
}
|
|
1122
1131
|
|
|
1123
|
-
renderLegend(
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1132
|
+
renderLegend() {
|
|
1133
|
+
for (const container of document.querySelectorAll(`.${this.cssId} .datalayer-legend`)) {
|
|
1134
|
+
container.innerHTML = ''
|
|
1135
|
+
if (this.layer.renderLegend) return this.layer.renderLegend(container)
|
|
1136
|
+
const color = DomUtil.create('span', 'datalayer-color', container)
|
|
1137
|
+
color.style.backgroundColor = this.getColor()
|
|
1138
|
+
}
|
|
1127
1139
|
}
|
|
1128
1140
|
|
|
1129
1141
|
renderToolbox(container) {
|
|
@@ -69,6 +69,9 @@ export default class Importer {
|
|
|
69
69
|
case 'communesfr':
|
|
70
70
|
import('./importers/communesfr.js').then(register)
|
|
71
71
|
break
|
|
72
|
+
case 'cadastrefr':
|
|
73
|
+
import('./importers/cadastrefr.js').then(register)
|
|
74
|
+
break
|
|
72
75
|
case 'overpass':
|
|
73
76
|
import('./importers/overpass.js').then(register)
|
|
74
77
|
break
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { DomUtil } from '../../../vendors/leaflet/leaflet-src.esm.js'
|
|
2
|
+
import { BaseAjax, SingleMixin } from '../autocomplete.js'
|
|
3
|
+
import * as Util from '../utils.js'
|
|
4
|
+
import { AutocompleteCommunes } from './communesfr.js'
|
|
5
|
+
|
|
6
|
+
const TEMPLATE = `
|
|
7
|
+
<h3>Cadastre</h3>
|
|
8
|
+
<p>Importer les données cadastrales d’une commune française.</p>
|
|
9
|
+
<select name="theme">
|
|
10
|
+
<option value="batiments">Bâtiments</option>
|
|
11
|
+
<option value="communes">Communes</option>
|
|
12
|
+
<option value="feuilles">Feuilles</option>
|
|
13
|
+
<option value="lieux_dits">Lieux dits</option>
|
|
14
|
+
<option value="parcelles" selected>Parcelles</option>
|
|
15
|
+
<option value="prefixes_sections">Préfixes sections</option>
|
|
16
|
+
<option value="sections">Sections</option>
|
|
17
|
+
<option value="subdivisions_fiscales">Subdivisions fiscales</option>
|
|
18
|
+
</select>
|
|
19
|
+
<label id="boundary">
|
|
20
|
+
</label>
|
|
21
|
+
`
|
|
22
|
+
|
|
23
|
+
export class Importer {
|
|
24
|
+
constructor(map, options) {
|
|
25
|
+
this.name = options.name || 'Cadastre'
|
|
26
|
+
this.id = 'cadastrefr'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async open(importer) {
|
|
30
|
+
let boundary = null
|
|
31
|
+
let boundaryName = null
|
|
32
|
+
const container = DomUtil.create('div')
|
|
33
|
+
container.innerHTML = TEMPLATE
|
|
34
|
+
const select = container.querySelector('select')
|
|
35
|
+
const options = {
|
|
36
|
+
placeholder: 'Nom ou code INSEE…',
|
|
37
|
+
url: 'https://geo.api.gouv.fr/communes?nom={q}&limit=5',
|
|
38
|
+
on_select: (choice) => {
|
|
39
|
+
boundary = choice.item.value
|
|
40
|
+
boundaryName = choice.item.label
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
this.autocomplete = new AutocompleteCommunes(container, options)
|
|
44
|
+
|
|
45
|
+
const confirm = (form) => {
|
|
46
|
+
if (!boundary || !form.theme) {
|
|
47
|
+
Alert.error(translate('Please choose a theme and a boundary first.'))
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
importer.url = `https://cadastre.data.gouv.fr/bundler/cadastre-etalab/communes/${boundary}/geojson/${form.theme}`
|
|
51
|
+
importer.format = 'geojson'
|
|
52
|
+
importer.layerName = `${boundaryName} — ${select.options[select.selectedIndex].textContent}`
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
importer.dialog
|
|
56
|
+
.open({
|
|
57
|
+
template: container,
|
|
58
|
+
className: `${this.id} importer dark`,
|
|
59
|
+
})
|
|
60
|
+
.then(confirm)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -2,7 +2,7 @@ import { DomUtil } from '../../../vendors/leaflet/leaflet-src.esm.js'
|
|
|
2
2
|
import { BaseAjax, SingleMixin } from '../autocomplete.js'
|
|
3
3
|
import * as Util from '../utils.js'
|
|
4
4
|
|
|
5
|
-
class
|
|
5
|
+
export class AutocompleteCommunes extends SingleMixin(BaseAjax) {
|
|
6
6
|
createResult(item) {
|
|
7
7
|
return super.createResult({
|
|
8
8
|
value: item.code,
|
|
@@ -46,7 +46,7 @@ export class Importer {
|
|
|
46
46
|
importer.dialog.close()
|
|
47
47
|
},
|
|
48
48
|
}
|
|
49
|
-
this.autocomplete = new
|
|
49
|
+
this.autocomplete = new AutocompleteCommunes(container, options)
|
|
50
50
|
|
|
51
51
|
importer.dialog.open({
|
|
52
52
|
template: container,
|
|
@@ -3,6 +3,7 @@ import { translate } from '../../i18n.js'
|
|
|
3
3
|
import { LayerMixin } from './base.js'
|
|
4
4
|
import * as Utils from '../../utils.js'
|
|
5
5
|
import { CircleMarker } from '../ui.js'
|
|
6
|
+
import colorbrewer from '../../../../vendors/colorbrewer/colorbrewer.js'
|
|
6
7
|
|
|
7
8
|
// Layer where each feature color is relative to the others,
|
|
8
9
|
// so we need all features before behing able to set one
|
|
@@ -74,6 +75,7 @@ const ClassifiedMixin = {
|
|
|
74
75
|
},
|
|
75
76
|
|
|
76
77
|
renderLegend: function (container) {
|
|
78
|
+
if (!this.datalayer.hasDataLoaded()) return
|
|
77
79
|
const parent = DomUtil.create('ul', '', container)
|
|
78
80
|
const items = this.getLegendItems()
|
|
79
81
|
for (const [color, label] of items) {
|
|
@@ -32,10 +32,18 @@ const FeatureMixin = {
|
|
|
32
32
|
if (map.editedFeature === this.feature) {
|
|
33
33
|
this.feature._marked_for_deletion = true
|
|
34
34
|
this.feature.endEdit()
|
|
35
|
-
map.
|
|
35
|
+
if (map.editedFeature === this.feature) {
|
|
36
|
+
map.editPanel.close()
|
|
37
|
+
}
|
|
36
38
|
}
|
|
37
39
|
},
|
|
38
40
|
|
|
41
|
+
_removeIcon: function () {
|
|
42
|
+
// It may not be in the DOM, and Leaflet does not deal with this
|
|
43
|
+
// situation
|
|
44
|
+
if (this._icon) Marker.prototype._removeIcon.call(this)
|
|
45
|
+
},
|
|
46
|
+
|
|
39
47
|
addInteractions: function () {
|
|
40
48
|
this.on('contextmenu editable:vertex:contextmenu', this.onContextMenu)
|
|
41
49
|
this.on('click', this.onClick)
|
|
@@ -86,8 +94,9 @@ const FeatureMixin = {
|
|
|
86
94
|
|
|
87
95
|
onContextMenu: function (event) {
|
|
88
96
|
DomEvent.stop(event)
|
|
89
|
-
const items = this.
|
|
90
|
-
|
|
97
|
+
const items = this.feature
|
|
98
|
+
.getContextMenuItems(event)
|
|
99
|
+
.concat(this._map.getContextMenuItems(event))
|
|
91
100
|
this._map.contextmenu.open(event.originalEvent, items)
|
|
92
101
|
},
|
|
93
102
|
|
|
@@ -160,6 +169,12 @@ export const LeafletMarker = Marker.extend({
|
|
|
160
169
|
return this.setLatLng(latlng)
|
|
161
170
|
},
|
|
162
171
|
|
|
172
|
+
getEvents: function () {
|
|
173
|
+
const events = Marker.prototype.getEvents.call(this)
|
|
174
|
+
events.moveend = this.onMoveEnd
|
|
175
|
+
return events
|
|
176
|
+
},
|
|
177
|
+
|
|
163
178
|
addInteractions() {
|
|
164
179
|
PointMixin.addInteractions.call(this)
|
|
165
180
|
this._popupHandlersAdded = true // prevent Leaflet from binding event on bindPopup
|
|
@@ -167,7 +182,19 @@ export const LeafletMarker = Marker.extend({
|
|
|
167
182
|
this.on('popupclose', this.resetHighlight)
|
|
168
183
|
},
|
|
169
184
|
|
|
185
|
+
onMoveEnd: function () {
|
|
186
|
+
this._initIcon()
|
|
187
|
+
this.update()
|
|
188
|
+
},
|
|
189
|
+
|
|
170
190
|
_initIcon: function () {
|
|
191
|
+
if (!this._map.getBounds().contains(this.getCenter())) {
|
|
192
|
+
if (this._icon) this._removeIcon()
|
|
193
|
+
if (this._tooltip && this.isTooltipOpen()) {
|
|
194
|
+
this.unbindTooltip()
|
|
195
|
+
}
|
|
196
|
+
return
|
|
197
|
+
}
|
|
171
198
|
this.options.icon = this.getIcon()
|
|
172
199
|
Marker.prototype._initIcon.call(this)
|
|
173
200
|
// Allow to run code when icon is actually part of the DOM
|
|
@@ -142,9 +142,7 @@ export default class Share {
|
|
|
142
142
|
async format(mode) {
|
|
143
143
|
const type = EXPORT_FORMATS[mode]
|
|
144
144
|
const content = await type.formatter(this.map)
|
|
145
|
-
|
|
146
|
-
name = name.replace(/[^a-z0-9]/gi, '_').toLowerCase()
|
|
147
|
-
const filename = name + type.ext
|
|
145
|
+
const filename = Utils.slugify(this.map.options.name) + type.ext
|
|
148
146
|
return { content, filetype: type.filetype, filename }
|
|
149
147
|
}
|
|
150
148
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import * as Utils from '../utils.js'
|
|
2
|
+
import { HybridLogicalClock } from './hlc.js'
|
|
1
3
|
import { DataLayerUpdater, FeatureUpdater, MapUpdater } from './updaters.js'
|
|
2
4
|
import { WebSocketTransport } from './websocket.js'
|
|
3
|
-
import { HybridLogicalClock } from './hlc.js'
|
|
4
|
-
import * as Utils from '../utils.js'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* The syncEngine exposes an API to sync messages between peers over the network.
|
|
@@ -81,7 +81,7 @@ export class SyncEngine {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
_send(inputMessage) {
|
|
84
|
-
|
|
84
|
+
const message = this._operations.addLocal(inputMessage)
|
|
85
85
|
|
|
86
86
|
if (this.offline) return
|
|
87
87
|
if (this.transport) {
|
|
@@ -101,6 +101,11 @@ export class SyncEngine {
|
|
|
101
101
|
updater.applyMessage(operation)
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
getNumberOfConnectedPeers() {
|
|
105
|
+
if (this.peers) return this.peers.length
|
|
106
|
+
return 0
|
|
107
|
+
}
|
|
108
|
+
|
|
104
109
|
/**
|
|
105
110
|
* This is called by the transport layer on new messages,
|
|
106
111
|
* and dispatches the different "on*" methods.
|
|
@@ -146,10 +151,10 @@ export class SyncEngine {
|
|
|
146
151
|
onJoinResponse({ uuid, peers }) {
|
|
147
152
|
debug('received join response', { uuid, peers })
|
|
148
153
|
this.uuid = uuid
|
|
149
|
-
this.peers
|
|
154
|
+
this.onListPeersResponse({ peers })
|
|
150
155
|
|
|
151
156
|
// Get one peer at random
|
|
152
|
-
|
|
157
|
+
const randomPeer = this._getRandomPeer()
|
|
153
158
|
|
|
154
159
|
if (randomPeer) {
|
|
155
160
|
// Retrieve the operations which happened before join.
|
|
@@ -168,6 +173,7 @@ export class SyncEngine {
|
|
|
168
173
|
onListPeersResponse({ peers }) {
|
|
169
174
|
debug('received peerinfo', { peers })
|
|
170
175
|
this.peers = peers
|
|
176
|
+
this.updaters.map.update({ key: 'numberOfConnectedPeers' })
|
|
171
177
|
}
|
|
172
178
|
|
|
173
179
|
/**
|
|
@@ -248,7 +254,7 @@ export class SyncEngine {
|
|
|
248
254
|
* @returns {string|bool} the selected peer uuid, or False if none was found.
|
|
249
255
|
*/
|
|
250
256
|
_getRandomPeer() {
|
|
251
|
-
|
|
257
|
+
const otherPeers = this.peers.filter((p) => p !== this.uuid)
|
|
252
258
|
if (otherPeers.length > 0) {
|
|
253
259
|
const random = Math.floor(Math.random() * otherPeers.length)
|
|
254
260
|
return otherPeers[random]
|
|
@@ -302,7 +308,7 @@ export class Operations {
|
|
|
302
308
|
* @returns {*} clock-aware message
|
|
303
309
|
*/
|
|
304
310
|
addLocal(inputMessage) {
|
|
305
|
-
|
|
311
|
+
const message = { ...inputMessage, hlc: this._hlc.tick() }
|
|
306
312
|
this._operations.push(message)
|
|
307
313
|
return message
|
|
308
314
|
}
|
|
@@ -342,7 +348,7 @@ export class Operations {
|
|
|
342
348
|
*/
|
|
343
349
|
storeRemoteOperations(remoteOperations) {
|
|
344
350
|
// get the highest date from the passed operations
|
|
345
|
-
|
|
351
|
+
const greatestHLC = remoteOperations
|
|
346
352
|
.map((op) => op.hlc)
|
|
347
353
|
.reduce((max, current) => (current > max ? current : max))
|
|
348
354
|
|
|
@@ -30,12 +30,12 @@ export class HybridLogicalClock {
|
|
|
30
30
|
* @returns object
|
|
31
31
|
*/
|
|
32
32
|
parse(raw) {
|
|
33
|
-
|
|
33
|
+
const tokens = raw.split(':')
|
|
34
34
|
|
|
35
35
|
if (tokens.length !== 3) {
|
|
36
36
|
throw new SyntaxError(`Unable to parse ${raw}`)
|
|
37
37
|
}
|
|
38
|
-
|
|
38
|
+
const [walltime, rawNN, id] = tokens
|
|
39
39
|
|
|
40
40
|
let nn = Number.parseInt(rawNN)
|
|
41
41
|
if (Number.isNaN(nn)) {
|
|
@@ -92,7 +92,7 @@ export class HybridLogicalClock {
|
|
|
92
92
|
if (now > local.walltime && now > remote.walltime) {
|
|
93
93
|
nextValue = { ...local, walltime: now }
|
|
94
94
|
} else if (local.walltime == remote.walltime) {
|
|
95
|
-
|
|
95
|
+
const nn = Math.max(local.nn, remote.nn) + 1
|
|
96
96
|
nextValue = { ...local, nn: nn }
|
|
97
97
|
} else if (remote.walltime > local.walltime) {
|
|
98
98
|
nextValue = { ...remote, id: local.id, nn: remote.nn + 1 }
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { fieldInSchema } from '../utils.js'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Updaters are classes able to convert messages
|
|
3
5
|
* received from other peers (or from the server) to changes on the map.
|
|
@@ -42,7 +44,10 @@ class BaseUpdater {
|
|
|
42
44
|
|
|
43
45
|
export class MapUpdater extends BaseUpdater {
|
|
44
46
|
update({ key, value }) {
|
|
45
|
-
|
|
47
|
+
if (fieldInSchema(key)) {
|
|
48
|
+
this.updateObjectValue(this.map, key, value)
|
|
49
|
+
}
|
|
50
|
+
|
|
46
51
|
this.map.render([key])
|
|
47
52
|
}
|
|
48
53
|
}
|
|
@@ -56,7 +61,14 @@ export class DataLayerUpdater extends BaseUpdater {
|
|
|
56
61
|
|
|
57
62
|
update({ key, metadata, value }) {
|
|
58
63
|
const datalayer = this.getDataLayerFromID(metadata.id)
|
|
59
|
-
|
|
64
|
+
if (fieldInSchema(key)) {
|
|
65
|
+
this.updateObjectValue(datalayer, key, value)
|
|
66
|
+
} else {
|
|
67
|
+
console.debug(
|
|
68
|
+
'Not applying update for datalayer because key is not in the schema',
|
|
69
|
+
key
|
|
70
|
+
)
|
|
71
|
+
}
|
|
60
72
|
datalayer.render([key])
|
|
61
73
|
}
|
|
62
74
|
}
|
|
@@ -21,6 +21,11 @@ export default class ContextMenu extends Positioned {
|
|
|
21
21
|
this.openAt([left, top], items)
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
openBelow(element, items) {
|
|
25
|
+
const coords = this.getPosition(element)
|
|
26
|
+
this.openAt([coords.left, coords.bottom], items)
|
|
27
|
+
}
|
|
28
|
+
|
|
24
29
|
openAt([left, top], items) {
|
|
25
30
|
this.container.innerHTML = ''
|
|
26
31
|
for (const item of items) {
|
|
@@ -25,6 +25,9 @@ export class Panel {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
open({ content, className, actions = [] } = {}) {
|
|
28
|
+
if (this.isOpen()) {
|
|
29
|
+
this.onClose()
|
|
30
|
+
}
|
|
28
31
|
this.container.className = `with-transition panel window ${this.className} ${
|
|
29
32
|
this.mode || ''
|
|
30
33
|
}`
|
|
@@ -71,10 +74,13 @@ export class Panel {
|
|
|
71
74
|
|
|
72
75
|
close() {
|
|
73
76
|
document.body.classList.remove(`panel-${this.className.split(' ')[0]}-on`)
|
|
77
|
+
this.onClose()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
onClose() {
|
|
74
81
|
if (DomUtil.hasClass(this.container, 'on')) {
|
|
75
82
|
DomUtil.removeClass(this.container, 'on')
|
|
76
83
|
this.map.invalidateSize({ pan: false })
|
|
77
|
-
this.map.editedFeature = null
|
|
78
84
|
}
|
|
79
85
|
}
|
|
80
86
|
}
|
|
@@ -84,6 +90,11 @@ export class EditPanel extends Panel {
|
|
|
84
90
|
super(map)
|
|
85
91
|
this.className = 'right dark'
|
|
86
92
|
}
|
|
93
|
+
|
|
94
|
+
onClose() {
|
|
95
|
+
super.onClose()
|
|
96
|
+
this.map.editedFeature = null
|
|
97
|
+
}
|
|
87
98
|
}
|
|
88
99
|
|
|
89
100
|
export class FullPanel extends Panel {
|