From 9cd83af941f6ba3bddffdf6646a07e678d55d31e Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Wed, 16 Feb 2022 00:56:48 +0100 Subject: [PATCH 1/6] Add 'upload GPX-trace to OSM' code, small improvements to gps_track-layer --- Logic/GeoOperations.ts | 2 +- Logic/Osm/OsmConnection.ts | 81 ++++++++++++++++++- UI/Popup/LoginButton.ts | 1 - .../gps_location_history.json | 15 +++- assets/tagRenderings/questions.json | 5 +- 5 files changed, 98 insertions(+), 6 deletions(-) diff --git a/Logic/GeoOperations.ts b/Logic/GeoOperations.ts index 9c0bad52c..c05ca387c 100644 --- a/Logic/GeoOperations.ts +++ b/Logic/GeoOperations.ts @@ -340,7 +340,7 @@ export class GeoOperations { return turf.lineIntersect(feature, otherFeature).features.map(p => <[number, number]>p.geometry.coordinates) } - public static AsGpx(feature, generatedWithLayer?: LayerConfig) { + public static AsGpx(feature, generatedWithLayer?: LayerConfig) : string{ const metadata = {} const tags = feature.properties diff --git a/Logic/Osm/OsmConnection.ts b/Logic/Osm/OsmConnection.ts index 2ff689f40..b7e296d42 100644 --- a/Logic/Osm/OsmConnection.ts +++ b/Logic/Osm/OsmConnection.ts @@ -9,6 +9,7 @@ import Img from "../../UI/Base/Img"; import {Utils} from "../../Utils"; import {OsmObject} from "./OsmObject"; import {Changes} from "./Changes"; +import {GeoOperations} from "../GeoOperations"; export default class UserDetails { @@ -30,10 +31,13 @@ export default class UserDetails { export class OsmConnection { public static readonly oauth_configs = { + "osm": { oauth_consumer_key: 'hivV7ec2o49Two8g9h8Is1VIiVOgxQ1iYexCbvem', oauth_secret: 'wDBRTCem0vxD7txrg1y6p5r8nvmz8tAhET7zDASI', url: "https://www.openstreetmap.org" + // OAUTH 1.0 application + // https://www.openstreetmap.org/user/Pieter%20Vander%20Vennet/oauth_clients/7404 }, "osm-test": { oauth_consumer_key: 'Zgr7EoKb93uwPv2EOFkIlf3n9NLwj5wbyfjZMhz2', @@ -312,7 +316,82 @@ export class OsmConnection { }) } - + + public async uploadGpxTrack(geojson: any, options: { + description: string, + visibility: "private" | "public" | "trackable" | "identifiable", + filename?: string + /** + * Some words to give some properties; + * + * Note: these are called 'tags' on the wiki, but I opted to name them 'labels' instead as they aren't "key=value" tags, but just words. + */ + labels: string[] + }): Promise<{ id: number }> { + const gpx = GeoOperations.AsGpx(geojson) + if (this._dryRun.data) { + console.warn("Dryrun enabled - not actually uploading GPX ", gpx) + return new Promise<{ id: number }>((ok, error) => { + window.setTimeout(() => ok({id: Math.floor(Math.random() * 1000)}), Math.random() * 5000) + }); + } + + const contents = { + "file": gpx, + "description": options.description, + "tags": options.labels.join(","), + "visibility": options.visibility + } + + const extras = { + "file": "; filename=\""+options.filename+"\"\r\nContent-Type: application/gpx+xml" + } + + const auth = this.auth; + const boundary ="987654" + + var body = "" + for (var key in contents) { + body += "--" + boundary + "\r\n" + body += "Content-Disposition: form-data; name=\"" + key + "\"" + if(extras[key] !== undefined){ + body += extras[key] + } + body += "\r\n\r\n" + body += contents[key] + "\r\n" + } + body += "--" + boundary + "--\r\n" + + + return new Promise((ok, error) => { + auth.xhr({ + method: 'POST', + path: `/api/0.6/gpx/create`, + options: { + header: + { + "Content-Type": "multipart/form-data; boundary=" + boundary, + "Content-Length": body.length + } + }, + content: body + + }, function ( + err, + response: string) { + console.log("RESPONSE IS", response) + if (err !== null) { + error(err) + } else { + const parsed = JSON.parse(response) + console.log("Uploaded GPX track", parsed) + ok({id: parsed}) + } + }) + }) + + } + public addCommentToNode(id: number | string, text: string): Promise { if (this._dryRun.data) { console.warn("Dryrun enabled - not actually adding comment ", text, "to note ", id) diff --git a/UI/Popup/LoginButton.ts b/UI/Popup/LoginButton.ts index dd16d6411..ecf859daf 100644 --- a/UI/Popup/LoginButton.ts +++ b/UI/Popup/LoginButton.ts @@ -28,7 +28,6 @@ export class LoginToggle extends VariableUiElement { const login = new LoginButton(text, state) super( state.osmConnection.loadingStatus.map(osmConnectionState => { - console.trace("Current osm state is ", osmConnectionState) if(osmConnectionState === "loading"){ return loading } diff --git a/assets/layers/gps_location_history/gps_location_history.json b/assets/layers/gps_location_history/gps_location_history.json index 056ce5980..42c531546 100644 --- a/assets/layers/gps_location_history/gps_location_history.json +++ b/assets/layers/gps_location_history/gps_location_history.json @@ -1,11 +1,22 @@ { "id": "gps_location_history", "description": "Meta layer which contains the previous locations of the user as single points. This is mainly for technical reasons, e.g. to keep match the distance to the modified object", - "minzoom": 0, + "minzoom": 1, + "name": { + "en": "Location history as points", + "nl": "Locatiegeschiedenis als punten" + }, "source": { "osmTags": "user:location=yes", "#": "Cache is disabled here as these points are kept seperately", "maxCacheAge": 0 }, - "mapRendering": null + "shownByDefault": false, + "mapRendering":[ + { + "location": ["point","centroid"], + "icon": "square:red", + "iconSize": "5,5,center" + } + ] } \ No newline at end of file diff --git a/assets/tagRenderings/questions.json b/assets/tagRenderings/questions.json index 18eff119c..dca93cc94 100644 --- a/assets/tagRenderings/questions.json +++ b/assets/tagRenderings/questions.json @@ -9,6 +9,9 @@ "export_as_gpx": { "render": "{export_as_gpx()}" }, + "export_as_geojson": { + "render": "{export_as_geojson()}" + }, "wikipedia": { "render": "{wikipedia():max-height:25rem}", "question": { @@ -828,4 +831,4 @@ } ] } -} \ No newline at end of file +} From 312db3ad50b6df56f956954440e5ae49f2c40943 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Fri, 5 Aug 2022 12:39:02 +0200 Subject: [PATCH 2/6] Add possibility to upload your travelled track to OSM --- Logic/GeoOperations.ts | 15 ++- Logic/Osm/OsmConnection.ts | 8 +- Logic/State/MapState.ts | 7 +- UI/Base/SubtleButton.ts | 1 - UI/BigComponents/UploadTraceToOsmUI.ts | 96 +++++++++++++++++++ UI/Input/RadioButton.ts | 5 +- UI/SpecialVisualizations.ts | 35 ++++++- .../gps_location_history.json | 7 +- assets/layers/gps_track/gps_track.json | 3 +- css/index-tailwind-output.css | 52 +++++----- langs/en.json | 23 +++++ 11 files changed, 208 insertions(+), 44 deletions(-) create mode 100644 UI/BigComponents/UploadTraceToOsmUI.ts diff --git a/Logic/GeoOperations.ts b/Logic/GeoOperations.ts index 5da1245f3..c7a03ed34 100644 --- a/Logic/GeoOperations.ts +++ b/Logic/GeoOperations.ts @@ -1,9 +1,9 @@ import * as turf from '@turf/turf' +import {AllGeoJSON, booleanWithin, Coord, Feature, Geometry, MultiPolygon, Polygon} from '@turf/turf' import {BBox} from "./BBox"; import togpx from "togpx" import Constants from "../Models/Constants"; import LayerConfig from "../Models/ThemeConfig/LayerConfig"; -import {AllGeoJSON, booleanWithin, Coord, Feature, Geometry, MultiPolygon, Polygon, Properties} from "@turf/turf"; export class GeoOperations { @@ -383,22 +383,21 @@ export class GeoOperations { return turf.lineIntersect(feature, otherFeature).features.map(p => <[number, number]>p.geometry.coordinates) } - public static AsGpx(feature, generatedWithLayer?: LayerConfig) : string{ + public static AsGpx(feature: Feature, options?: {layer?: LayerConfig, gpxMetadata?: any }) : string{ - const metadata = {} + const metadata = options?.gpxMetadata ?? {} + metadata["time"] = metadata["time"] ?? new Date().toISOString() const tags = feature.properties - if (generatedWithLayer !== undefined) { + if (options?.layer !== undefined) { - metadata["name"] = generatedWithLayer.title?.GetRenderValue(tags)?.Subs(tags)?.txt - metadata["desc"] = "Generated with MapComplete layer " + generatedWithLayer.id + metadata["name"] = options?.layer.title?.GetRenderValue(tags)?.Subs(tags)?.txt + metadata["desc"] = "Generated with MapComplete layer " + options?.layer.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() } } diff --git a/Logic/Osm/OsmConnection.ts b/Logic/Osm/OsmConnection.ts index 0399e45a4..07839e4a4 100644 --- a/Logic/Osm/OsmConnection.ts +++ b/Logic/Osm/OsmConnection.ts @@ -9,6 +9,7 @@ import {Utils} from "../../Utils"; import {OsmObject} from "./OsmObject"; import {Changes} from "./Changes"; import {GeoOperations} from "../GeoOperations"; +import { Feature } from "@turf/turf"; export default class UserDetails { @@ -322,7 +323,7 @@ export class OsmConnection { } - public async uploadGpxTrack(geojson: any, options: { + public async uploadGpxTrack(gpx: string, options: { description: string, visibility: "private" | "public" | "trackable" | "identifiable", filename?: string @@ -333,7 +334,6 @@ export class OsmConnection { */ labels: string[] }): Promise<{ id: number }> { - const gpx = GeoOperations.AsGpx(geojson) if (this._dryRun.data) { console.warn("Dryrun enabled - not actually uploading GPX ", gpx) return new Promise<{ id: number }>((ok, error) => { @@ -355,8 +355,8 @@ export class OsmConnection { const auth = this.auth; const boundary ="987654" - var body = "" - for (var key in contents) { + let body = "" + for (const key in contents) { body += "--" + boundary + "\r\n" body += "Content-Disposition: form-data; name=\"" + key + "\"" if(extras[key] !== undefined){ diff --git a/Logic/State/MapState.ts b/Logic/State/MapState.ts index 9af4edf6b..29de7573f 100644 --- a/Logic/State/MapState.ts +++ b/Logic/State/MapState.ts @@ -69,7 +69,7 @@ export default class MapState extends UserRelatedState { public currentUserLocation: SimpleFeatureSource; /** - * All previously visited points + * All previously visited points, with their metadata */ public historicalUserLocations: SimpleFeatureSource; /** @@ -77,6 +77,11 @@ export default class MapState extends UserRelatedState { * Time in seconds */ public gpsLocationHistoryRetentionTime = new UIEventSource(7 * 24 * 60 * 60, "gps_location_retention") + /** + * A featureSource containing a single linestring which has the GPS-history of the user. + * However, metadata (such as when every single point was visited) is lost here (but is kept in `historicalUserLocations`. + * Note that this featureSource is _derived_ from 'historicalUserLocations' + */ public historicalUserLocationsTrack: FeatureSourceForLayer & Tiled; /** diff --git a/UI/Base/SubtleButton.ts b/UI/Base/SubtleButton.ts index 32931845f..ccc09b156 100644 --- a/UI/Base/SubtleButton.ts +++ b/UI/Base/SubtleButton.ts @@ -78,7 +78,6 @@ export class SubtleButton extends UIElement { }) const loading = new Lazy(() => new Loading(loadingText) ) return new VariableUiElement(state.map(st => { - console.log("State is: ", st) if(st === "idle"){ return button } diff --git a/UI/BigComponents/UploadTraceToOsmUI.ts b/UI/BigComponents/UploadTraceToOsmUI.ts new file mode 100644 index 000000000..b73a44b77 --- /dev/null +++ b/UI/BigComponents/UploadTraceToOsmUI.ts @@ -0,0 +1,96 @@ +import Toggle from "../Input/Toggle"; +import {RadioButton} from "../Input/RadioButton"; +import {FixedInputElement} from "../Input/FixedInputElement"; +import Combine from "../Base/Combine"; +import Translations from "../i18n/Translations"; +import {TextField} from "../Input/TextField"; +import {UIEventSource} from "../../Logic/UIEventSource"; +import Title from "../Base/Title"; +import {SubtleButton} from "../Base/SubtleButton"; +import Svg from "../../Svg"; +import {OsmConnection} from "../../Logic/Osm/OsmConnection"; +import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; +import {Translation} from "../i18n/Translation"; + + +export default class UploadTraceToOsmUI extends Toggle { + + + constructor( + trace: () => string, + state: { + layoutToUse: LayoutConfig; + osmConnection: OsmConnection + }, options?: { + whenUploaded?: () => void | Promise + }) { + const t = Translations.t.general.uploadGpx + + const traceVisibilities: { + key: "private" | "public", + name: Translation, + docs: Translation + }[] = [ + { + key: "private", + ...Translations.t.general.uploadGpx.modes.private + }, + { + key: "public", + ...Translations.t.general.uploadGpx.modes.public + } + ] + + const dropdown = new RadioButton<"private" | "public">( + traceVisibilities.map(tv => new FixedInputElement<"private" | "public">( + new Combine([Translations.W( + tv.name + ).SetClass("font-bold"), tv.docs]).SetClass("flex flex-col") + , tv.key)), + { + value: state?.osmConnection?.GetPreference("gps.trace.visibility") + } + ) + const description = new TextField({ + placeholder: t.placeHolder + }) + const clicked = new UIEventSource(false) + + const confirmPanel = new Combine([ + new Title(t.title), + t.intro0, + t.intro1, + + t.choosePermission, + dropdown, + new Title(t.description.title, 4), + t.description.intro, + description, + new Combine([ + new SubtleButton(Svg.close_svg(), Translations.t.general.cancel).onClick(() => { + clicked.setData(false) + }).SetClass(""), + new SubtleButton(Svg.upload_svg(), t.confirm).OnClickWithLoading(t.uploading, async () => { + await state?.osmConnection?.uploadGpxTrack(trace(), { + visibility: dropdown.GetValue().data, + description: description.GetValue().data, + labels: ["MapComplete", state?.layoutToUse?.id] + }) + + if (options?.whenUploaded !== undefined) { + await options.whenUploaded() + } + + }).SetClass("") + ]).SetClass("flex flex-wrap flex-wrap-reverse justify-between items-stretch") + ]).SetClass("flex flex-col p-4 rounded border-2 m-2 border-subtle") + + + super( + confirmPanel, + new SubtleButton(Svg.upload_svg(), t.title) + .onClick(() => clicked.setData(true)), + clicked + ) + } +} \ No newline at end of file diff --git a/UI/Input/RadioButton.ts b/UI/Input/RadioButton.ts index 9c2145101..a52b5a41f 100644 --- a/UI/Input/RadioButton.ts +++ b/UI/Input/RadioButton.ts @@ -14,14 +14,15 @@ export class RadioButton extends InputElement { elements: InputElement[], options?: { selectFirstAsDefault?: true | boolean, - dontStyle?: boolean + dontStyle?: boolean, + value?: UIEventSource } ) { super(); options = options ?? {} this._selectFirstAsDefault = options.selectFirstAsDefault ?? true; this._elements = Utils.NoNull(elements); - this.value = new UIEventSource(undefined); + this.value = options?.value ?? new UIEventSource(undefined); this._dontStyle = options.dontStyle ?? false } diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 062563118..3f99c175c 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -61,6 +61,10 @@ import StatisticsPanel from "./BigComponents/StatisticsPanel"; import {OsmFeature} from "../Models/OsmFeature"; import EditableTagRendering from "./Popup/EditableTagRendering"; import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"; +import UploadTraceToOsmUI from "./BigComponents/UploadTraceToOsmUI"; +import {Feature} from "geojson"; +import {GeoLocationPointProperties} from "../Logic/Actors/GeoLocationHandler"; +import {Point} from "@turf/turf"; export interface SpecialVisualization { funcName: string, @@ -864,7 +868,7 @@ export default class SpecialVisualizations { 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 gpx = GeoOperations.AsGpx(feature, {layer: matchingLayer}) const title = matchingLayer.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "gpx_track" Utils.offerContentsAsDownloadableFile(gpx, title + "_mapcomplete_export.gpx", { mimetype: "{gpx=application/gpx+xml}" @@ -874,6 +878,35 @@ export default class SpecialVisualizations { }) } }, + { + funcName: "upload_to_osm", + docs: "Uploads the GPS-history as GPX to OpenStreetMap.org; clears the history afterwards. The actual feature is ignored.", + args:[], + constr(state, featureTags, args) { + + function getTrace() { + const userLocations : Feature[] = state.historicalUserLocations.features.data.map(f => f.feature) + const trackPoints: string[] = [] + for (const l of userLocations) { + let trkpt = ` ` + trkpt += ` ` + if(l.properties.altitude !== null && l.properties.altitude !== undefined ){ + trkpt += ` ${l.properties.altitude}` + } + trkpt += " " + trackPoints.push(trkpt) + } + const header = '' + return header+"\n\n"+trackPoints.join("\n")+"\n" + } + + return new UploadTraceToOsmUI(getTrace, state,{ + whenUploaded: async () => { + state.historicalUserLocations.features.setData([]) + } + }) + } + }, { funcName: "export_as_geojson", docs: "Exports the selected feature as GeoJson-file", diff --git a/assets/layers/gps_location_history/gps_location_history.json b/assets/layers/gps_location_history/gps_location_history.json index 42c531546..036f407e7 100644 --- a/assets/layers/gps_location_history/gps_location_history.json +++ b/assets/layers/gps_location_history/gps_location_history.json @@ -12,9 +12,12 @@ "maxCacheAge": 0 }, "shownByDefault": false, - "mapRendering":[ + "mapRendering": [ { - "location": ["point","centroid"], + "location": [ + "point", + "centroid" + ], "icon": "square:red", "iconSize": "5,5,center" } diff --git a/assets/layers/gps_track/gps_track.json b/assets/layers/gps_track/gps_track.json index 4c159671f..f0a8f4436 100644 --- a/assets/layers/gps_track/gps_track.json +++ b/assets/layers/gps_track/gps_track.json @@ -1,6 +1,6 @@ { "id": "gps_track", - "description": "Meta layer showing the previous locations of the user as single line. Add this to your theme and override the icon to change the appearance of the current location.", + "description": "Meta layer showing the previous locations of the user as single line with controls, e.g. to erase, upload or download this track. Add this to your theme and override the maprendering to change the appearance of the travelled track.", "minzoom": 0, "source": { "osmTags": "id=location_track", @@ -22,6 +22,7 @@ }, "export_as_gpx", "export_as_geojson", + "{upload_to_osm()}", "minimap", { "id": "delete", diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css index 5b3eb4abf..6eed12268 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -811,6 +811,10 @@ video { margin: 0.25rem; } +.m-2 { + margin: 0.5rem; +} + .m-4 { margin: 1rem; } @@ -819,10 +823,6 @@ video { margin: 1.25rem; } -.m-2 { - margin: 0.5rem; -} - .m-0\.5 { margin: 0.125rem; } @@ -866,14 +866,6 @@ video { margin-bottom: 1rem; } -.mt-8 { - margin-top: 2rem; -} - -.mt-4 { - margin-top: 1rem; -} - .mt-2 { margin-top: 0.5rem; } @@ -886,6 +878,10 @@ video { margin-right: 2rem; } +.mt-4 { + margin-top: 1rem; +} + .mt-6 { margin-top: 1.5rem; } @@ -934,6 +930,10 @@ video { margin-top: 0px; } +.mt-8 { + margin-top: 2rem; +} + .mb-8 { margin-bottom: 2rem; } @@ -1162,6 +1162,10 @@ video { width: 2rem; } +.w-1\/3 { + width: 33.333333%; +} + .w-4 { width: 1rem; } @@ -1407,14 +1411,14 @@ video { border-radius: 9999px; } -.rounded-3xl { - border-radius: 1.5rem; -} - .rounded { border-radius: 0.25rem; } +.rounded-3xl { + border-radius: 1.5rem; +} + .rounded-md { border-radius: 0.375rem; } @@ -1436,14 +1440,14 @@ video { border-bottom-left-radius: 0.25rem; } -.border { - border-width: 1px; -} - .border-2 { border-width: 2px; } +.border { + border-width: 1px; +} + .border-4 { border-width: 4px; } @@ -2866,10 +2870,6 @@ input { width: 75%; } - .lg\:w-1\/3 { - width: 33.333333%; - } - .lg\:w-1\/4 { width: 25%; } @@ -2878,6 +2878,10 @@ input { width: 16.666667%; } + .lg\:w-1\/3 { + width: 33.333333%; + } + .lg\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } diff --git a/langs/en.json b/langs/en.json index f359f52a9..695b25814 100644 --- a/langs/en.json +++ b/langs/en.json @@ -239,6 +239,29 @@ "skip": "Skip this question", "skippedQuestions": "Some questions are skipped", "testing": "Testing - changes won't be saved", + "uploadGpx": { + "choosePermission": "Choose below if your track should be shared:", + "confirm": "Confirm upload", + "description": { + "intro": "Optionally, you can enter a description of your trace below", + "title": "Description" + }, + "intro0": "By uploading your track, OpenStreetMap.org will retain a full copy of the track.", + "intro1": "You will be able to download your track again and to load them into OpenStreetMap editing programs", + "modes": { + "private": { + "docs": "The points of your track will be shared and aggregated among other tracks. The full track will be visible to you and you will be able to load it into other editing programs. OpenStreetMap.org retains a copy of your trace", + "name": "Anonymous" + }, + "public": { + "docs": "Your trace will be visible to everyone, both on your user profile and on the list of GPS-traces on openstreetmap.org", + "name": "Public" + } + }, + "placeHolder": "Enter a description of your trace", + "title": "Upload your track to OpenStreetMap.org", + "uploading": "Uploading your trace..." + }, "useSearch": "Use the search above to see presets", "useSearchForMore": "Use the search function to search within {total} more values....", "weekdays": { From 06b5df833fc1421cd0f47a99252b8f175ab8f0f1 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Fri, 5 Aug 2022 19:47:24 +0200 Subject: [PATCH 3/6] Some finetuning of GPX trace uploading --- Logic/Osm/OsmConnection.ts | 2 -- UI/BigComponents/UploadTraceToOsmUI.ts | 36 ++++++++++++++++---------- UI/SpecialVisualizations.ts | 21 +++++++++------ langs/en.json | 13 ++++++---- 4 files changed, 43 insertions(+), 29 deletions(-) diff --git a/Logic/Osm/OsmConnection.ts b/Logic/Osm/OsmConnection.ts index 07839e4a4..db5fa9fbc 100644 --- a/Logic/Osm/OsmConnection.ts +++ b/Logic/Osm/OsmConnection.ts @@ -8,8 +8,6 @@ import Img from "../../UI/Base/Img"; import {Utils} from "../../Utils"; import {OsmObject} from "./OsmObject"; import {Changes} from "./Changes"; -import {GeoOperations} from "../GeoOperations"; -import { Feature } from "@turf/turf"; export default class UserDetails { diff --git a/UI/BigComponents/UploadTraceToOsmUI.ts b/UI/BigComponents/UploadTraceToOsmUI.ts index b73a44b77..bd82ab590 100644 --- a/UI/BigComponents/UploadTraceToOsmUI.ts +++ b/UI/BigComponents/UploadTraceToOsmUI.ts @@ -17,7 +17,7 @@ export default class UploadTraceToOsmUI extends Toggle { constructor( - trace: () => string, + trace: (title: string) => string, state: { layoutToUse: LayoutConfig; osmConnection: OsmConnection @@ -25,7 +25,7 @@ export default class UploadTraceToOsmUI extends Toggle { whenUploaded?: () => void | Promise }) { const t = Translations.t.general.uploadGpx - + const uploadFinished = new UIEventSource(false) const traceVisibilities: { key: "private" | "public", name: Translation, @@ -33,11 +33,11 @@ export default class UploadTraceToOsmUI extends Toggle { }[] = [ { key: "private", - ...Translations.t.general.uploadGpx.modes.private + ...t.modes.private }, { key: "public", - ...Translations.t.general.uploadGpx.modes.public + ...t.modes.public } ] @@ -52,7 +52,10 @@ export default class UploadTraceToOsmUI extends Toggle { } ) const description = new TextField({ - placeholder: t.placeHolder + placeholder: t.meta.descriptionPlaceHolder + }) + const title = new TextField({ + placeholder: t.meta.titlePlaceholder }) const clicked = new UIEventSource(false) @@ -63,15 +66,17 @@ export default class UploadTraceToOsmUI extends Toggle { t.choosePermission, dropdown, - new Title(t.description.title, 4), - t.description.intro, + new Title(t.meta.title, 4), + t.meta.intro, + title, + t.meta.descriptionIntro, description, new Combine([ new SubtleButton(Svg.close_svg(), Translations.t.general.cancel).onClick(() => { clicked.setData(false) }).SetClass(""), new SubtleButton(Svg.upload_svg(), t.confirm).OnClickWithLoading(t.uploading, async () => { - await state?.osmConnection?.uploadGpxTrack(trace(), { + await state?.osmConnection?.uploadGpxTrack(trace(title.GetValue().data), { visibility: dropdown.GetValue().data, description: description.GetValue().data, labels: ["MapComplete", state?.layoutToUse?.id] @@ -80,17 +85,20 @@ export default class UploadTraceToOsmUI extends Toggle { if (options?.whenUploaded !== undefined) { await options.whenUploaded() } + uploadFinished.setData(true) - }).SetClass("") + }) ]).SetClass("flex flex-wrap flex-wrap-reverse justify-between items-stretch") ]).SetClass("flex flex-col p-4 rounded border-2 m-2 border-subtle") super( - confirmPanel, - new SubtleButton(Svg.upload_svg(), t.title) - .onClick(() => clicked.setData(true)), - clicked - ) + new Combine([Svg.confirm_svg(),t.uploadFinished]).SetClass("flex"), + new Toggle( + confirmPanel, + new SubtleButton(Svg.upload_svg(), t.title) + .onClick(() => clicked.setData(true)), + clicked + ), uploadFinished) } } \ No newline at end of file diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 3f99c175c..2747772d8 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -881,26 +881,31 @@ export default class SpecialVisualizations { { funcName: "upload_to_osm", docs: "Uploads the GPS-history as GPX to OpenStreetMap.org; clears the history afterwards. The actual feature is ignored.", - args:[], + args: [], constr(state, featureTags, args) { - - function getTrace() { - const userLocations : Feature[] = state.historicalUserLocations.features.data.map(f => f.feature) + + function getTrace(title: string) { + title = title?.trim() + if (title === undefined || title === "") { + title = "Uploaded with MapComplete" + } + title = Utils.EncodeXmlValue(title) + const userLocations: Feature[] = state.historicalUserLocations.features.data.map(f => f.feature) const trackPoints: string[] = [] for (const l of userLocations) { let trkpt = ` ` trkpt += ` ` - if(l.properties.altitude !== null && l.properties.altitude !== undefined ){ + if (l.properties.altitude !== null && l.properties.altitude !== undefined) { trkpt += ` ${l.properties.altitude}` } trkpt += " " trackPoints.push(trkpt) } const header = '' - return header+"\n\n"+trackPoints.join("\n")+"\n" + return header + "\n" + title + "\n\n" + trackPoints.join("\n") + "\n" } - - return new UploadTraceToOsmUI(getTrace, state,{ + + return new UploadTraceToOsmUI(getTrace, state, { whenUploaded: async () => { state.historicalUserLocations.features.setData([]) } diff --git a/langs/en.json b/langs/en.json index 695b25814..d50ccc65a 100644 --- a/langs/en.json +++ b/langs/en.json @@ -242,12 +242,15 @@ "uploadGpx": { "choosePermission": "Choose below if your track should be shared:", "confirm": "Confirm upload", - "description": { - "intro": "Optionally, you can enter a description of your trace below", - "title": "Description" - }, "intro0": "By uploading your track, OpenStreetMap.org will retain a full copy of the track.", "intro1": "You will be able to download your track again and to load them into OpenStreetMap editing programs", + "meta": { + "descriptionIntro": "Optionally, you can enter a description of your trace:", + "descriptionPlaceHolder": "Enter a description of your trace", + "intro": "Add a title for your track:", + "title": "Title and description", + "titlePlaceholder": "Enter the title of your trace" + }, "modes": { "private": { "docs": "The points of your track will be shared and aggregated among other tracks. The full track will be visible to you and you will be able to load it into other editing programs. OpenStreetMap.org retains a copy of your trace", @@ -258,8 +261,8 @@ "name": "Public" } }, - "placeHolder": "Enter a description of your trace", "title": "Upload your track to OpenStreetMap.org", + "uploadFinished": "Your track has been uploaded!", "uploading": "Uploading your trace..." }, "useSearch": "Use the search above to see presets", From eaa5fc012ea72ebbee82a0ed7cfd9a54bdd734ed Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Fri, 5 Aug 2022 19:51:52 +0200 Subject: [PATCH 4/6] Disable gps-points from history --- assets/layers/gps_location_history/gps_location_history.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/assets/layers/gps_location_history/gps_location_history.json b/assets/layers/gps_location_history/gps_location_history.json index 036f407e7..3e4f0f582 100644 --- a/assets/layers/gps_location_history/gps_location_history.json +++ b/assets/layers/gps_location_history/gps_location_history.json @@ -2,10 +2,7 @@ "id": "gps_location_history", "description": "Meta layer which contains the previous locations of the user as single points. This is mainly for technical reasons, e.g. to keep match the distance to the modified object", "minzoom": 1, - "name": { - "en": "Location history as points", - "nl": "Locatiegeschiedenis als punten" - }, + "name": null, "source": { "osmTags": "user:location=yes", "#": "Cache is disabled here as these points are kept seperately", From 5360d269a6d0852d74f2f25785d2b9a01720f9b4 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Sun, 7 Aug 2022 21:32:49 +0200 Subject: [PATCH 5/6] Some tweaks to upload functionality --- UI/BigComponents/UploadTraceToOsmUI.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/UI/BigComponents/UploadTraceToOsmUI.ts b/UI/BigComponents/UploadTraceToOsmUI.ts index bd82ab590..5dae8b786 100644 --- a/UI/BigComponents/UploadTraceToOsmUI.ts +++ b/UI/BigComponents/UploadTraceToOsmUI.ts @@ -79,6 +79,7 @@ export default class UploadTraceToOsmUI extends Toggle { await state?.osmConnection?.uploadGpxTrack(trace(title.GetValue().data), { visibility: dropdown.GetValue().data, description: description.GetValue().data, + filename: title.GetValue().data+".gpx", labels: ["MapComplete", state?.layoutToUse?.id] }) @@ -93,7 +94,9 @@ export default class UploadTraceToOsmUI extends Toggle { super( - new Combine([Svg.confirm_svg(),t.uploadFinished]).SetClass("flex"), + new Combine([Svg.confirm_svg().SetClass("w-12 h-12 mr-2"), + t.uploadFinished]) + .SetClass("flex p-2 rounded-xl border-2 subtle-border items-center"), new Toggle( confirmPanel, new SubtleButton(Svg.upload_svg(), t.title) From f48d1a06c31c8d0aa246e7ba41a927c0f65ab587 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Tue, 9 Aug 2022 14:32:24 +0200 Subject: [PATCH 6/6] Add default values for gpx upload --- Logic/Osm/OsmConnection.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Logic/Osm/OsmConnection.ts b/Logic/Osm/OsmConnection.ts index db5fa9fbc..5e545171a 100644 --- a/Logic/Osm/OsmConnection.ts +++ b/Logic/Osm/OsmConnection.ts @@ -341,13 +341,13 @@ export class OsmConnection { const contents = { "file": gpx, - "description": options.description, - "tags": options.labels.join(","), + "description": options.description ?? "", + "tags": options.labels?.join(",") ?? "", "visibility": options.visibility } const extras = { - "file": "; filename=\""+options.filename+"\"\r\nContent-Type: application/gpx+xml" + "file": "; filename=\""+(options.filename ?? ("gpx_track_mapcomplete_"+(new Date().toISOString())))+"\"\r\nContent-Type: application/gpx+xml" } const auth = this.auth;