forked from MapComplete/MapComplete
Refactoring: fix GPX-track view
This commit is contained in:
parent
4172af6a72
commit
c6e12fdd6b
23 changed files with 217 additions and 347 deletions
|
@ -92,13 +92,13 @@ export default class UploadTraceToOsmUI extends LoginToggle {
|
|||
)
|
||||
const descriptionStr = UploadTraceToOsmUI.createDefault(
|
||||
description.GetValue().data,
|
||||
"Track created with MapComplete with theme " + state?.layoutToUse?.id
|
||||
"Track created with MapComplete with theme " + state?.layout?.id
|
||||
)
|
||||
await state?.osmConnection?.uploadGpxTrack(trace(title.GetValue().data), {
|
||||
visibility: dropdown.GetValue().data,
|
||||
description: descriptionStr,
|
||||
filename: titleStr + ".gpx",
|
||||
labels: ["MapComplete", state?.layoutToUse?.id],
|
||||
labels: ["MapComplete", state?.layout?.id],
|
||||
})
|
||||
|
||||
if (options?.whenUploaded !== undefined) {
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
import { InputElement } from "./InputElement"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import { Translation } from "../i18n/Translation"
|
||||
import { SubstitutedTranslation } from "../SubstitutedTranslation"
|
||||
import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"
|
||||
|
||||
export default class InputElementWrapper<T> extends InputElement<T> {
|
||||
private readonly _inputElement: InputElement<T>
|
||||
private readonly _renderElement: BaseUIElement
|
||||
|
||||
constructor(
|
||||
inputElement: InputElement<T>,
|
||||
translation: Translation,
|
||||
key: string,
|
||||
tags: UIEventSource<any>,
|
||||
state: FeaturePipelineState
|
||||
) {
|
||||
super()
|
||||
this._inputElement = inputElement
|
||||
const mapping = new Map<string, BaseUIElement>()
|
||||
|
||||
mapping.set(key, inputElement)
|
||||
|
||||
// Bit of a hack: the SubstitutedTranslation expects a special rendering, but those are formatted '{key()}' instead of '{key}', so we substitute it first
|
||||
translation = translation.OnEveryLanguage((txt) =>
|
||||
txt.replace("{" + key + "}", "{" + key + "()}")
|
||||
)
|
||||
this._renderElement = new SubstitutedTranslation(translation, tags, state, mapping)
|
||||
}
|
||||
|
||||
GetValue(): UIEventSource<T> {
|
||||
return this._inputElement.GetValue()
|
||||
}
|
||||
|
||||
IsValid(t: T): boolean {
|
||||
return this._inputElement.IsValid(t)
|
||||
}
|
||||
|
||||
protected InnerConstructElement(): HTMLElement {
|
||||
return this._renderElement.ConstructElement()
|
||||
}
|
||||
}
|
|
@ -5,7 +5,6 @@ import Img from "../Base/Img"
|
|||
import { FixedUiElement } from "../Base/FixedUiElement"
|
||||
import Combine from "../Base/Combine"
|
||||
import Link from "../Base/Link"
|
||||
import { SubstitutedTranslation } from "../SubstitutedTranslation"
|
||||
import { Utils } from "../../Utils"
|
||||
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
|
||||
import { VariableUiElement } from "../Base/VariableUIElement"
|
||||
|
@ -25,6 +24,7 @@ import { MapLibreAdaptor } from "../Map/MapLibreAdaptor"
|
|||
import ShowDataLayer from "../Map/ShowDataLayer"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import MaplibreMap from "../Map/MaplibreMap.svelte"
|
||||
import SpecialVisualizations from "../SpecialVisualizations"
|
||||
|
||||
export interface AutoAction extends SpecialVisualization {
|
||||
supportsAutoAction: boolean
|
||||
|
@ -148,19 +148,22 @@ class ApplyButton extends UIElement {
|
|||
const featureTags = this.state.featureProperties.getStore(targetFeatureId)
|
||||
const rendering = this.tagRenderingConfig.GetRenderValue(featureTags.data).txt
|
||||
const specialRenderings = Utils.NoNull(
|
||||
SubstitutedTranslation.ExtractSpecialComponents(rendering).map((x) => x.special)
|
||||
).filter((v) => v.func["supportsAutoAction"] === true)
|
||||
SpecialVisualizations.constructSpecification(rendering)
|
||||
).filter((v) => typeof v !== "string" && v.func["supportsAutoAction"] === true)
|
||||
|
||||
if (specialRenderings.length == 0) {
|
||||
console.warn(
|
||||
"AutoApply: feature " +
|
||||
targetFeatureId +
|
||||
" got a rendering without supported auto actions:",
|
||||
targetFeatureId +
|
||||
" got a rendering without supported auto actions:",
|
||||
rendering
|
||||
)
|
||||
}
|
||||
|
||||
for (const specialRendering of specialRenderings) {
|
||||
if (typeof specialRendering === "string") {
|
||||
continue
|
||||
}
|
||||
const action = <AutoAction>specialRendering.func
|
||||
await action.applyActionOn(this.state, featureTags, specialRendering.args)
|
||||
}
|
||||
|
@ -225,7 +228,7 @@ export default class AutoApplyButton implements SpecialVisualization {
|
|||
"To effectively use this button, you'll need some ingredients:",
|
||||
new List([
|
||||
"A target layer with features for which an action is defined in a tag rendering. The following special visualisations support an autoAction: " +
|
||||
supportedActions.join(", "),
|
||||
supportedActions.join(", "),
|
||||
"A host feature to place the auto-action on. This can be a big outline (such as a city). Another good option for this is the layer ",
|
||||
new Link("current_view", "./BuiltinLayers.md#current_view"),
|
||||
"Then, use a calculated tag on the host feature to determine the overlapping object ids",
|
||||
|
@ -245,7 +248,7 @@ export default class AutoApplyButton implements SpecialVisualization {
|
|||
!(
|
||||
state.featureSwitchIsTesting.data ||
|
||||
state.osmConnection._oauth_config.url ===
|
||||
OsmConnection.oauth_configs["osm-test"].url
|
||||
OsmConnection.oauth_configs["osm-test"].url
|
||||
)
|
||||
) {
|
||||
const t = Translations.t.general.add.import
|
||||
|
|
|
@ -5,16 +5,26 @@ import Combine from "../Base/Combine"
|
|||
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||
import { Utils } from "../../Utils"
|
||||
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Feature, LineString } from "geojson"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
|
||||
export class ExportAsGpxViz implements SpecialVisualization {
|
||||
funcName = "export_as_gpx"
|
||||
docs = "Exports the selected feature as GPX-file"
|
||||
args = []
|
||||
|
||||
constr(state: SpecialVisualizationState, tagSource: UIEventSource<Record<string, string>>) {
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
argument: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
) {
|
||||
const t = Translations.t.general.download
|
||||
|
||||
if (feature.geometry.type !== "LineString") {
|
||||
return undefined
|
||||
}
|
||||
return new SubtleButton(
|
||||
Svg.download_ui(),
|
||||
new Combine([
|
||||
|
@ -24,10 +34,8 @@ export class ExportAsGpxViz implements SpecialVisualization {
|
|||
).onClick(() => {
|
||||
console.log("Exporting as GPX!")
|
||||
const tags = tagSource.data
|
||||
const feature = state.indexedFeatures.featuresById.data.get(tags.id)
|
||||
const layer = state?.layout?.getMatchingLayer(tags)
|
||||
const gpx = GeoOperations.AsGpx(feature, { layer })
|
||||
const title = layer.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "gpx_track"
|
||||
const gpx = GeoOperations.toGpx(<Feature<LineString>>feature, title)
|
||||
Utils.offerContentsAsDownloadableFile(gpx, title + "_mapcomplete_export.gpx", {
|
||||
mimetype: "{gpx=application/gpx+xml}",
|
||||
})
|
||||
|
|
|
@ -9,9 +9,7 @@ import InputElementMap from "../Input/InputElementMap"
|
|||
import { SaveButton } from "./SaveButton"
|
||||
import { VariableUiElement } from "../Base/VariableUIElement"
|
||||
import Translations from "../i18n/Translations"
|
||||
import { FixedUiElement } from "../Base/FixedUiElement"
|
||||
import { Translation } from "../i18n/Translation"
|
||||
import Constants from "../../Models/Constants"
|
||||
import { SubstitutedTranslation } from "../SubstitutedTranslation"
|
||||
import { TagsFilter } from "../../Logic/Tags/TagsFilter"
|
||||
import { Tag } from "../../Logic/Tags/Tag"
|
||||
|
@ -19,7 +17,6 @@ import { And } from "../../Logic/Tags/And"
|
|||
import { TagUtils, UploadableTag } from "../../Logic/Tags/TagUtils"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import { DropDown } from "../Input/DropDown"
|
||||
import InputElementWrapper from "../Input/InputElementWrapper"
|
||||
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
|
||||
import TagRenderingConfig, { Mapping } from "../../Models/ThemeConfig/TagRenderingConfig"
|
||||
import { Unit } from "../../Models/Unit"
|
||||
|
@ -626,25 +623,11 @@ export default class TagRenderingQuestion extends Combine {
|
|||
}
|
||||
})
|
||||
|
||||
let inputTagsFilter: InputElement<UploadableTag> = new InputElementMap(
|
||||
return new InputElementMap(
|
||||
input,
|
||||
(a, b) => a === b || (a?.shadows(b) ?? false),
|
||||
pickString,
|
||||
toString
|
||||
)
|
||||
|
||||
if (freeform.inline) {
|
||||
inputTagsFilter.SetClass("w-48-imp")
|
||||
inputTagsFilter = new InputElementWrapper(
|
||||
inputTagsFilter,
|
||||
configuration.render,
|
||||
freeform.key,
|
||||
tags,
|
||||
state
|
||||
)
|
||||
inputTagsFilter.SetClass("block")
|
||||
}
|
||||
|
||||
return inputTagsFilter
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import { GeoLocationPointProperties } from "../../Logic/State/GeoLocationState"
|
|||
import UploadTraceToOsmUI from "../BigComponents/UploadTraceToOsmUI"
|
||||
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||
|
||||
/**
|
||||
* Wrapper around 'UploadTraceToOsmUI'
|
||||
|
@ -20,38 +21,8 @@ export class UploadToOsmViz implements SpecialVisualization {
|
|||
featureTags: UIEventSource<Record<string, string>>,
|
||||
args: string[]
|
||||
) {
|
||||
function getTrace(title: string) {
|
||||
title = title?.trim()
|
||||
if (title === undefined || title === "") {
|
||||
title = "Uploaded with MapComplete"
|
||||
}
|
||||
title = Utils.EncodeXmlValue(title)
|
||||
const userLocations = <Feature<Point, GeoLocationPointProperties>[]>(
|
||||
state.historicalUserLocations.features.data
|
||||
)
|
||||
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<name>" +
|
||||
title +
|
||||
"</name>\n<trk><trkseg>\n" +
|
||||
trackPoints.join("\n") +
|
||||
"\n</trkseg></trk></gpx>"
|
||||
)
|
||||
}
|
||||
|
||||
return new UploadTraceToOsmUI(getTrace, state, {
|
||||
const locations = state.historicalUserLocations.features.data
|
||||
return new UploadTraceToOsmUI((title) => GeoOperations.toGpx(locations, title), state, {
|
||||
whenUploaded: async () => {
|
||||
state.historicalUserLocations.features.setData([])
|
||||
},
|
||||
|
|
|
@ -6,7 +6,7 @@ import { OsmConnection } from "../Logic/Osm/OsmConnection";
|
|||
import { Changes } from "../Logic/Osm/Changes";
|
||||
import { ExportableMap, MapProperties } from "../Models/MapProperties";
|
||||
import LayerState from "../Logic/State/LayerState";
|
||||
import { Feature, Geometry } from "geojson";
|
||||
import { Feature, Geometry, Point } from "geojson";
|
||||
import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource";
|
||||
import { MangroveIdentity } from "../Logic/Web/MangroveReviews";
|
||||
import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore";
|
||||
|
@ -34,7 +34,7 @@ export interface SpecialVisualizationState {
|
|||
*/
|
||||
readonly newFeatures: WritableFeatureSource
|
||||
|
||||
readonly historicalUserLocations: WritableFeatureSource
|
||||
readonly historicalUserLocations: WritableFeatureSource<Feature<Point>>
|
||||
|
||||
readonly osmConnection: OsmConnection
|
||||
readonly featureSwitchUserbadge: Store<boolean>
|
||||
|
|
|
@ -13,7 +13,6 @@ import { MinimapViz } from "./Popup/MinimapViz"
|
|||
import { ShareLinkViz } from "./Popup/ShareLinkViz"
|
||||
import { UploadToOsmViz } from "./Popup/UploadToOsmViz"
|
||||
import { MultiApplyViz } from "./Popup/MultiApplyViz"
|
||||
import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz"
|
||||
import { AddNoteCommentViz } from "./Popup/AddNoteCommentViz"
|
||||
import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz"
|
||||
import { ConflateButton, ImportPointButton, ImportWayButton } from "./Popup/ImportButton"
|
||||
|
@ -80,6 +79,7 @@ import DeleteWizard from "./Popup/DeleteWizard"
|
|||
import { OsmId, OsmTags, WayId } from "../Models/OsmFeature"
|
||||
import MoveWizard from "./Popup/MoveWizard"
|
||||
import SplitRoadWizard from "./Popup/SplitRoadWizard"
|
||||
import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz"
|
||||
|
||||
class NearbyImageVis implements SpecialVisualization {
|
||||
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
|
||||
|
@ -597,9 +597,9 @@ export default class SpecialVisualizations {
|
|||
},
|
||||
},
|
||||
new ShareLinkViz(),
|
||||
new ExportAsGpxViz(),
|
||||
new UploadToOsmViz(),
|
||||
new MultiApplyViz(),
|
||||
new ExportAsGpxViz(),
|
||||
new AddNoteCommentViz(),
|
||||
{
|
||||
funcName: "open_note",
|
||||
|
@ -874,7 +874,7 @@ export default class SpecialVisualizations {
|
|||
funcName: "export_as_geojson",
|
||||
docs: "Exports the selected feature as GeoJson-file",
|
||||
args: [],
|
||||
constr: (state, tagSource) => {
|
||||
constr: (state, tagSource, tagsSource, feature, layer) => {
|
||||
const t = Translations.t.general.download
|
||||
|
||||
return new SubtleButton(
|
||||
|
@ -886,10 +886,8 @@ export default class SpecialVisualizations {
|
|||
).onClick(() => {
|
||||
console.log("Exporting as Geojson")
|
||||
const tags = tagSource.data
|
||||
const feature = state.indexedFeatures.featuresById.data.get(tags.id)
|
||||
const matchingLayer = state?.layout?.getMatchingLayer(tags)
|
||||
const title =
|
||||
matchingLayer.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "geojson"
|
||||
layer?.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "geojson"
|
||||
const data = JSON.stringify(feature, null, " ")
|
||||
Utils.offerContentsAsDownloadableFile(
|
||||
data,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue