Add 'export as GPX'-feature, fix #276

This commit is contained in:
pietervdvn 2021-11-08 20:49:51 +01:00
parent 91fe29cfdd
commit e8ce53d5eb
9 changed files with 308 additions and 79 deletions

View file

@ -1,5 +1,9 @@
import * as turf from '@turf/turf'
import {BBox} from "./BBox";
import togpx from "togpx"
import Constants from "../Models/Constants";
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
import {meta} from "@turf/turf";
export class GeoOperations {
@ -436,6 +440,31 @@ export class GeoOperations {
return undefined;
}
public static AsGpx(feature, generatedWithLayer?: LayerConfig){
const metadata = {}
const tags = feature.properties
if(generatedWithLayer !== undefined){
metadata["name"] = generatedWithLayer.title?.GetRenderValue(tags)?.Subs(tags)?.txt
metadata["desc"] = "Generated with MapComplete layer "+generatedWithLayer.id
if(tags._backend?.contains("openstreetmap")){
metadata["copyright"]= "Data copyrighted by OpenStreetMap-contributors, freely available under ODbL. See https://www.openstreetmap.org/copyright"
metadata["author"] = tags["_last_edit:contributor"]
metadata["link"]= "https://www.openstreetmap.org/"+tags.id
metadata["time"] = tags["_last_edit:timestamp"]
}else{
metadata["time"] = new Date().toISOString()
}
}
return togpx(feature, {
creator: "MapComplete "+Constants.vNumber,
metadata
})
}
}

View file

@ -300,5 +300,17 @@ export default class LayoutConfig {
public isLeftRightSensitive() {
return this.layers.some(l => l.isLeftRightSensitive())
}
public getMatchingLayer(tags: any) : LayerConfig | undefined{
if(tags === undefined){
return undefined
}
for (const layer of this.layers) {
if (layer.source.osmTags.matchesProperties(tags)) {
return layer
}
}
return undefined
}
}

View file

