diff --git a/package.json b/package.json
index d9388928..201541fd 100644
--- a/package.json
+++ b/package.json
@@ -37,6 +37,8 @@
},
"homepage": "http://wiki.openstreetmap.org/wiki/UMap",
"dependencies": {
+ "@dwayneparton/geojson-to-gpx": "^0.2.0",
+ "@placemarkio/tokml": "^0.3.3",
"@tmcw/togeojson": "^5.8.0",
"colorbrewer": "^1.5.6",
"csv2geojson": "5.1.1",
@@ -62,9 +64,7 @@
"leaflet.path.drag": "0.0.6",
"leaflet.photon": "0.9.1",
"osmtogeojson": "^3.0.0-beta.3",
- "simple-statistics": "^7.8.3",
- "togpx": "^0.5.4",
- "tokml": "0.4.0"
+ "simple-statistics": "^7.8.3"
},
"browserslist": [
"> 0.5%, last 2 versions, Firefox ESR, not dead, not op_mini all"
diff --git a/scripts/vendorsjs.sh b/scripts/vendorsjs.sh
index 163f72f7..fbcd906d 100755
--- a/scripts/vendorsjs.sh
+++ b/scripts/vendorsjs.sh
@@ -19,16 +19,16 @@ mkdir -p umap/static/umap/vendors/formbuilder/ && cp -r node_modules/leaflet-for
mkdir -p umap/static/umap/vendors/measurable/ && cp -r node_modules/leaflet-measurable/Leaflet.Measurable.* umap/static/umap/vendors/measurable/
mkdir -p umap/static/umap/vendors/photon/ && cp -r node_modules/leaflet.photon/leaflet.photon.js umap/static/umap/vendors/photon/
mkdir -p umap/static/umap/vendors/csv2geojson/ && cp -r node_modules/csv2geojson/csv2geojson.js umap/static/umap/vendors/csv2geojson/
-mkdir -p umap/static/umap/vendors/togeojson/ && cp -r node_modules/@tmcw/togeojson/dist/togeojson.umd.* umap/static/umap/vendors/togeojson/
+mkdir -p umap/static/umap/vendors/togeojson/ && cp node_modules/@tmcw/togeojson/dist/togeojson.es.mjs umap/static/umap/vendors/togeojson/togeojson.es.js
+mkdir -p umap/static/umap/vendors/tokml/ && cp node_modules/@placemarkio/tokml/dist/tokml.es.mjs umap/static/umap/vendors/tokml/tokml.es.js
mkdir -p umap/static/umap/vendors/osmtogeojson/ && cp -r node_modules/osmtogeojson/osmtogeojson.js umap/static/umap/vendors/osmtogeojson/
mkdir -p umap/static/umap/vendors/georsstogeojson/ && cp -r node_modules/georsstogeojson/GeoRSSToGeoJSON.js umap/static/umap/vendors/georsstogeojson/
-mkdir -p umap/static/umap/vendors/togpx/ && cp -r node_modules/togpx/togpx.js umap/static/umap/vendors/togpx/
-mkdir -p umap/static/umap/vendors/tokml && cp -r node_modules/tokml/tokml.js umap/static/umap/vendors/tokml
mkdir -p umap/static/umap/vendors/locatecontrol/ && cp -r node_modules/leaflet.locatecontrol/dist/L.Control.Locate.min.* umap/static/umap/vendors/locatecontrol/
mkdir -p umap/static/umap/vendors/dompurify/ && cp -r node_modules/dompurify/dist/purify.es.mjs umap/static/umap/vendors/dompurify/purify.es.js
mkdir -p umap/static/umap/vendors/dompurify/ && cp -r node_modules/dompurify/dist/purify.es.mjs.map umap/static/umap/vendors/dompurify/purify.es.mjs.map
mkdir -p umap/static/umap/vendors/colorbrewer/ && cp node_modules/colorbrewer/index.js umap/static/umap/vendors/colorbrewer/colorbrewer.js
mkdir -p umap/static/umap/vendors/simple-statistics/ && cp node_modules/simple-statistics/dist/simple-statistics.min.* umap/static/umap/vendors/simple-statistics/
mkdir -p umap/static/umap/vendors/iconlayers/ && cp node_modules/leaflet-iconlayers/dist/* umap/static/umap/vendors/iconlayers/
+mkdir -p umap/static/umap/vendors/geojson-to-gpx/ && cp node_modules/@dwayneparton/geojson-to-gpx/dist/index.js umap/static/umap/vendors/geojson-to-gpx/
echo 'Done!'
diff --git a/umap/static/umap/js/modules/formatter.js b/umap/static/umap/js/modules/formatter.js
new file mode 100644
index 00000000..ac7ed595
--- /dev/null
+++ b/umap/static/umap/js/modules/formatter.js
@@ -0,0 +1,132 @@
+/* Uses globals for: csv2geojson, osmtogeojson, GeoRSSToGeoJSON (not available as ESM) */
+import { translate } from './i18n.js'
+
+export default class Formatter {
+ async fromGPX(str) {
+ let togeojson
+ await import('../../vendors/togeojson/togeojson.es.js').then((module) => {
+ togeojson = module
+ })
+ return togeojson.gpx(this.toDom(str))
+ }
+
+ async fromKML(str) {
+ console.log(str)
+ let togeojson
+ await import('../../vendors/togeojson/togeojson.es.js').then((module) => {
+ togeojson = module
+ })
+ return togeojson.kml(this.toDom(str), {
+ skipNullGeometry: true,
+ })
+ }
+
+ async fromGeoJSON(str) {
+ try {
+ return JSON.parse(str)
+ } catch (err) {
+ U.Alert.error(`Invalid JSON file: ${err}`)
+ }
+ }
+
+ async fromOSM(str) {
+ let src
+ try {
+ src = JSON.parse(str)
+ } catch (e) {
+ src = this.toDom(str)
+ }
+ return osmtogeojson(src, { flatProperties: true })
+ }
+
+ fromCSV(str, callback) {
+ csv2geojson.csv2geojson(
+ str,
+ {
+ delimiter: 'auto',
+ includeLatLon: false,
+ },
+ (err, result) => {
+ // csv2geojson fallback to null geometries when it cannot determine
+ // lat or lon columns. This is valid geojson, but unwanted from a user
+ // point of view.
+ if (result?.features.length) {
+ if (result.features[0].geometry === null) {
+ err = {
+ type: 'Error',
+ message: translate('Cannot determine latitude and longitude columns.'),
+ }
+ }
+ }
+ if (err) {
+ let message
+ if (err.type === 'Error') {
+ message = err.message
+ } else {
+ message = translate('{count} errors during import: {message}', {
+ count: err.length,
+ message: err[0].message,
+ })
+ }
+ U.Alert.error(message, 10000)
+ console.error(err)
+ }
+ if (result?.features.length) {
+ callback(result)
+ }
+ }
+ )
+ }
+
+ async fromGeoRSS(str) {
+ return GeoRSSToGeoJSON(this.toDom(c))
+ }
+
+ toDom(x) {
+ const doc = new DOMParser().parseFromString(x, 'text/xml')
+ const errorNode = doc.querySelector('parsererror')
+ if (errorNode) {
+ U.Alert.error(translate('Cannot parse data'))
+ }
+ return doc
+ }
+
+ async parse(str, format) {
+ switch (format) {
+ case 'csv':
+ return new Promise((resolve, reject) => {
+ return this.fromCSV(str, (data) => resolve(data))
+ })
+ case 'gpx':
+ return await this.fromGPX(str)
+ case 'kml':
+ return await this.fromKML(str)
+ case 'osm':
+ return await this.fromOSM(str)
+ case 'georss':
+ return await this.fromGeoRSS(str)
+ case 'geojson':
+ return await this.fromGeoJSON(str)
+ }
+ }
+
+ async toGPX(geojson) {
+ let togpx
+ await import('../../vendors/geojson-to-gpx/index.js').then((module) => {
+ togpx = module
+ })
+ for (const feature of geojson.features) {
+ feature.properties.desc = feature.properties.description
+ }
+ const gpx = togpx.default(geojson)
+ return new XMLSerializer().serializeToString(gpx)
+ }
+
+ async toKML(geojson) {
+ let tokml
+ await import('../../vendors/tokml/tokml.es.js').then((module) => {
+ tokml = module
+ })
+ return tokml.toKML(geojson)
+ }
+}
diff --git a/umap/static/umap/js/modules/global.js b/umap/static/umap/js/modules/global.js
index 9e71921c..f49786e3 100644
--- a/umap/static/umap/js/modules/global.js
+++ b/umap/static/umap/js/modules/global.js
@@ -7,6 +7,7 @@ import { AjaxAutocomplete, AjaxAutocompleteMultiple } from './autocomplete.js'
import Browser from './browser.js'
import Caption from './caption.js'
import Facets from './facets.js'
+import Formatter from './formatter.js'
import Help from './help.js'
import Importer from './importer.js'
import Orderable from './orderable.js'
@@ -36,6 +37,7 @@ window.U = {
Dialog,
EditPanel,
Facets,
+ Formatter,
FullPanel,
Help,
HTTPError,
diff --git a/umap/static/umap/js/umap.js b/umap/static/umap/js/umap.js
index d1ef06d2..00e14ded 100644
--- a/umap/static/umap/js/umap.js
+++ b/umap/static/umap/js/umap.js
@@ -116,6 +116,8 @@ U.Map = L.Map.extend({
// Needed for actions labels
this.help = new U.Help(this)
+ this.formatter = new U.Formatter(this)
+
this.initControls()
// Needs locate control and hash to exist
this.initCenter()
diff --git a/umap/static/umap/js/umap.layer.js b/umap/static/umap/js/umap.layer.js
index 857befe0..e321db5d 100644
--- a/umap/static/umap/js/umap.layer.js
+++ b/umap/static/umap/js/umap.layer.js
@@ -792,11 +792,9 @@ U.DataLayer = L.Evented.extend({
const response = await this.map.request.get(url)
if (response?.ok) {
this.clear()
- this.rawToGeoJSON(
- await response.text(),
- this.options.remoteData.format,
- (geojson) => this.fromGeoJSON(geojson)
- )
+ await this.map.formatter
+ .parse(await response.text(), this.options.remoteData.format)
+ .then((geojson) => this.fromGeoJSON(geojson))
}
},
@@ -930,83 +928,6 @@ U.DataLayer = L.Evented.extend({
}
},
- addRawData: function (c, type) {
- this.rawToGeoJSON(c, type, (geojson) => this.addData(geojson))
- },
-
- rawToGeoJSON: (c, type, callback) => {
- const toDom = (x) => {
- const doc = new DOMParser().parseFromString(x, 'text/xml')
- const errorNode = doc.querySelector('parsererror')
- if (errorNode) {
- U.Alert.error(L._('Cannot parse data'))
- }
- return doc
- }
-
- // TODO add a duck typing guessType
- if (type === 'csv') {
- csv2geojson.csv2geojson(
- c,
- {
- delimiter: 'auto',
- includeLatLon: false,
- },
- (err, result) => {
- // csv2geojson fallback to null geometries when it cannot determine
- // lat or lon columns. This is valid geojson, but unwanted from a user
- // point of view.
- if (result?.features.length) {
- if (result.features[0].geometry === null) {
- err = {
- type: 'Error',
- message: L._('Cannot determine latitude and longitude columns.'),
- }
- }
- }
- if (err) {
- let message
- if (err.type === 'Error') {
- message = err.message
- } else {
- message = L._('{count} errors during import: {message}', {
- count: err.length,
- message: err[0].message,
- })
- }
- U.Alert.error(message, 10000)
- console.error(err)
- }
- if (result?.features.length) {
- callback(result)
- }
- }
- )
- } else if (type === 'gpx') {
- callback(toGeoJSON.gpx(toDom(c)))
- } else if (type === 'georss') {
- callback(GeoRSSToGeoJSON(toDom(c)))
- } else if (type === 'kml') {
- callback(toGeoJSON.kml(toDom(c)))
- } else if (type === 'osm') {
- let d
- try {
- d = JSON.parse(c)
- } catch (e) {
- d = toDom(c)
- }
- callback(osmtogeojson(d, { flatProperties: true }))
- } else if (type === 'geojson') {
- try {
- const gj = JSON.parse(c)
- callback(gj)
- } catch (err) {
- U.Alert.error(`Invalid JSON file: ${err}`)
- return
- }
- }
- },
-
// The choice of the name is not ours, because it is required by Leaflet.
// It is misleading, as the returned objects are uMap objects, and not
// GeoJSON features.
@@ -1136,10 +1057,12 @@ U.DataLayer = L.Evented.extend({
return new U.Polygon(this.map, latlngs, { geojson: geojson, datalayer: this }, id)
},
- importRaw: function (raw, type) {
- this.addRawData(raw, type)
+ importRaw: async function (raw, format) {
+ await this.map.formatter
+ .parse(raw, format)
+ .then((geojson) => this.addData(geojson))
+ .then(() => this.zoomTo())
this.isDirty = true
- this.zoomTo()
},
importFromFiles: function (files, type) {
diff --git a/umap/static/umap/js/umap.permissions.js b/umap/static/umap/js/umap.permissions.js
index 42630301..58cdd2e5 100644
--- a/umap/static/umap/js/umap.permissions.js
+++ b/umap/static/umap/js/umap.permissions.js
@@ -116,7 +116,7 @@ U.MapPermissions = L.Class.extend({
L._('Advanced actions')
)
const advancedButtons = L.DomUtil.create('div', 'button-bar', advancedActions)
- const download = L.DomUtil.createButton(
+ L.DomUtil.createButton(
'button',
advancedButtons,
L._('Attach the map to my account'),
diff --git a/umap/static/umap/js/umap.share.js b/umap/static/umap/js/umap.share.js
index a2ab6106..21207255 100644
--- a/umap/static/umap/js/umap.share.js
+++ b/umap/static/umap/js/umap.share.js
@@ -1,22 +1,22 @@
U.Share = L.Class.extend({
EXPORT_TYPES: {
geojson: {
- formatter: (map) => JSON.stringify(map.toGeoJSON(), null, 2),
+ formatter: async (map) => JSON.stringify(map.toGeoJSON(), null, 2),
ext: '.geojson',
filetype: 'application/json',
},
gpx: {
- formatter: (map) => togpx(map.toGeoJSON()),
+ formatter: async (map) => await map.formatter.toGPX(map.toGeoJSON()),
ext: '.gpx',
filetype: 'application/gpx+xml',
},
kml: {
- formatter: (map) => tokml(map.toGeoJSON()),
+ formatter: async (map) => await map.formatter.toKML(map.toGeoJSON()),
ext: '.kml',
filetype: 'application/vnd.google-earth.kml+xml',
},
csv: {
- formatter: (map) => {
+ formatter: async (map) => {
const table = []
map.eachFeature((feature) => {
const row = feature.toGeoJSON().properties
@@ -156,17 +156,17 @@ U.Share = L.Class.extend({
this.map.panel.open({ content: this.container })
},
- format: function (mode) {
+ format: async function (mode) {
const type = this.EXPORT_TYPES[mode]
- const content = type.formatter(this.map)
+ const content = await type.formatter(this.map)
let name = this.map.options.name || 'data'
name = name.replace(/[^a-z0-9]/gi, '_').toLowerCase()
const filename = name + type.ext
return { content, filetype: type.filetype, filename }
},
- download: function (mode) {
- const { content, filetype, filename } = this.format(mode)
+ download: async function (mode) {
+ const { content, filetype, filename } = await this.format(mode)
const blob = new Blob([content], { type: filetype })
window.URL = window.URL || window.webkitURL
const el = document.createElement('a')
diff --git a/umap/templates/umap/js.html b/umap/templates/umap/js.html
index 4bdaeaeb..7e2c0037 100644
--- a/umap/templates/umap/js.html
+++ b/umap/templates/umap/js.html
@@ -19,7 +19,6 @@
-
-
-
diff --git a/umap/tests/integration/test_export_map.py b/umap/tests/integration/test_export_map.py
index 1f65d46f..9f0d0b6a 100644
--- a/umap/tests/integration/test_export_map.py
+++ b/umap/tests/integration/test_export_map.py
@@ -218,8 +218,7 @@ def test_gpx_export(map, live_server, bootstrap, page):
download.save_as(path)
assert (
path.read_text()
- == """testname=test
-description=Some descriptionname polyname=name polytestname=test"""
+ == """testSome descriptiontest"""
)
@@ -235,7 +234,7 @@ def test_kml_export(map, live_server, bootstrap, page):
download.save_as(path)
assert (
path.read_text()
- == """name polyname poly11.25,53.585984 10.151367,52.975108 12.689209,52.167194 14.084473,53.199452 12.634277,53.618579 11.25,53.585984 11.25,53.585984testSome description[object Object]testSome description-0.274658,52.57635test[object Object]test-0.571289,54.476422 0.439453,54.610255 1.724854,53.448807 4.163818,53.988395 5.306396,53.533778 6.591797,53.709714 7.042236,53.350551"""
+ == """\n\nname poly\n \n\n 11.25,53.585984\n10.151367,52.975108\n12.689209,52.167194\n14.084473,53.199452\n12.634277,53.618579\n11.25,53.585984\n11.25,53.585984\n\ntestSome description\n {"color":"OliveDrab"}\n -0.274658,52.57635\n\ntest\n {"fill":false,"opacity":0.6}\n -0.571289,54.476422\n0.439453,54.610255\n1.724854,53.448807\n4.163818,53.988395\n5.306396,53.533778\n6.591797,53.709714\n7.042236,53.350551"""
)