Better handling of GPX-state

This commit is contained in:
Pieter Vander Vennet 2021-11-12 04:11:53 +01:00
parent 519feaa54b
commit a37a7462a2
17 changed files with 585 additions and 412 deletions

View file

@ -19,7 +19,7 @@ export default class AllKnownLayers {
public static sharedLayersJson: Map<string, any> = AllKnownLayers.getSharedLayersJson();
public static added_by_default: string[] = ["gps_location", "home_location", "gps_track"]
public static added_by_default: string[] = ["gps_location","gps_location_history", "home_location", "gps_track",]
public static no_include: string[] = [ "conflation", "left_right_style"]
/**
* Layer IDs of layers which have special properties through built-in hooks

View file

@ -10,7 +10,7 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
* Makes sure the hash shows the selected element and vice-versa.
*/
export default class SelectedFeatureHandler {
private static readonly _no_trigger_on = new Set(["welcome", "copyright", "layers", "new", "filters", "", undefined])
private static readonly _no_trigger_on = new Set(["welcome", "copyright", "layers", "new", "filters", "location_track", "", undefined])
private readonly hash: UIEventSource<string>;
private readonly state: {
selectedElement: UIEventSource<any>,

View file

@ -5,11 +5,9 @@ import FeatureSource, {FeatureSourceForLayer, IndexedFeatureSource, Tiled} from
import TiledFeatureSource from "./TiledFeatureSource/TiledFeatureSource";
import {UIEventSource} from "../UIEventSource";
import {TileHierarchyTools} from "./TiledFeatureSource/TileHierarchy";
import FilteredLayer from "../../Models/FilteredLayer";
import MetaTagging from "../MetaTagging";
import RememberingSource from "./Sources/RememberingSource";
import OverpassFeatureSource from "../Actors/OverpassFeatureSource";
import {Changes} from "../Osm/Changes";
import GeoJsonSource from "./Sources/GeoJsonSource";
import Loc from "../../Models/Loc";
import RegisteringAllFromFeatureSourceActor from "./Actors/RegisteringAllFromFeatureSourceActor";
@ -22,11 +20,10 @@ import {NewGeometryFromChangesFeatureSource} from "./Sources/NewGeometryFromChan
import ChangeGeometryApplicator from "./Sources/ChangeGeometryApplicator";
import {BBox} from "../BBox";
import OsmFeatureSource from "./TiledFeatureSource/OsmFeatureSource";
import {OsmConnection} from "../Osm/OsmConnection";
import {Tiles} from "../../Models/TileRange";
import TileFreshnessCalculator from "./TileFreshnessCalculator";
import {ElementStorage} from "../ElementStorage";
import FullNodeDatabaseSource from "./TiledFeatureSource/FullNodeDatabaseSource";
import MapState from "../State/MapState";
/**
@ -51,19 +48,7 @@ export default class FeaturePipeline {
public readonly newDataLoadedSignal: UIEventSource<FeatureSource> = new UIEventSource<FeatureSource>(undefined)
private readonly overpassUpdater: OverpassFeatureSource
private state: {
readonly filteredLayers: UIEventSource<FilteredLayer[]>,
readonly locationControl: UIEventSource<Loc>,
readonly selectedElement: UIEventSource<any>,
readonly changes: Changes,
readonly layoutToUse: LayoutConfig,
readonly leafletMap: any,
readonly overpassUrl: UIEventSource<string[]>;
readonly overpassTimeout: UIEventSource<number>;
readonly overpassMaxZoom: UIEventSource<number>;
readonly osmConnection: OsmConnection
readonly currentBounds: UIEventSource<BBox>
};
private state: MapState;
private readonly relationTracker: RelationsTracker
private readonly perLayerHierarchy: Map<string, TileHierarchyMerger>;
@ -74,24 +59,7 @@ export default class FeaturePipeline {
constructor(
handleFeatureSource: (source: FeatureSourceForLayer & Tiled) => void,
state: {
readonly historicalUserLocations: FeatureSourceForLayer & Tiled;
readonly homeLocation: FeatureSourceForLayer & Tiled;
readonly currentUserLocation: FeatureSourceForLayer & Tiled;
readonly filteredLayers: UIEventSource<FilteredLayer[]>,
readonly locationControl: UIEventSource<Loc>,
readonly selectedElement: UIEventSource<any>,
readonly changes: Changes,
readonly layoutToUse: LayoutConfig,
readonly leafletMap: any,
readonly overpassUrl: UIEventSource<string[]>;
readonly overpassTimeout: UIEventSource<number>;
readonly overpassMaxZoom: UIEventSource<number>;
readonly osmConnection: OsmConnection
readonly currentBounds: UIEventSource<BBox>,
readonly osmApiTileSize: UIEventSource<number>,
readonly allElements: ElementStorage
}) {
state: MapState) {
this.state = state;
const self = this
@ -138,7 +106,7 @@ export default class FeaturePipeline {
handleFeatureSource(srcFiltered)
self.somethingLoaded.setData(true)
// We do not mark as visited here, this is the responsability of the code near the actual loader (e.g. overpassLoader and OSMApiFeatureLoader)
};
}
function handlePriviligedFeatureSource(src: FeatureSourceForLayer & Tiled){
// Passthrough to passed function, except that it registers as well
@ -168,11 +136,16 @@ export default class FeaturePipeline {
continue
}
if (id === "gps_track") {
if (id === "gps_location_history") {
handlePriviligedFeatureSource(state.historicalUserLocations)
continue
}
if (id === "gps_track") {
handlePriviligedFeatureSource(state.historicalUserLocationsTrack)
continue
}
if (id === "home_location") {
handlePriviligedFeatureSource(state.homeLocation)
continue

View file

@ -16,6 +16,8 @@ import FilterConfig from "../../Models/ThemeConfig/FilterConfig";
import ShowOverlayLayer from "../../UI/ShowDataLayer/ShowOverlayLayer";
import {FeatureSourceForLayer, Tiled} from "../FeatureSource/FeatureSource";
import SimpleFeatureSource from "../FeatureSource/Sources/SimpleFeatureSource";
import {LocalStorageSource} from "../Web/LocalStorageSource";
import {GeoOperations} from "../GeoOperations";
/**
* Contains all the leaflet-map related state
@ -52,6 +54,11 @@ export default class MapState extends UserRelatedState {
* All previously visited points
*/
public historicalUserLocations: FeatureSourceForLayer & Tiled;
/**
* The number of seconds that the GPS-locations are stored in memory
*/
public gpsLocationHistoryRetentionTime = new UIEventSource(7 * 24 * 60 * 60, "gps_location_retention" )
public historicalUserLocationsTrack: FeatureSourceForLayer & Tiled;
/**
* A feature source containing the current home location of the user
@ -188,42 +195,75 @@ export default class MapState extends UserRelatedState {
}
private initUserLocationTrail(){
const histCoordinates = []
let lineFeature = {
type:"Feature",
geometry:{
type: "LineString",
coordinates: histCoordinates
},
properties:{
"user:location":"yes",
"id":"gps_track"
}
}
const features = new UIEventSource<{feature: any, freshness: Date}[]>([], "gps_track")
const features = LocalStorageSource.GetParsed<{feature: any, freshness: Date}[]>("gps_location_history", [])
const now = new Date().getTime()
features.data = features.data
.map(ff => ({feature: ff.feature, freshness: new Date(ff.freshness)}))
.filter(ff => (now - ff.freshness.getTime()) < this.gpsLocationHistoryRetentionTime.data)
features.ping()
const self = this;
let i = 0
this.currentUserLocation.features.addCallbackAndRunD(([location]) => {
if(location === undefined){
return;
}
const feature = JSON.parse(JSON.stringify(location.feature))
feature.properties.id = "gps/"+i
i++
features.data.push({feature, freshness: new Date()})
histCoordinates.push(feature.geometry.coordinates)
if(lineFeature !== undefined && lineFeature.geometry.coordinates.length >= 2){
features.data.push({feature: lineFeature, freshness: new Date()})
lineFeature = undefined
const previousLocation = features.data[features.data.length - 1]
if(previousLocation !== undefined){
const d = GeoOperations.distanceBetween(
previousLocation.feature.geometry.coordinates,
location.feature.geometry.coordinates
)
if(d < 20){
// Do not append changes less then 20m - it's probably noise anyway
return;
}
}
const feature = JSON.parse(JSON.stringify(location.feature))
feature.properties.id = "gps/"+features.data.length
i++
features.data.push({feature, freshness: new Date()})
features.ping()
})
let gpsLayerDef: FilteredLayer = this.filteredLayers.data.filter(l => l.layerDef.id === "gps_track")[0]
let gpsLayerDef: FilteredLayer = this.filteredLayers.data.filter(l => l.layerDef.id === "gps_location_history")[0]
this.historicalUserLocations = new SimpleFeatureSource(gpsLayerDef, Tiles.tile_index(0, 0, 0), features);
this.changes.useLocationHistory(this)
const asLine = features.map(allPoints => {
if(allPoints === undefined || allPoints.length < 2){
return []
}
const feature = {
type: "Feature",
properties:{
"id":"location_track",
"_date:now": new Date().toISOString(),
},
geometry:{
type: "LineString",
coordinates: allPoints.map(ff => ff.feature.geometry.coordinates)
}
}
self.allElements.ContainingFeatures.set(feature.properties.id, feature)
return [{
feature,
freshness: new Date()
}]
})
let gpsLineLayerDef: FilteredLayer = this.filteredLayers.data.filter(l => l.layerDef.id === "gps_track")[0]
this.historicalUserLocationsTrack = new SimpleFeatureSource(gpsLineLayerDef, Tiles.tile_index(0, 0, 0), asLine);
}
private initHomeLocation() {

View file

@ -39,10 +39,11 @@ import {And} from "../Logic/Tags/And";
import Toggle from "./Input/Toggle";
import {DefaultGuiState} from "./DefaultGuiState";
import {GeoOperations} from "../Logic/GeoOperations";
import Hash from "../Logic/Web/Hash";
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 }[]
@ -174,11 +175,11 @@ export default class SpecialVisualizations {
idList = JSON.parse(value)
}
for (const id of idList) {
const feature = featureStore.get(id)
features.push({
freshness: new Date(),
feature: featureStore.get(id)
feature
})
}
}
@ -617,11 +618,24 @@ export default class SpecialVisualizations {
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", {
Utils.offerContentsAsDownloadableFile(gpx, title + "_mapcomplete_export.gpx", {
mimetype: "{gpx=application/gpx+xml}"
})
})
}
},
{
funcName: "clear_location_history",
docs: "A button to remove the travelled track information from the device",
args: [],
constr: state => {
return new SubtleButton(
Svg.delete_icon_svg().SetStyle("height: 1.5rem"), Translations.t.general.removeLocationHistory
).onClick(() => {
state.historicalUserLocations.features.setData([])
Hash.hash.setData(undefined)
})
}
}

View file

@ -361,6 +361,34 @@ Note that these values can be prepare with javascript in the theme by using a [c
)
}
public static upload(url: string, data, headers?: any): Promise<string> {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onload = () => {
if (xhr.status == 200) {
resolve(xhr.response)
} else if (xhr.status === 509 || xhr.status === 429) {
reject("rate limited")
} else {
reject(xhr.statusText)
}
};
xhr.open('POST', url);
if (headers !== undefined) {
for (const key in headers) {
xhr.setRequestHeader(key, headers[key])
}
}
xhr.send(data);
xhr.onerror = reject
}
)
}
public static async downloadJsonCached(url: string, maxCacheTimeMs: number, headers?: any): Promise<any> {
const cached = Utils._download_cache.get(url)
if (cached !== undefined) {

View file

@ -189,7 +189,6 @@
"it": "Si tratta di un normale defibrillatore automatico o un defibrillatore manuale riservato ai professionisti?",
"de": "Ist dies ein normaler automatischer Defibrillator oder ein manueller Defibrillator nur für Profis?"
},
"condition": {
"and": [
"access=no"

View file

@ -0,0 +1,10 @@
{
"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,
"source": {
"osmTags": "user:location=yes",
"maxCacheAge": 604800
},
"mapRendering": null
}

View file

@ -1,27 +1,32 @@
{
"id": "gps_track",
"description": "Meta layer showing the previou locations of the user. 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. Add this to your theme and override the icon to change the appearance of the current location.",
"minzoom": 0,
"source": {
"osmTags": "user:location=yes",
"osmTags": "id=location_track",
"maxCacheAge": 0
},
"#title": {
"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."
"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 without your permission."
}
},
"export_as_gpx"
"export_as_gpx",
"minimap",
{
"id": "delete",
"render": "{clear_location_history()}"
}
],
"#name": "Your track",
"name": "Your track",
"mapRendering": [
{
"width": 0,
"width": 3,
"color": "#bb000077"
}
]

72
assets/svg/upload.svg Normal file
View file

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 600 600"
enable-background="new 0 0 600 600"
xml:space="preserve"
sodipodi:docname="upload.svg"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"><metadata
id="metadata17"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata>
<defs
id="defs15">
</defs>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1003"
id="namedview13"
showgrid="false"
inkscape:zoom="0.78666667"
inkscape:cx="257.94125"
inkscape:cy="387.47074"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" />
<rect
display="none"
fill="#020202"
stroke="#000000"
stroke-width="1.1344"
stroke-miterlimit="10"
width="600"
height="600"
id="rect2" />
<polygon
display="none"
fill="#FFFFFF"
points="0,0 0,600 600,300 "
id="polygon4" />
<path
style="fill:#000000;fill-opacity:1"
d="m 449.20044,207.4 h -99.8 v 153.3 c 0,14 -11.2,25.5 -25,25.5 h -49.9 c -13.7,0 -25,-11.5 -25,-25.5 V 207.4 h -99.8 L 299.40044,3 Z"
id="path20"
inkscape:connector-curvature="0" /><path
style="fill:#000000;fill-opacity:1"
d="m 37.300444,274.5 c 20.7,0 37.4,17.2 37.4,38.3 V 504.4 H 524.00044 V 312.8 c 0,-21.2 16.8,-38.3 37.4,-38.3 20.7,0 37.5,17.1 37.5,38.3 v 229.9 c 0,21.1 -16.8,38.3 -37.4,38.3 H 37.400444 C 16.700444,581 4.4412682e-4,563.8 4.4412682e-4,542.7 V 312.8 C -0.09955587,291.6 16.700444,274.5 37.300444,274.5 Z"
id="path6" />
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1,8 @@
{
"authors": [
"Pieter Vander Vennet"
],
"path": "upload.svg",
"license": "CC0",
"sources": []
}

View file

@ -5,27 +5,31 @@
"nl": "Verrekijkers",
"de": "Ferngläser",
"it": "Binocoli",
"nb_NO": "Kikkerter"
"nb_NO": "Kikkerter",
"zh_Hant": "望遠鏡"
},
"shortDescription": {
"en": "A map with fixed binoculars",
"nl": "Een kaart met publieke verrekijker",
"de": "Eine Karte mit festinstallierten Ferngläsern",
"it": "Una cartina dei binocoli pubblici fissi",
"nb_NO": "Et kart over fastmonterte kikkerter"
"nb_NO": "Et kart over fastmonterte kikkerter",
"zh_Hant": "固定望遠鏡的地圖"
},
"description": {
"en": "A map with binoculars fixed in place with a pole. It can typically be found on touristic locations, viewpoints, on top of panoramic towers or occasionally on a nature reserve.",
"nl": "Een kaart met verrekijkers die op een vaste plaats zijn gemonteerd",
"de": "Eine Karte mit festinstallierten Ferngläsern. Man findet sie typischerweise an touristischen Orten, Aussichtspunkten, auf Aussichtstürmen oder gelegentlich in einem Naturschutzgebiet.",
"it": "Una cartina dei binocoli su un palo fissi in un luogo. Si trovano tipicamente nei luoghi turistici, nei belvedere, in cima a torri panoramiche oppure occasionalmente nelle riserve naturali."
"it": "Una cartina dei binocoli su un palo fissi in un luogo. Si trovano tipicamente nei luoghi turistici, nei belvedere, in cima a torri panoramiche oppure occasionalmente nelle riserve naturali.",
"zh_Hant": "固定一地的望遠鏡地圖,特別是能夠在旅遊景點、觀景點、城鎮環景點,或是自然保護區找到。"
},
"language": [
"en",
"nl",
"de",
"it",
"nb_NO"
"nb_NO",
"zh_Hant"
],
"maintainer": "",
"icon": "./assets/layers/binocular/telescope.svg",

View file

@ -6,7 +6,8 @@
"de": "Cafés und Kneipen",
"it": "Caffè e pub",
"nb_NO": "Kafeer og kneiper",
"id": "Kafe dan pub"
"id": "Kafe dan pub",
"zh_Hant": "咖啡廳與酒吧"
},
"description": {
"nl": "Cafés, kroegen en drinkgelegenheden"
@ -17,7 +18,8 @@
"de",
"it",
"nb_NO",
"id"
"id",
"zh_Hant"
],
"maintainer": "",
"icon": "./assets/layers/cafe_pub/pub.svg",

View file

@ -658,7 +658,8 @@
"it": "Aggiungi una nuova area di sosta ufficiale per camper. Si tratta di aree destinate alla sosta notturna dei camper. Potrebbe trattarsi di luoghi di campeggio o semplici parcheggi. Potrebbero anche non essere segnalati sul posto, ma semplicemente indicati in una delibera comunale. Un parcheggio destinato ai camper in cui non è però consentito trascorrere la notte -non- va considerato un'area di sosta per camper. ",
"fr": "Ajouter une nouvelle aire de camping officielle, destinée à y passer la nuit avec un camping-car. Elle ne nécessite pas dinfrastructures particulières et peut être simplement désignée sous arrêté municipal, un simple parking ne suffit pas à rentrer dans cette catégorie ",
"de": "Fügen Sie einen neuen offiziellen Wohnmobilstellplatz hinzu. Dies sind ausgewiesene Plätze, an denen Sie in Ihrem Wohnmobil übernachten können. Sie können wie ein richtiger Campingplatz oder nur wie ein Parkplatz aussehen. Möglicherweise sind sie gar nicht ausgeschildert, sondern nur in einem Gemeindebeschluss festgelegt. Ein normaler Parkplatz für Wohnmobile, auf dem übernachten nicht zulässig ist, ist kein Wohnmobilstellplatz. ",
"nl": "Voeg een nieuwe officiële camperplaats toe. Dit zijn speciaal aangeduide plaatsen waar het toegestaan is om te overnachten met een camper. Ze kunnen er uitzien als een parking, of soms eerder als een camping. Soms staan ze niet ter plaatse aangeduid, maar heeft de gemeente wel degelijk beslist dat dit een camperplaats is. Een parking voor campers waar je niet mag overnachten is géén camperplaats. "
"nl": "Voeg een nieuwe officiële camperplaats toe. Dit zijn speciaal aangeduide plaatsen waar het toegestaan is om te overnachten met een camper. Ze kunnen er uitzien als een parking, of soms eerder als een camping. Soms staan ze niet ter plaatse aangeduid, maar heeft de gemeente wel degelijk beslist dat dit een camperplaats is. Een parking voor campers waar je niet mag overnachten is géén camperplaats. ",
"zh_Hant": "新增正式露營地點,通常是設計給過夜的露營者的地點。看起來像是真的露營地或是一般的停車場,而且也許沒有任何指標,但在城鎮被定議地點。如果一般給露營者的停車場並不是用來過夜,則不是露營地點 "
}
}
],
@ -704,7 +705,8 @@
"it": "Luoghi di sversamento delle acque reflue",
"fr": "Site de vidange",
"pt_BR": "Estações de despejo sanitário",
"de": "Sanitäre Entsorgungsstationen"
"de": "Sanitäre Entsorgungsstationen",
"zh_Hant": "垃圾處理站"
},
"minzoom": 10,
"source": {
@ -751,7 +753,8 @@
"it": "Luoghi di sversamento delle acque reflue",
"fr": "Site de vidange",
"pt_BR": "Estações de despejo sanitário",
"de": "Sanitäre Entsorgungsstationen"
"de": "Sanitäre Entsorgungsstationen",
"zh_Hant": "垃圾處理站"
},
"tagRenderings": [
"images",
@ -764,7 +767,8 @@
"it": "Questo luogo è a pagamento?",
"fr": "Ce site est-il payant ?",
"pt_BR": "Este lugar cobra alguma taxa?",
"de": "Wird hier eine Gebühr erhoben?"
"de": "Wird hier eine Gebühr erhoben?",
"zh_Hant": "這個地方需要付費嗎?"
},
"mappings": [
{
@ -780,7 +784,8 @@
"it": "A pagamento",
"fr": "Ce site demande un paiement",
"pt_BR": "Você precisa pagar pelo uso",
"de": "Sie müssen für die Nutzung bezahlen"
"de": "Sie müssen für die Nutzung bezahlen",
"zh_Hant": "你需要付費才能使用"
}
},
{
@ -796,7 +801,8 @@
"it": "È gratuito",
"fr": "Ce site ne demande pas de paiement",
"pt_BR": "Pode ser usado gratuitamente",
"de": "Nutzung kostenlos"
"de": "Nutzung kostenlos",
"zh_Hant": "這裡可以免費使用"
}
}
]
@ -809,7 +815,8 @@
"it": "Ha una tariffa di {charge}",
"fr": "Ce site fait payer {charge}",
"pt_BR": "Este lugar cobra {charge}",
"de": "Die Gebühr beträgt {charge}"
"de": "Die Gebühr beträgt {charge}",
"zh_Hant": "這個地方收費 {charge}"
},
"question": {
"en": "How much does this place charge?",
@ -818,7 +825,8 @@
"it": "Qual è la tariffa di questo luogo?",
"fr": "Combien ce site demande til de payer ?",
"pt_BR": "Quanto este lugar cobra?",
"de": "Wie hoch ist die Gebühr an diesem Ort?"
"de": "Wie hoch ist die Gebühr an diesem Ort?",
"zh_Hant": "這個地方收費多少?"
},
"freeform": {
"key": "charge"
@ -973,7 +981,8 @@
"it": "Chi può utilizzare questo luogo di sversamento?",
"ru": "Кто может использовать эту станцию утилизации?",
"fr": "Qui peut utiliser le site de vidange ?",
"de": "Wer darf diese sanitäre Entsorgungsstation nutzen?"
"de": "Wer darf diese sanitäre Entsorgungsstation nutzen?",
"zh_Hant": "誰可以使用這個垃圾站?"
},
"mappings": [
{
@ -987,7 +996,8 @@
"ja": "これを使用するには、ネットワークキー/コードが必要です",
"it": "Servono una chiave o un codice di accesso",
"fr": "Un code est nécessaire",
"de": "Sie benötigen einen Schlüssel/Code zur Benutzung"
"de": "Sie benötigen einen Schlüssel/Code zur Benutzung",
"zh_Hant": "你需要網路鑰匙/密碼來使用這個設施"
}
},
{
@ -1001,7 +1011,8 @@
"ja": "この場所を使用するには、キャンプ/キャンプサイトのお客様である必要があります",
"it": "È obbligatorio essere un cliente di questo campeggio o di questa area camper",
"fr": "Le site est réservés aux clients",
"de": "Sie müssen Kunde des Campingplatzes sein, um diesen Ort nutzen zu können"
"de": "Sie müssen Kunde des Campingplatzes sein, um diesen Ort nutzen zu können",
"zh_Hant": "你需要是露營/露營地的客戶才能使用這一地方"
}
},
{
@ -1016,7 +1027,8 @@
"it": "Chiunque può farne uso",
"ru": "Любой может воспользоваться этой станцией утилизации",
"fr": "Le site est en libre-service",
"de": "Jeder darf diese sanitäre Entsorgungsstation nutzen"
"de": "Jeder darf diese sanitäre Entsorgungsstation nutzen",
"zh_Hant": "任何人都可以使用這個衛生廢棄物站"
},
"hideInAnswer": true
},
@ -1032,7 +1044,8 @@
"it": "Chiunque può farne uso",
"ru": "Любой может воспользоваться этой станцией утилизации",
"fr": "Le site est en libre-service",
"de": "Jeder darf diese sanitäre Entsorgungsstation nutzen"
"de": "Jeder darf diese sanitäre Entsorgungsstation nutzen",
"zh_Hant": "任何人都可以使用這個垃圾站"
}
}
]
@ -1070,14 +1083,16 @@
"ja": "衛生ゴミ捨て場",
"it": "luogo di sversamento delle acque reflue",
"fr": "Site de vidange",
"de": "Sanitäre Entsorgungsstation"
"de": "Sanitäre Entsorgungsstation",
"zh_Hant": "垃圾丟棄站"
},
"description": {
"en": "Add a new sanitary dump station. This is a place where camper drivers can dump waste water or chemical toilet waste. Often there's also drinking water and electricity.",
"ja": "新しい衛生ゴミ捨て場を追加します。ここは、キャンピングカーの運転手が排水や携帯トイレの廃棄物を捨てることができる場所です。飲料水や電気もあることが多いです。",
"it": "Aggiungi un nuovo luogo di sversamento delle acque reflue. Si tratta di luoghi dove chi viaggia in camper può smaltire le acque grigie o le acque nere. Spesso forniscono anche acqua ed elettricità.",
"fr": "Ajouter un nouveau site de vidange. Un espace où évacuer ses eaux usées (grises et/ou noires) généralement alimenté en eau potable et électricité.",
"de": "Fügen Sie eine neue sanitäre Entsorgungsstation hinzu. Hier können Camper Abwasser oder chemischen Toilettenabfälle entsorgen. Oft gibt es auch Trinkwasser und Strom."
"de": "Fügen Sie eine neue sanitäre Entsorgungsstation hinzu. Hier können Camper Abwasser oder chemischen Toilettenabfälle entsorgen. Oft gibt es auch Trinkwasser und Strom.",
"zh_Hant": "新增垃圾站,這通常是提供露營駕駛丟棄廢水與化學性廁所廢水的地方,也會有飲用水與電力。"
}
}
],

View file

@ -172,6 +172,7 @@
"readYourMessages": "Please, read all your OpenStreetMap-messages before adding a new point.",
"fewChangesBefore": "Please, answer a few questions of existing points before adding a new point.",
"goToInbox": "Open inbox",
"removeLocationHistory": "Delete the location history",
"getStartedLogin": "Log in with OpenStreetMap to get started",
"getStartedNewAccount": " or <a href='https://www.openstreetmap.org/user/new' target='_blank'>create a new account</a>",
"noTagsSelected": "No tags selected",
@ -192,6 +193,8 @@
"downloadGeojson": "Download visible data as GeoJSON",
"downloadGpx":"Download as GPX-file",
"downloadGpxHelper":"A GPX-file can be used with most navigation devices and applications",
"uploadGpx":"Upload your track to OpenStreetMap",
"exporting": "Exporting…",
"downloadGeoJsonHelper": "Compatible with QGIS, ArcGIS, ESRI, …",
"downloadCSV": "Download visible data as CSV",

View file

@ -2852,7 +2852,7 @@
"gps_track": {
"tagRenderings": {
"Privacy notice": {
"render": "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."
"render": "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 without your permission."
}
}
},

View file

@ -16,10 +16,18 @@
"description": "單車圖書館是指每年支付小額費用,然後可以租用單車的地方。最有名的單車圖書館案例是給小孩的,能夠讓長大的小孩用目前的單車換成比較大的單車",
"title": "單車圖書館"
},
"binoculars": {
"description": "固定一地的望遠鏡地圖,特別是能夠在旅遊景點、觀景點、城鎮環景點,或是自然保護區找到。",
"shortDescription": "固定望遠鏡的地圖",
"title": "望遠鏡"
},
"bookcases": {
"description": "公共書架是街邊箱子、盒子、舊的電話亭或是其他存放書本的物件,每一個人都能放置或拿取書本。這份地圖收集所有類型的書架,你可以探索你附近新的書架,同時也能用免費的開放街圖帳號來快速新增你最愛的書架。",
"title": "開放書架地圖"
},
"cafes_and_pubs": {
"title": "咖啡廳與酒吧"
},
"campersite": {
"description": "這個網站收集所有官方露營地點,以及那邊能排放廢水。你可以加上詳細的服務項目與價格,加上圖片以及評價。這是網站與網路 app資料則是存在開放街圖因此會永遠免費而且可以被所有 app 再利用。",
"layers": {
@ -28,8 +36,8 @@
"name": "露營地",
"presets": {
"0": {
"title": "露營地",
"description": "新增正式露營地點,通常是設計給過夜的露營者的地點。看起來像是真的露營地或是一般的停車場,而且也許沒有任何指標,但在城鎮被定議地點。如果一般給露營者的停車場並不是用來過夜,則不是露營地點 "
"description": "新增正式露營地點,通常是設計給過夜的露營者的地點。看起來像是真的露營地或是一般的停車場,而且也許沒有任何指標,但在城鎮被定議地點。如果一般給露營者的停車場並不是用來過夜,則不是露營地點 ",
"title": "露營地"
}
},
"tagRenderings": {
@ -136,7 +144,36 @@
}
},
"1": {
"description": "垃圾處理站",
"name": "垃圾處理站",
"presets": {
"0": {
"description": "新增垃圾站,這通常是提供露營駕駛丟棄廢水與化學性廁所廢水的地方,也會有飲用水與電力。",
"title": "垃圾丟棄站"
}
},
"tagRenderings": {
"dumpstations-access": {
"mappings": {
"0": {
"then": "你需要網路鑰匙/密碼來使用這個設施"
},
"1": {
"then": "你需要是露營/露營地的客戶才能使用這一地方"
},
"2": {
"then": "任何人都可以使用這個衛生廢棄物站"
},
"3": {
"then": "任何人都可以使用這個垃圾站"
}
},
"question": "誰可以使用這個垃圾站?"
},
"dumpstations-charge": {
"question": "這個地方收費多少?",
"render": "這個地方收費 {charge}"
},
"dumpstations-chemical-waste": {
"mappings": {
"0": {
@ -148,23 +185,6 @@
},
"question": "你能在這裡丟棄廁所化學廢棄物嗎?"
},
"dumpstations-access": {
"mappings": {
"0": {
"then": "你需要網路鑰匙/密碼來使用這個設施"
},
"1": {
"then": "你需要是露營/露營地的客戶才能使用這一地方"
},
"3": {
"then": "任何人都可以使用這個垃圾站"
},
"2": {
"then": "任何人都可以使用這個衛生廢棄物站"
}
},
"question": "誰可以使用這個垃圾站?"
},
"dumpstations-fee": {
"mappings": {
"0": {
@ -175,18 +195,6 @@
}
},
"question": "這個地方需要付費嗎?"
},
"dumpstations-charge": {
"render": "這個地方收費 {charge}",
"question": "這個地方收費多少?"
}
},
"description": "垃圾處理站",
"name": "垃圾處理站",
"presets": {
"0": {
"title": "垃圾丟棄站",
"description": "新增垃圾站,這通常是提供露營駕駛丟棄廢水與化學性廁所廢水的地方,也會有飲用水與電力。"
}
}
}
@ -311,13 +319,5 @@
"description": "繪製所有樹木!",
"shortDescription": "所有樹木的地圖",
"title": "樹木"
},
"binoculars": {
"shortDescription": "固定望遠鏡的地圖",
"title": "望遠鏡",
"description": "固定一地的望遠鏡地圖,特別是能夠在旅遊景點、觀景點、城鎮環景點,或是自然保護區找到。"
},
"cafes_and_pubs": {
"title": "咖啡廳與酒吧"
}
}