forked from MapComplete/MapComplete
Add possibility to upload your travelled track to OSM
This commit is contained in:
parent
9424364f3f
commit
312db3ad50
11 changed files with 208 additions and 44 deletions
|
@ -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
|
||||
}
|
||||
|
|
96
UI/BigComponents/UploadTraceToOsmUI.ts
Normal file
96
UI/BigComponents/UploadTraceToOsmUI.ts
Normal file
|
@ -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<void>
|
||||
}) {
|
||||
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: <any>state?.osmConnection?.GetPreference("gps.trace.visibility")
|
||||
}
|
||||
)
|
||||
const description = new TextField({
|
||||
placeholder: t.placeHolder
|
||||
})
|
||||
const clicked = new UIEventSource<boolean>(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
|
||||
)
|
||||
}
|
||||
}
|
|
@ -14,14 +14,15 @@ export class RadioButton<T> extends InputElement<T> {
|
|||
elements: InputElement<T>[],
|
||||
options?: {
|
||||
selectFirstAsDefault?: true | boolean,
|
||||
dontStyle?: boolean
|
||||
dontStyle?: boolean,
|
||||
value?: UIEventSource<T>
|
||||
}
|
||||
) {
|
||||
super();
|
||||
options = options ?? {}
|
||||
this._selectFirstAsDefault = options.selectFirstAsDefault ?? true;
|
||||
this._elements = Utils.NoNull(elements);
|
||||
this.value = new UIEventSource<T>(undefined);
|
||||
this.value = options?.value ?? new UIEventSource<T>(undefined);
|
||||
this._dontStyle = options.dontStyle ?? false
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Point, GeoLocationPointProperties>[] = state.historicalUserLocations.features.data.map(f => f.feature)
|
||||
const trackPoints: string[] = []
|
||||
for (const l of userLocations) {
|
||||
let trkpt = ` <trkpt lat="${l.geometry.coordinates[1]}" lon="${l.geometry.coordinates[0]}">`
|
||||
trkpt += ` <time>${l.properties.date}</time>`
|
||||
if(l.properties.altitude !== null && l.properties.altitude !== undefined ){
|
||||
trkpt += ` <ele>${l.properties.altitude}</ele>`
|
||||
}
|
||||
trkpt += " </trkpt>"
|
||||
trackPoints.push(trkpt)
|
||||
}
|
||||
const header = '<gpx version="1.1" creator="MapComplete track uploader" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">'
|
||||
return header+"\n<trk><trkseg>\n"+trackPoints.join("\n")+"\n</trkseg></trk></gpx>"
|
||||
}
|
||||
|
||||
return new UploadTraceToOsmUI(getTrace, state,{
|
||||
whenUploaded: async () => {
|
||||
state.historicalUserLocations.features.setData([])
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
funcName: "export_as_geojson",
|
||||
docs: "Exports the selected feature as GeoJson-file",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue