Refactoring: put all special visualisations into their own class, add their location into the documentation

This commit is contained in:
Pieter Vander Vennet 2025-06-26 05:20:12 +02:00
parent c0e7c9e8fa
commit ae5205f92d
29 changed files with 2270 additions and 2075 deletions

View file

@ -1,20 +1,16 @@
import { Store, Stores, UIEventSource } from "../../Logic/UIEventSource"
import ThemeConfig from "../../Models/ThemeConfig/ThemeConfig"
import { Changes } from "../../Logic/Osm/Changes"
import {
SpecialVisualization,
SpecialVisualizationState,
SpecialVisualizationSvelte,
} from "../SpecialVisualization"
import { SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
import { IndexedFeatureSource } from "../../Logic/FeatureSource/FeatureSource"
import SvelteUIElement from "../Base/SvelteUIElement"
import { Feature } from "geojson"
import AutoApplyButton from "./AutoApplyButton.svelte"
export interface AutoAction extends SpecialVisualization {
export abstract class AutoAction extends SpecialVisualization {
supportsAutoAction: boolean
applyActionOn(
abstract applyActionOn(
feature: Feature,
state: {
theme: ThemeConfig
@ -26,8 +22,8 @@ export interface AutoAction extends SpecialVisualization {
): Promise<void>
}
export default class AutoApplyButtonVis implements SpecialVisualizationSvelte {
public readonly docs: string
export default class AutoApplyButtonVis extends SpecialVisualizationSvelte {
public readonly docs: string = ""
public readonly funcName: string = "auto_apply"
public readonly needsUrls = []
public readonly group = "data_import"
@ -65,6 +61,7 @@ export default class AutoApplyButtonVis implements SpecialVisualizationSvelte {
]
constructor(allSpecialVisualisations: SpecialVisualization[]) {
super()
this.docs = AutoApplyButtonVis.generateDocs(
allSpecialVisualisations
.filter((sv) => sv["supportsAutoAction"] === true)
@ -124,7 +121,5 @@ export default class AutoApplyButtonVis implements SpecialVisualizationSvelte {
return new SvelteUIElement(AutoApplyButton, { state, ids: stableIds, options })
}
getLayerDependencies(args: string[]): string[] {
return [args[0]]
}
getLayerDependencies = (args: string[]): string[] => [args[0]]
}

View file

@ -0,0 +1,67 @@
import { SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
import { UIEventSource } from "../../Logic/UIEventSource"
import { Feature, LineString } from "geojson"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import Translations from "../i18n/Translations"
import SvelteUIElement from "../Base/SvelteUIElement"
import ExportFeatureButton from "./ExportFeatureButton.svelte"
import { GeoOperations } from "../../Logic/GeoOperations"
class ExportAsGpxVis extends SpecialVisualizationSvelte {
funcName = "export_as_gpx"
docs = "Exports the selected feature as GPX-file"
args = []
needsUrls = []
constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig,
) {
if (feature.geometry.type !== "LineString") {
return undefined
}
const t = Translations.t.general.download
return new SvelteUIElement(ExportFeatureButton, {
tags,
feature,
layer,
mimetype: "{gpx=application/gpx+xml}",
extension: "gpx",
construct: (feature: Feature<LineString>, title: string) =>
GeoOperations.toGpx(feature, title),
helpertext: t.downloadGpxHelper,
maintext: t.downloadFeatureAsGpx,
})
}
}
class ExportAsGeojsonVis extends SpecialVisualizationSvelte {
funcName = "export_as_geojson"
docs = "Exports the selected feature as GeoJson-file"
args = []
constr(state, tags, args, feature, layer) {
const t = Translations.t.general.download
return new SvelteUIElement(ExportFeatureButton, {
tags,
feature,
layer,
mimetype: "application/vnd.geo+json",
extension: "geojson",
construct: (feature: Feature<LineString>) =>
JSON.stringify(feature, null, " "),
maintext: t.downloadFeatureAsGeojson,
helpertext: t.downloadGeoJsonHelper,
})
}
}
export class DataExportVisualisations {
public static initList(): SpecialVisualization[] {
return [new ExportAsGpxVis(), new ExportAsGeojsonVis()]
}
}

View file

@ -0,0 +1,352 @@
import { SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization"
import { HistogramViz } from "./HistogramViz"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { Feature } from "geojson"
import BaseUIElement from "../BaseUIElement"
import SvelteUIElement from "../Base/SvelteUIElement"
import DirectionIndicator from "../Base/DirectionIndicator.svelte"
import { VariableUiElement } from "../Base/VariableUIElement"
import { GeoOperations } from "../../Logic/GeoOperations"
import Translations from "../i18n/Translations"
import Constants from "../../Models/Constants"
import opening_hours from "opening_hours"
import { OH } from "../OpeningHours/OpeningHours"
import OpeningHoursWithError from "../OpeningHours/Visualisation/OpeningHoursWithError.svelte"
import NextChangeViz from "../OpeningHours/NextChangeViz.svelte"
import { Unit } from "../../Models/Unit"
import AllFeaturesStatistics from "../Statistics/AllFeaturesStatistics.svelte"
import { LanguageElement } from "./LanguageElement/LanguageElement"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
import { And } from "../../Logic/Tags/And"
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
import TagRenderingEditable from "./TagRendering/TagRenderingEditable.svelte"
import AllTagsPanel from "./AllTagsPanel/AllTagsPanel.svelte"
import { FixedUiElement } from "../Base/FixedUiElement"
import { TagUtils } from "../../Logic/Tags/TagUtils"
class DirectionIndicatorVis extends SpecialVisualization {
funcName = "direction_indicator"
args = []
docs = "Gives a distance indicator and a compass pointing towards the location from your GPS-location. If clicked, centers the map on the object"
group = "data"
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
): BaseUIElement {
return new SvelteUIElement(DirectionIndicator, { state, feature })
}
}
class DirectionAbsolute extends SpecialVisualization {
funcName = "direction_absolute"
docs = "Converts compass degrees (with 0° being north, 90° being east, ...) into a human readable, translated direction such as 'north', 'northeast'"
args = [
{
name: "key",
type: "key",
doc: "The attribute containing the degrees",
defaultValue: "_direction:centerpoint",
},
{
name: "offset",
doc: "Offset value that is added to the actual value, e.g. `180` to indicate the opposite (backward) direction",
defaultValue: "0",
},
]
group = "data"
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
args: string[],
): BaseUIElement {
const key = args[0] === "" ? "_direction:centerpoint" : args[0]
const offset = args[1] === "" ? 0 : Number(args[1])
return new VariableUiElement(
tagSource
.map((tags) => {
console.log("Direction value", tags[key], key)
return tags[key]
})
.mapD((value) => {
const dir = GeoOperations.bearingToHuman(
GeoOperations.parseBearing(value) + offset,
)
console.log("Human dir", dir)
return Translations.t.general.visualFeedback.directionsAbsolute[dir]
}),
)
}
}
class OpeningHoursTableVis extends SpecialVisualizationSvelte {
funcName = "opening_hours_table"
docs = "Creates an opening-hours table. Usage: {opening_hours_table(opening_hours)} to create a table of the tag 'opening_hours'."
args = [
{
name: "key",
defaultValue: "opening_hours",
type: "key",
doc: "The tagkey from which the table is constructed.",
},
{
name: "prefix",
defaultValue: "",
doc: "Remove this string from the start of the value before parsing. __Note: use `&LPARENs` to indicate `(` if needed__",
},
{
name: "postfix",
defaultValue: "",
doc: "Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__",
},
]
group = "data"
needsUrls = [Constants.countryCoderEndpoint]
example =
"A normal opening hours table can be invoked with `{opening_hours_table()}`. A table for e.g. conditional access with opening hours can be `{opening_hours_table(access:conditional, no @ &LPARENS, &RPARENS)}`"
constr(state, tagSource: UIEventSource<any>, args) {
const [key, prefix, postfix] = args
const openingHoursStore: Store<opening_hours | "error" | undefined> =
OH.CreateOhObjectStore(tagSource, key, prefix, postfix)
return new SvelteUIElement(OpeningHoursWithError, {
tags: tagSource,
key,
opening_hours_obj: openingHoursStore,
})
}
}
class OpeningHoursState extends SpecialVisualizationSvelte {
group = "data"
funcName = "opening_hours_state"
docs = "A small element, showing if the POI is currently open and when the next change is"
args = [
{
name: "key",
type: "key",
defaultValue: "opening_hours",
doc: "The tagkey from which the opening hours are read.",
},
{
name: "prefix",
defaultValue: "",
doc: "Remove this string from the start of the value before parsing. __Note: use `&LPARENs` to indicate `(` if needed__",
},
{
name: "postfix",
defaultValue: "",
doc: "Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__",
},
]
constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
args: string[],
): SvelteUIElement {
const keyToUse = args[0]
const prefix = args[1]
const postfix = args[2]
return new SvelteUIElement(NextChangeViz, {
state,
keyToUse,
tags,
prefix,
postfix,
})
}
}
class Canonical extends SpecialVisualization {
group = "data"
funcName = "canonical"
docs = "Converts a short, canonical value into the long, translated text including the unit. This only works if a `unit` is defined for the corresponding value. The unit specification will be included in the text. "
example =
"If the object has `length=42`, then `{canonical(length)}` will be shown as **42 meter** (in english), **42 metre** (in french), ..."
args = [
{
name: "key",
type: "key",
doc: "The key of the tag to give the canonical text for",
required: true,
},
]
constr(state, tagSource, args) {
const key = args[0]
return new VariableUiElement(
tagSource
.map((tags) => tags[key])
.map((value) => {
if (value === undefined) {
return undefined
}
const allUnits: Unit[] = [].concat(
...(state?.theme?.layers?.map((lyr) => lyr.units) ?? []),
)
const unit = allUnits.filter((unit) =>
unit.isApplicableToKey(key),
)[0]
if (unit === undefined) {
return value
}
const getCountry = () => tagSource.data._country
return unit.asHumanLongValue(value, getCountry)
}),
)
}
}
class StatisticsVis extends SpecialVisualizationSvelte {
funcName = "statistics"
group = "data"
docs = "Show general statistics about all the elements currently in view. Intended to use on the `current_view`-layer. They will be split per layer"
args = []
constr(state) {
return new SvelteUIElement(AllFeaturesStatistics, { state })
}
}
class PresetDescription extends SpecialVisualization {
funcName = "preset_description"
docs = "Shows the extra description from the presets of the layer, if one matches. It will pick the most specific one (e.g. if preset `A` implies `B`, but `B` does not imply `A`, it'll pick B) or the first one if no ordering can be made. Might be empty"
args = []
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
): BaseUIElement {
const translation = tagSource.map((tags) => {
const layer = state.theme.getMatchingLayer(tags)
return layer?.getMostMatchingPreset(tags)?.description
})
return new VariableUiElement(translation)
}
}
class PresetTypeSelect extends SpecialVisualizationSvelte {
funcName = "preset_type_select"
docs = "An editable tag rendering which allows to change the type"
args = []
constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
argument: string[],
selectedElement: Feature,
layer: LayerConfig,
): SvelteUIElement {
const t = Translations.t.preset_type
const question: QuestionableTagRenderingConfigJson = {
id: layer.id + "-type",
question: t.question.translations,
mappings: layer.presets.map((pr) => ({
if: new And(pr.tags).asJson(),
icon: "auto",
then: (pr.description ? t.typeDescription : t.typeTitle).Subs({
title: pr.title,
description: pr.description,
}).translations,
})),
}
const config = new TagRenderingConfig(question)
return new SvelteUIElement(TagRenderingEditable, {
config,
tags,
selectedElement,
state,
layer,
})
}
}
class AllTagsVis extends SpecialVisualizationSvelte {
funcName = "all_tags"
docs = "Prints all key-value pairs of the object - used for debugging"
args = []
group = "data"
constr(
state,
tags: UIEventSource<Record<string, string>>,
_,
__,
layer: LayerConfig,
) {
return new SvelteUIElement(AllTagsPanel, { tags, layer })
}
}
class TagsVis extends SpecialVisualization {
funcName = "tags"
docs = "Shows a (json of) tags in a human-readable way + links to the wiki"
args = [
{
name: "key",
type: "key",
defaultValue: "value",
doc: "The key to look for the tags",
},
]
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
): BaseUIElement {
const key = argument[0] ?? "value"
return new VariableUiElement(
tagSource.map((tags) => {
let value = tags[key]
if (!value) {
return new FixedUiElement("No tags found").SetClass("font-bold")
}
if (typeof value === "string" && value.startsWith("{")) {
value = JSON.parse(value)
}
try {
const parsed = TagUtils.Tag(value)
return parsed.asHumanString(true, false, {})
} catch (e) {
return new FixedUiElement(
"Could not parse this tag: " +
JSON.stringify(value) +
" due to " +
e,
).SetClass("alert")
}
}),
)
}
}
export class DataVisualisations {
public static initList(): SpecialVisualization[] {
return [
new HistogramViz(),
new StatisticsVis(),
new DirectionAbsolute(),
new DirectionIndicatorVis(),
new OpeningHoursTableVis(),
new OpeningHoursState(),
new Canonical(),
new LanguageElement(),
new PresetDescription(),
new PresetTypeSelect(),
new AllTagsVis(),
new TagsVis(),
]
}
}

View file

@ -3,7 +3,7 @@ import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisua
import Histogram from "../BigComponents/Histogram"
import { Feature } from "geojson"
export class HistogramViz implements SpecialVisualization {
export class HistogramViz extends SpecialVisualization {
funcName = "histogram"
docs = "Create a histogram for a list of given values, read from the properties."
needsUrls = []

View file

@ -13,6 +13,7 @@ import { IndexedFeatureSource } from "../../../Logic/FeatureSource/FeatureSource
import { Changes } from "../../../Logic/Osm/Changes"
import ThemeConfig from "../../../Models/ThemeConfig/ThemeConfig"
import { OsmConnection } from "../../../Logic/Osm/OsmConnection"
import { OsmTags } from "../../../Models/OsmFeature"
export interface ConflateFlowArguments extends ImportFlowArguments {
way_to_conflate: string
@ -21,7 +22,7 @@ export interface ConflateFlowArguments extends ImportFlowArguments {
snap_onto_layers?: string
}
export default class ConflateImportButtonViz implements SpecialVisualization, AutoAction {
export default class ConflateImportButtonViz extends SpecialVisualization implements AutoAction {
supportsAutoAction: boolean = true
needsUrls = []
group = "data_import"
@ -85,7 +86,7 @@ export default class ConflateImportButtonViz implements SpecialVisualization, Au
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
tagSource: UIEventSource<OsmTags>,
argument: string[],
feature: Feature
): BaseUIElement {
@ -111,7 +112,5 @@ export default class ConflateImportButtonViz implements SpecialVisualization, Au
})
}
getLayerDependencies(args: string[]) {
return ImportFlowUtils.getLayerDependenciesWithSnapOnto(this.args, args)
}
getLayerDependencies = (args: string[]) => ImportFlowUtils.getLayerDependenciesWithSnapOnto(this.args, args)
}

View file

@ -9,53 +9,46 @@ import { Utils } from "../../../Utils"
import { ImportFlowUtils } from "./ImportFlow"
import Translations from "../../i18n/Translations"
import { GeoOperations } from "../../../Logic/GeoOperations"
import { OsmTags } from "../../../Models/OsmFeature"
/**
* The wrapper to make the special visualisation for the PointImportFlow
*/
export class PointImportButtonViz implements SpecialVisualization {
public readonly funcName: string
public readonly docs: string | BaseUIElement
public readonly example?: string
public readonly args: { name: string; defaultValue?: string; doc: string; split?: boolean }[]
export class PointImportButtonViz extends SpecialVisualization {
public readonly funcName = "import_button"
public readonly docs: string = "This button will copy the point from an external dataset into OpenStreetMap" +
ImportFlowUtils.documentationGeneral
public readonly args: { name: string; defaultValue?: string; doc: string; split?: boolean }[] = [
...ImportFlowUtils.generalArguments,
{
name: "snap_onto_layers",
doc: "If a way of the given layer(s) is closeby, will snap the new point onto this way (similar as preset might snap). To show multiple layers to snap onto, use a `;`-seperated list",
},
{
name: "max_snap_distance",
doc: "The maximum distance that the imported point will be moved to snap onto a way in an already existing layer (in meters). This is previewed to the contributor, similar to the 'add new point'-action of MapComplete",
defaultValue: "5",
},
{
name: "note_id",
doc: "If given, this key will be read. The corresponding note on OSM will be closed, stating 'imported'",
},
{
name: "maproulette_id",
doc: "The property name of the maproulette_id - this is probably `mr_taskId`. If given, the maproulette challenge will be marked as fixed. Only use this if part of a maproulette-layer.",
},
{
name: "to_point",
doc: "If set, a feature will be converted to a centerpoint",
},
]
public needsUrls = []
group = "data_import"
constructor() {
this.funcName = "import_button"
this.docs =
"This button will copy the point from an external dataset into OpenStreetMap" +
ImportFlowUtils.documentationGeneral
this.args = [
...ImportFlowUtils.generalArguments,
{
name: "snap_onto_layers",
doc: "If a way of the given layer(s) is closeby, will snap the new point onto this way (similar as preset might snap). To show multiple layers to snap onto, use a `;`-seperated list",
},
{
name: "max_snap_distance",
doc: "The maximum distance that the imported point will be moved to snap onto a way in an already existing layer (in meters). This is previewed to the contributor, similar to the 'add new point'-action of MapComplete",
defaultValue: "5",
},
{
name: "note_id",
doc: "If given, this key will be read. The corresponding note on OSM will be closed, stating 'imported'",
},
{
name: "maproulette_id",
doc: "The property name of the maproulette_id - this is probably `mr_taskId`. If given, the maproulette challenge will be marked as fixed. Only use this if part of a maproulette-layer.",
},
{
name: "to_point",
doc: "If set, a feature will be converted to a centerpoint",
},
]
}
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
tagSource: UIEventSource<OsmTags>,
argument: string[],
feature: Feature
): BaseUIElement {

View file

@ -6,7 +6,7 @@ import { Feature, Point } from "geojson"
import Maproulette from "../../../Logic/Maproulette"
import { GeoOperations } from "../../../Logic/GeoOperations"
import { Tag } from "../../../Logic/Tags/Tag"
import ThemeViewState from "../../../Models/ThemeViewState"
import { SpecialVisualizationState } from "../../SpecialVisualization"
export interface PointImportFlowArguments extends ImportFlowArguments {
max_snap_distance?: string
@ -21,7 +21,7 @@ export class PointImportFlowState extends ImportFlow<PointImportFlowArguments> {
public readonly startCoordinate: [number, number]
constructor(
state: ThemeViewState,
state: SpecialVisualizationState,
originalFeature: Feature<Point>,
args: PointImportFlowArguments,
tagsToApply: Store<Tag[]>,

View file

@ -14,11 +14,12 @@ import ThemeConfig from "../../../Models/ThemeConfig/ThemeConfig"
import { Changes } from "../../../Logic/Osm/Changes"
import { IndexedFeatureSource } from "../../../Logic/FeatureSource/FeatureSource"
import FullNodeDatabaseSource from "../../../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"
import { OsmTags } from "../../../Models/OsmFeature"
/**
* Wrapper around 'WayImportFlow' to make it a special visualisation
*/
export default class WayImportButtonViz implements AutoAction, SpecialVisualization {
export default class WayImportButtonViz extends SpecialVisualization implements AutoAction {
public readonly funcName: string = "import_way_button"
needsUrls = []
group = "data_import"
@ -62,7 +63,7 @@ export default class WayImportButtonViz implements AutoAction, SpecialVisualizat
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
tagSource: UIEventSource<OsmTags>,
argument: string[],
feature: Feature,
_: LayerConfig
@ -125,7 +126,5 @@ export default class WayImportButtonViz implements AutoAction, SpecialVisualizat
await state.changes.applyAction(action)
}
getLayerDependencies(args: string[]) {
return ImportFlowUtils.getLayerDependenciesWithSnapOnto(this.args, args)
}
getLayerDependencies = (args: string[]) => ImportFlowUtils.getLayerDependenciesWithSnapOnto(this.args, args)
}

View file

@ -6,7 +6,7 @@ import { Feature } from "geojson"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import { default as LanguageElementSvelte } from "./LanguageElement.svelte"
export class LanguageElement implements SpecialVisualization {
export class LanguageElement extends SpecialVisualization {
funcName: string = "language_chooser"
needsUrls = []
@ -45,7 +45,7 @@ export class LanguageElement implements SpecialVisualization {
},
]
example: `
example= `
\`\`\`json
{"special":
"type": "language_chooser",

View file

@ -5,7 +5,7 @@ import { Feature } from "geojson"
import SvelteUIElement from "../Base/SvelteUIElement"
import MapillaryLink from "../BigComponents/MapillaryLink.svelte"
export class MapillaryLinkVis implements SpecialVisualizationSvelte {
export class MapillaryLinkVis extends SpecialVisualizationSvelte {
funcName = "mapillary_link"
group = "web_and_communication"

View file

@ -4,7 +4,7 @@ import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisua
import SvelteUIElement from "../Base/SvelteUIElement"
import MultiApplyButton from "./MultiApplyButton.svelte"
export class MultiApplyViz implements SpecialVisualization {
export class MultiApplyViz extends SpecialVisualization {
funcName = "multi_apply"
needsUrls = []
docs =

View file

@ -10,7 +10,7 @@ import SvelteUIElement from "../Base/SvelteUIElement"
import PlantNet from "../PlantNet/PlantNet.svelte"
import { default as PlantNetCode } from "../../Logic/Web/PlantNet"
export class PlantNetDetectionViz implements SpecialVisualization {
export class PlantNetDetectionViz extends SpecialVisualization {
funcName = "plantnet_detection"
needsUrls = [PlantNetCode.baseUrl]
group = "data_import"

View file

@ -4,7 +4,7 @@ import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../Specia
import SvelteUIElement from "../Base/SvelteUIElement"
import ShareButton from "../Base/ShareButton.svelte"
export class ShareLinkViz implements SpecialVisualizationSvelte {
export class ShareLinkViz extends SpecialVisualizationSvelte {
funcName = "share_link"
group = "default"
docs = "Creates a link that (attempts to) open the native 'share'-screen"

View file

@ -8,7 +8,7 @@ import UploadTraceToOsmUI from "../BigComponents/UploadTraceToOsmUI.svelte"
/**
* Wrapper around 'UploadTraceToOsmUI'
*/
export class UploadToOsmViz implements SpecialVisualization {
export class UploadToOsmViz extends SpecialVisualization {
funcName = "upload_to_osm"
docs =
"Uploads the GPS-history as GPX to OpenStreetMap.org; clears the history afterwards. The actual feature is ignored."