@ -38,10 +38,11 @@ import ChangeTagAction from "../Logic/Osm/Actions/ChangeTagAction";
import {And} from "../Logic/Tags/And";
import Toggle from "./Input/Toggle";
import {DefaultGuiState} from "./DefaultGuiState";
import {GeoOperations} from "../Logic/GeoOperations";
export interface SpecialVisualization {
funcName: string,
constr: ((state: State, tagSource: UIEventSource<any>, argument: string[], guistate: DefaultGuiState) => BaseUIElement),
constr: ((state: State, tagSource: UIEventSource<any>, argument: string[], guistate: DefaultGuiState, ) => BaseUIElement),
docs: string,
example?: string,
args: { name: string, defaultValue?: string, doc: string }[]
@ -172,7 +173,7 @@ export default class SpecialVisualizations {
// This is a list of values
idList = JSON.parse(value)
}
for (const id of idList) {
features.push({
@ -425,12 +426,7 @@ export default class SpecialVisualizations {
const title = state?.layoutToUse?.title?.txt ?? "MapComplete";
let matchingLayer: LayerConfig = undefined;
for (const layer of (state?.layoutToUse?.layers ?? [])) {
if (layer.source.osmTags.matchesProperties(tagSource?.data)) {
matchingLayer = layer
}
}
let matchingLayer: LayerConfig = state?.layoutToUse?.getMatchingLayer(tagSource?.data);
let name = matchingLayer?.title?.GetRenderValue(tagSource.data)?.txt ?? tagSource.data?.name ?? "POI";
if (name) {
name = `${name} (${title})`
@ -603,6 +599,31 @@ export default class SpecialVisualizations {
)
, undefined, state.osmConnection.isLoggedIn)
}
},
{
funcName: "export_as_gpx",
docs: "Exports the selected feature as GPX-file",
args: [],
constr: (state, tagSource, args) => {
const t = Translations.t.general.download;
return new SubtleButton(Svg.download_ui(),
new Combine([t.downloadGpx.SetClass("font-bold text-lg"),
t.downloadGpxHelper.SetClass("subtle")]).SetClass("flex flex-col")
).onClick(() => {
console.log("Exporting as GPX!")
const tags = tagSource.data
const feature = state.allElements.ContainingFeatures.get(tags.id)
const matchingLayer = state?.layoutToUse?.getMatchingLayer(tags)
const gpx = GeoOperations.AsGpx(feature, matchingLayer)
const title = matchingLayer.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "gpx_track"
Utils.offerContentsAsDownloadableFile(gpx, title+"_mapcomplete_export.gpx", {
mimetype: "{gpx=application/gpx+xml}"
})
})
}
}
]

View file

@ -6,6 +6,18 @@
"osmTags": "user:location=yes",
"maxCacheAge": 0
},
"title": {
"render": "Your travelled path"
},
"tagRenderings": [
{
"id": "Privacy notice",
"render": {
"en": "This is the path you've travelled since this website is opened. Don't worry - this is only visible to you and no one else. Your location data is never sent off-device."
}
},
"export_as_gpx"
],
"name": "Your track",
"mapRendering": [
{

View file

@ -2,6 +2,9 @@
"images": {
"render": "{image_carousel()}{image_upload()}"
},
"export_as_gpx":{
"render": "{export_as_gpx()}"
},
"wikipedia": {
"render": "{wikipedia():max-height:25rem}",
"question": {

View file

@ -64,9 +64,13 @@
},
"minzoom": 13,
"minzoomVisible": 0,
"icon": {
"render": "circle:#FE6F32;./assets/themes/natuurpunt/nature_reserve.svg"
}
"mapRendering": [
{
"icon": {
"render": "circle:#FE6F32;./assets/themes/natuurpunt/nature_reserve.svg"
}
}
]
}
},
{
@ -84,10 +88,14 @@
"isOsmCache": "duplicate"
},
"minzoom": 1,
"icon": {
"render": "circle:#FE6F32;./assets/themes/natuurpunt/nature_reserve.svg"
},
"presets": []
"mapRendering": [
{
"icon": {
"render": "circle:#FE6F32;./assets/themes/natuurpunt/nature_reserve.svg"
},
"presets": []
}
]
}
},
{
@ -103,9 +111,13 @@
"isOsmCache": true
},
"minzoom": 1,
"icon": {
"render": "circle:#FE6F32;./assets/themes/natuurpunt/information.svg"
}
"mapRendering": [
{
"icon": {
"render": "circle:#FE6F32;./assets/themes/natuurpunt/information.svg"
}
}
]
}
},
{
@ -122,19 +134,23 @@
"isOsmCache": true
},
"minzoom": 10,
"icon": {
"render": "circle:#FE6F32;./assets/themes/natuurpunt/trail.svg",
"mappings": [
{
"if": "wheelchair=yes",
"then": "circle:#FE6F32;./assets/themes/natuurpunt/walk_wheelchair.svg"
},
{
"if": "pushchair=yes",
"then": "circle:#FE6F32;./assets/themes/natuurpunt/pushchair.svg"
"mapRendering": [
{
"icon": {
"render": "circle:#FE6F32;./assets/themes/natuurpunt/trail.svg",
"mappings": [
{
"if": "wheelchair=yes",
"then": "circle:#FE6F32;./assets/themes/natuurpunt/walk_wheelchair.svg"
},
{
"if": "pushchair=yes",
"then": "circle:#FE6F32;./assets/themes/natuurpunt/pushchair.svg"
}
]
}
]
}
}
]
}
},
{
@ -146,19 +162,23 @@
"geoJsonZoomLevel": 12,
"isOsmCache": true
},
"icon": {
"render": "circle:#FE6F32;./assets/themes/natuurpunt/toilets.svg",
"mappings": [
{
"if": "wheelchair=yes",
"then": "circle:#FE6F32;./assets/themes/natuurpunt/wheelchair.svg"
},
{
"if": "toilets:position=urinals",
"then": "circle:#FE6F32;./assets/themes/natuurpunt/urinal.svg"
"mapRendering": [
{
"icon": {
"render": "circle:#FE6F32;./assets/themes/natuurpunt/toilets.svg",
"mappings": [
{
"if": "wheelchair=yes",
"then": "circle:#FE6F32;./assets/themes/natuurpunt/wheelchair.svg"
},
{
"if": "toilets:position=urinals",
"then": "circle:#FE6F32;./assets/themes/natuurpunt/urinal.svg"
}
]
}
]
}
}
]
}
},
{
@ -170,10 +190,14 @@
"geoJsonZoomLevel": 12,
"isOsmCache": true
},
"icon": {
"render": "circle:#FE6F32;./assets/themes/natuurpunt/birdhide.svg",
"mappings": null
}
"mapRendering": [
{
"icon": {
"render": "circle:#FE6F32;./assets/themes/natuurpunt/birdhide.svg",
"mappings": null
}
}
]
}
},
{
@ -185,9 +209,13 @@
"geoJsonZoomLevel": 12,
"isOsmCache": true
},
"icon": {
"render": "circle:#FE6F32;./assets/themes/natuurpunt/picnic_table.svg"
}
"mapRendering": [
{
"icon": {
"render": "circle:#FE6F32;./assets/themes/natuurpunt/picnic_table.svg"
}
}
]
}
},
{
@ -199,34 +227,37 @@
"geoJsonZoomLevel": 12,
"isOsmCache": true
},
"icon": {
"render": "circle:#FE6F32;./assets/themes/natuurpunt/drips.svg"
}
"mapRendering": [
{
"icon": {
"render": "circle:#FE6F32;./assets/themes/natuurpunt/drips.svg"
}
}
]
}
},
{
"builtin": "parking",
"override": {
"minzoom": "16",
"icon": {
"render": "circle:#FE6F32;./assets/themes/natuurpunt/parking.svg",
"mappings": [
{
"if": "amenity=bicycle_parking",
"then": "circle:#FE6F32;./assets/themes/natuurpunt/parkingbike.svg"
}
]
},
"iconOverlays": [
"mapRendering": [
{
"if": "amenity=motorcycle_parking",
"then": "circle:#335D9F;./assets/themes/natuurpunt/parkingmotor.svg",
"badge": true
},
{
"if": "capacity:disabled=yes",
"then": "circle:#335D9F;./assets/themes/natuurpunt/parkingwheels.svg",
"badge": true
"icon": {
"render": "circle:#FE6F32;./assets/themes/natuurpunt/parking.svg",
"mappings": [
{
"if": "amenity=bicycle_parking",
"then": "circle:#FE6F32;./assets/themes/natuurpunt/parkingbike.svg"
}
]
},
"iconOverlays": [
{
"if": "capacity:disabled=yes",
"then": "circle:#335D9F;./assets/themes/natuurpunt/parkingwheels.svg",
"badge": true
}
]
}
]
}
@ -240,9 +271,13 @@
"geoJsonZoomLevel": 12,
"isOsmCache": true
},
"icon": {
"render": "circle:#FE6F32;./assets/themes/natuurpunt/information_board.svg"
}
"mapRendering": [
{
"icon": {
"render": "circle:#FE6F32;./assets/themes/natuurpunt/information_board.svg"
}
}
]
}
},
{
@ -254,9 +289,13 @@
"geoJsonZoomLevel": 12,
"isOsmCache": true
},
"icon": {
"render": "circle:#FE6F32;./assets/themes/natuurpunt/bench.svg"
}
"mapRendering": [
{
"icon": {
"render": "circle:#FE6F32;./assets/themes/natuurpunt/bench.svg"
}
}
]
}
},
{
@ -268,9 +307,13 @@
"geoJsonZoomLevel": 12,
"isOsmCache": true
},
"icon": {
"render": "circle:#FE6F32;./assets/themes/natuurpunt/watermill.svg"
}
"mapRendering": [
{
"icon": {
"render": "circle:#FE6F32;./assets/themes/natuurpunt/watermill.svg"
}
}
]
}
}
],

View file

@ -190,6 +190,8 @@
"downloadAsPdf": "Download a PDF of the current map",
"downloadAsPdfHelper": "Ideal to print the current map",
"downloadGeojson": "Download visible data as GeoJSON",
"downloadGpx":"Download as GPX-file",
"downloadGpxHelper":"A GPX-file can be used with most navigation devices and applications",
"exporting": "Exporting…",
"downloadGeoJsonHelper": "Compatible with QGIS, ArcGIS, ESRI, …",
"downloadCSV": "Download visible data as CSV",

106
package-lock.json generated
View file

@ -46,6 +46,7 @@
"parcel": "^1.2.4",
"prompt-sync": "^4.2.0",
"tailwindcss": "^2.2.15",
"togpx": "^0.5.4",
"tslint": "^6.1.3",
"wikibase-sdk": "^7.14.0",
"wikidata-sdk": "^7.14.0"
@ -3756,6 +3757,23 @@
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
},
"node_modules/bops": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/bops/-/bops-0.0.6.tgz",
"integrity": "sha1-CC0dVfoB5g29wuvC26N/ZZVUzzo=",
"dependencies": {
"base64-js": "0.0.2",
"to-utf8": "0.0.1"
}
},
"node_modules/bops/node_modules/base64-js": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.2.tgz",
"integrity": "sha1-Ak8Pcq+iW3X5wO5zzU9V7Bvtl4Q=",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -8619,6 +8637,14 @@
"safe-buffer": "^5.0.1"
}
},
"node_modules/jxon": {
"version": "2.0.0-beta.5",
"resolved": "https://registry.npmjs.org/jxon/-/jxon-2.0.0-beta.5.tgz",
"integrity": "sha1-O2qUEE+YAe5oL9BWZF/1Rz2bND4=",
"dependencies": {
"xmldom": "^0.1.21"
}
},
"node_modules/kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@ -15930,6 +15956,36 @@
"node": ">=8.0"
}
},
"node_modules/to-utf8": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/to-utf8/-/to-utf8-0.0.1.tgz",
"integrity": "sha1-0Xrqcv8vujm55DYBvns/9y4ImFI="
},
"node_modules/togpx": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/togpx/-/togpx-0.5.4.tgz",
"integrity": "sha1-sz27BUHfBL1rpPULhtqVNCS7d3M=",
"dependencies": {
"concat-stream": "~1.0.1",
"jxon": "~2.0.0-beta.5",
"optimist": "~0.3.5",
"xmldom": "~0.1.17"
},
"bin": {
"togpx": "togpx"
}
},
"node_modules/togpx/node_modules/concat-stream": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.0.1.tgz",
"integrity": "sha1-AYsYvBx9BzotyCqkhEI0GixN158=",
"engines": [
"node >= 0.8.0"
],
"dependencies": {
"bops": "0.0.6"
}
},
"node_modules/toidentifier": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
@ -21029,6 +21085,22 @@
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
},
"bops": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/bops/-/bops-0.0.6.tgz",
"integrity": "sha1-CC0dVfoB5g29wuvC26N/ZZVUzzo=",
"requires": {
"base64-js": "0.0.2",
"to-utf8": "0.0.1"
},
"dependencies": {
"base64-js": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.2.tgz",
"integrity": "sha1-Ak8Pcq+iW3X5wO5zzU9V7Bvtl4Q="
}
}
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -24823,6 +24895,14 @@
"safe-buffer": "^5.0.1"
}
},
"jxon": {
"version": "2.0.0-beta.5",
"resolved": "https://registry.npmjs.org/jxon/-/jxon-2.0.0-beta.5.tgz",
"integrity": "sha1-O2qUEE+YAe5oL9BWZF/1Rz2bND4=",
"requires": {
"xmldom": "^0.1.21"
}
},
"kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@ -30870,6 +30950,32 @@
"is-number": "^7.0.0"
}
},
"to-utf8": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/to-utf8/-/to-utf8-0.0.1.tgz",
"integrity": "sha1-0Xrqcv8vujm55DYBvns/9y4ImFI="
},
"togpx": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/togpx/-/togpx-0.5.4.tgz",
"integrity": "sha1-sz27BUHfBL1rpPULhtqVNCS7d3M=",
"requires": {
"concat-stream": "~1.0.1",
"jxon": "~2.0.0-beta.5",
"optimist": "~0.3.5",
"xmldom": "~0.1.17"
},
"dependencies": {
"concat-stream": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.0.1.tgz",
"integrity": "sha1-AYsYvBx9BzotyCqkhEI0GixN158=",
"requires": {
"bops": "0.0.6"
}
}
}
},
"toidentifier": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",

View file

@ -94,6 +94,7 @@
"parcel": "^1.2.4",
"prompt-sync": "^4.2.0",
"tailwindcss": "^2.2.15",
"togpx": "^0.5.4",
"tslint": "^6.1.3",
"wikibase-sdk": "^7.14.0",
"wikidata-sdk": "^7.14.0"