Merge branch 'develop' into feature/professional
|
@ -19,14 +19,66 @@ 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 no_include: string[] = [ "conflation", "left_right_style"]
|
||||
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
|
||||
*/
|
||||
public static priviliged_layers: string[] = [...AllKnownLayers.added_by_default, "type_node",...AllKnownLayers.no_include]
|
||||
public static priviliged_layers: string[] = [...AllKnownLayers.added_by_default, "type_node", ...AllKnownLayers.no_include]
|
||||
|
||||
/**
|
||||
* Gets the appropriate tagRenderingJSON
|
||||
* Allows to steal them from other layers.
|
||||
* This will add the tags of the layer to the configuration though!
|
||||
* @param renderingId
|
||||
*/
|
||||
static getTagRendering(renderingId: string): TagRenderingConfigJson[] {
|
||||
if (renderingId.indexOf(".") < 0) {
|
||||
const found = SharedTagRenderings.SharedTagRenderingJson.get(renderingId)
|
||||
if(found === undefined){
|
||||
return []
|
||||
}
|
||||
return [found]
|
||||
}
|
||||
|
||||
const [layerId, id] = renderingId.split(".")
|
||||
const layer = AllKnownLayers.getSharedLayersJson().get(layerId)
|
||||
if (layer === undefined) {
|
||||
if (Utils.runningFromConsole) {
|
||||
// Probably generating the layer overview
|
||||
return <TagRenderingConfigJson[]>[{
|
||||
id: "dummy"
|
||||
}]
|
||||
}
|
||||
throw "Builtin layer " + layerId + " not found"
|
||||
}
|
||||
|
||||
const renderings = layer?.tagRenderings ?? []
|
||||
if (id === "*") {
|
||||
return <TagRenderingConfigJson[]>JSON.parse(JSON.stringify(renderings))
|
||||
}
|
||||
|
||||
const selectByGroup = id.startsWith("*")
|
||||
const expectedGroupName = id.substring(1)
|
||||
|
||||
const allValidValues = []
|
||||
for (const rendering of renderings) {
|
||||
if ((!selectByGroup && rendering["id"] === id) || (selectByGroup && rendering["group"] === expectedGroupName)) {
|
||||
const found = <TagRenderingConfigJson>JSON.parse(JSON.stringify(rendering))
|
||||
if (found.condition === undefined) {
|
||||
found.condition = layer.source.osmTags
|
||||
} else {
|
||||
found.condition = {and: [found.condition, layer.source.osmTags]}
|
||||
}
|
||||
allValidValues.push(found)
|
||||
}
|
||||
}
|
||||
if(allValidValues.length === 0){
|
||||
|
||||
throw `The rendering with id ${id} was not found in the builtin layer ${layerId}. Try one of ${Utils.NoNull(renderings.map(r => r["id"])).join(", ")}`
|
||||
}
|
||||
return allValidValues
|
||||
}
|
||||
|
||||
private static getSharedLayers(): Map<string, LayerConfig> {
|
||||
const sharedLayers = new Map<string, LayerConfig>();
|
||||
|
@ -75,41 +127,4 @@ export default class AllKnownLayers {
|
|||
return sharedLayers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the appropriate tagRenderingJSON
|
||||
* Allows to steal them from other layers.
|
||||
* This will add the tags of the layer to the configuration though!
|
||||
* @param renderingId
|
||||
*/
|
||||
static getTagRendering(renderingId: string): TagRenderingConfigJson {
|
||||
if(renderingId.indexOf(".") < 0){
|
||||
return SharedTagRenderings.SharedTagRenderingJson.get(renderingId)
|
||||
}
|
||||
|
||||
const [layerId, id] = renderingId.split(".")
|
||||
const layer = AllKnownLayers.getSharedLayersJson().get(layerId)
|
||||
if(layer === undefined){
|
||||
if(Utils.runningFromConsole){
|
||||
// Probably generating the layer overview
|
||||
return <TagRenderingConfigJson> {
|
||||
id: "dummy"
|
||||
}
|
||||
}
|
||||
throw "Builtin layer "+layerId+" not found"
|
||||
}
|
||||
const renderings = layer?.tagRenderings ?? []
|
||||
for (const rendering of renderings) {
|
||||
if(rendering["id"] === id){
|
||||
const found = <TagRenderingConfigJson> JSON.parse(JSON.stringify(rendering))
|
||||
if(found.condition === undefined){
|
||||
found.condition = layer.source.osmTags
|
||||
}else{
|
||||
found.condition = {and: [found.condition, layer.source.osmTags]}
|
||||
}
|
||||
return found
|
||||
}
|
||||
}
|
||||
throw `The rendering with id ${id} was not found in the builtin layer ${layerId}. Try one of ${Utils.NoNull(renderings.map(r => r["id"])).join(", ")}`
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -65,6 +65,7 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL
|
|||
continue
|
||||
}
|
||||
|
||||
|
||||
const leafletLayer: () => TileLayer = () => AvailableBaseLayersImplementation.CreateBackgroundLayer(
|
||||
props.id,
|
||||
props.name,
|
||||
|
@ -83,7 +84,7 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL
|
|||
min_zoom: props.min_zoom ?? 1,
|
||||
name: props.name,
|
||||
layer: leafletLayer,
|
||||
feature: layer,
|
||||
feature: layer.geometry !== null ? layer : null,
|
||||
isBest: props.best ?? false,
|
||||
category: props.category
|
||||
});
|
||||
|
@ -96,14 +97,14 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL
|
|||
X; // Import X to make sure the namespace is not optimized away
|
||||
function l(id: string, name: string): BaseLayer {
|
||||
try {
|
||||
const layer: any = () => L.tileLayer.provider(id, undefined);
|
||||
const layer: any = L.tileLayer.provider(id, undefined);
|
||||
return {
|
||||
feature: null,
|
||||
id: id,
|
||||
name: name,
|
||||
layer: layer,
|
||||
min_zoom: layer.minzoom,
|
||||
max_zoom: layer.maxzoom,
|
||||
layer: () => L.tileLayer.provider(id, undefined),
|
||||
min_zoom: 1,
|
||||
max_zoom: layer.options.maxZoom,
|
||||
category: "osmbasedmap",
|
||||
isBest: false
|
||||
}
|
||||
|
@ -114,7 +115,6 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL
|
|||
}
|
||||
|
||||
const layers = [
|
||||
l("CyclOSM", "CyclOSM - A bicycle oriented map"),
|
||||
l("Stamen.TonerLite", "Toner Lite (by Stamen)"),
|
||||
l("Stamen.TonerBackground", "Toner Background - no labels (by Stamen)"),
|
||||
l("Stamen.Watercolor", "Watercolor (by Stamen)"),
|
||||
|
@ -195,35 +195,18 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL
|
|||
}
|
||||
|
||||
public AvailableLayersAt(location: UIEventSource<Loc>): UIEventSource<BaseLayer[]> {
|
||||
const source = location.map(
|
||||
return UIEventSource.ListStabilized(location.map(
|
||||
(currentLocation) => {
|
||||
|
||||
if (currentLocation === undefined) {
|
||||
return this.layerOverview;
|
||||
}
|
||||
|
||||
const currentLayers = source?.data; // A bit unorthodox - I know
|
||||
const newLayers = this.CalculateAvailableLayersAt(currentLocation?.lon, currentLocation?.lat);
|
||||
|
||||
if (currentLayers === undefined) {
|
||||
return newLayers;
|
||||
}
|
||||
if (newLayers.length !== currentLayers.length) {
|
||||
return newLayers;
|
||||
}
|
||||
for (let i = 0; i < newLayers.length; i++) {
|
||||
if (newLayers[i].name !== currentLayers[i].name) {
|
||||
return newLayers;
|
||||
}
|
||||
}
|
||||
|
||||
return currentLayers;
|
||||
});
|
||||
return source;
|
||||
return this.CalculateAvailableLayersAt(currentLocation?.lon, currentLocation?.lat);
|
||||
}));
|
||||
}
|
||||
|
||||
public SelectBestLayerAccordingTo(location: UIEventSource<Loc>, preferedCategory: UIEventSource<string | string[]>): UIEventSource<BaseLayer> {
|
||||
return this.AvailableLayersAt(location).map(available => {
|
||||
return this.AvailableLayersAt(location)
|
||||
.map(available => {
|
||||
// First float all 'best layers' to the top
|
||||
available.sort((a, b) => {
|
||||
if (a.isBest && b.isBest) {
|
||||
|
@ -264,9 +247,10 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL
|
|||
)
|
||||
}
|
||||
return available[0]
|
||||
})
|
||||
}, [preferedCategory])
|
||||
}
|
||||
|
||||
|
||||
private CalculateAvailableLayersAt(lon: number, lat: number): BaseLayer[] {
|
||||
const availableLayers = [this.osmCarto]
|
||||
const globalLayers = [];
|
||||
|
|
|
@ -70,6 +70,7 @@ export default class GeoLocationHandler extends VariableUiElement {
|
|||
|
||||
constructor(
|
||||
state: {
|
||||
selectedElement: UIEventSource<any>;
|
||||
currentUserLocation: FeatureSource,
|
||||
leafletMap: UIEventSource<any>,
|
||||
layoutToUse: LayoutConfig,
|
||||
|
@ -183,7 +184,8 @@ export default class GeoLocationHandler extends VariableUiElement {
|
|||
|
||||
const latLonGiven = QueryParameters.wasInitialized("lat") && QueryParameters.wasInitialized("lon")
|
||||
|
||||
this.init(false, !latLonGiven && state.featureSwitchGeolocation.data);
|
||||
const doAutoZoomToLocation = !latLonGiven && state.featureSwitchGeolocation.data && state.selectedElement.data !== undefined
|
||||
this.init(false, doAutoZoomToLocation);
|
||||
|
||||
isLocked.addCallbackAndRunD(isLocked => {
|
||||
if (isLocked) {
|
||||
|
|
|
@ -10,6 +10,7 @@ import RelationsTracker from "../Osm/RelationsTracker";
|
|||
import {BBox} from "../BBox";
|
||||
import Loc from "../../Models/Loc";
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
||||
import AllKnownLayers from "../../Customizations/AllKnownLayers";
|
||||
|
||||
|
||||
export default class OverpassFeatureSource implements FeatureSource {
|
||||
|
@ -121,6 +122,9 @@ export default class OverpassFeatureSource implements FeatureSource {
|
|||
if (typeof (layer) === "string") {
|
||||
throw "A layer was not expanded!"
|
||||
}
|
||||
if(AllKnownLayers.priviliged_layers.indexOf(layer.id) >= 0){
|
||||
continue
|
||||
}
|
||||
if (this.state.locationControl.data.zoom < layer.minzoom) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -23,7 +23,6 @@ export default class DetermineLayout {
|
|||
const layoutFromBase64 = decodeURIComponent(loadCustomThemeParam.data);
|
||||
|
||||
if (layoutFromBase64.startsWith("http")) {
|
||||
// The userLayout is actually an url
|
||||
const layout = await DetermineLayout.LoadRemoteTheme(layoutFromBase64)
|
||||
return [layout, undefined]
|
||||
}
|
||||
|
@ -114,6 +113,7 @@ export default class DetermineLayout {
|
|||
if (hash === undefined || hash.length < 10) {
|
||||
DetermineLayout.ShowErrorOnCustomTheme("Could not load a theme from the hash", new FixedUiElement("Hash does not contain data"))
|
||||
}
|
||||
this.ShowErrorOnCustomTheme("Could not parse the hash", new FixedUiElement(e))
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,7 +90,7 @@ export class ExtraFunction {
|
|||
private static readonly DistanceToFunc = new ExtraFunction(
|
||||
{
|
||||
name: "distanceTo",
|
||||
doc: "Calculates the distance between the feature and a specified point in kilometer. The input should either be a pair of coordinates, a geojson feature or the ID of an object",
|
||||
doc: "Calculates the distance between the feature and a specified point in meter. The input should either be a pair of coordinates, a geojson feature or the ID of an object",
|
||||
args: ["feature OR featureID OR longitude", "undefined OR latitude"]
|
||||
},
|
||||
(featuresPerLayer, feature) => {
|
||||
|
|
|
@ -3,56 +3,126 @@
|
|||
*
|
||||
* Technically, more an Actor then a featuresource, but it fits more neatly this ay
|
||||
*/
|
||||
import {FeatureSourceForLayer} from "../FeatureSource";
|
||||
import FeatureSource, {Tiled} from "../FeatureSource";
|
||||
import {Tiles} from "../../../Models/TileRange";
|
||||
import {IdbLocalStorage} from "../../Web/IdbLocalStorage";
|
||||
import {UIEventSource} from "../../UIEventSource";
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
|
||||
import {BBox} from "../../BBox";
|
||||
import SimpleFeatureSource from "../Sources/SimpleFeatureSource";
|
||||
import FilteredLayer from "../../../Models/FilteredLayer";
|
||||
import Loc from "../../../Models/Loc";
|
||||
|
||||
export default class SaveTileToLocalStorageActor {
|
||||
public static readonly storageKey: string = "cached-features";
|
||||
public static readonly formatVersion: string = "2"
|
||||
private readonly visitedTiles: UIEventSource<Map<number, Date>>
|
||||
private readonly _layer: LayerConfig;
|
||||
private readonly _flayer: FilteredLayer
|
||||
private readonly initializeTime = new Date()
|
||||
|
||||
constructor(source: FeatureSourceForLayer, tileIndex: number) {
|
||||
constructor(layer: FilteredLayer) {
|
||||
this._flayer = layer
|
||||
this._layer = layer.layerDef
|
||||
this.visitedTiles = IdbLocalStorage.Get("visited_tiles_" + this._layer.id,
|
||||
{defaultValue: new Map<number, Date>(),})
|
||||
this.visitedTiles.stabilized(100).addCallbackAndRunD(tiles => {
|
||||
for (const key of Array.from(tiles.keys())) {
|
||||
const tileFreshness = tiles.get(key)
|
||||
|
||||
source.features.addCallbackAndRunD(features => {
|
||||
const key = `${SaveTileToLocalStorageActor.storageKey}-${source.layer.layerDef.id}-${tileIndex}`
|
||||
const now = new Date()
|
||||
|
||||
try {
|
||||
if (features.length > 0) {
|
||||
localStorage.setItem(key, JSON.stringify(features));
|
||||
const toOld = (this.initializeTime.getTime() - tileFreshness.getTime()) > 1000 * this._layer.maxAgeOfCache
|
||||
if (toOld) {
|
||||
// Purge this tile
|
||||
this.SetIdb(key, undefined)
|
||||
console.debug("Purging tile", this._layer.id, key)
|
||||
tiles.delete(key)
|
||||
}
|
||||
// We _still_ write the time to know that this tile is empty!
|
||||
SaveTileToLocalStorageActor.MarkVisited(source.layer.layerDef.id, tileIndex, now)
|
||||
} catch (e) {
|
||||
console.warn("Could not save the features to local storage:", e)
|
||||
}
|
||||
this.visitedTiles.ping()
|
||||
return true;
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
public static MarkVisited(layerId: string, tileId: number, freshness: Date) {
|
||||
const key = `${SaveTileToLocalStorageActor.storageKey}-${layerId}-${tileId}`
|
||||
try {
|
||||
localStorage.setItem(key + "-time", JSON.stringify(freshness.getTime()))
|
||||
localStorage.setItem(key + "-format", SaveTileToLocalStorageActor.formatVersion)
|
||||
} catch (e) {
|
||||
console.error("Could not mark tile ", key, "as visited")
|
||||
public LoadTilesFromDisk(currentBounds: UIEventSource<BBox>, location: UIEventSource<Loc>,
|
||||
registerFreshness: (tileId: number, freshness: Date) => void,
|
||||
registerTile: ((src: FeatureSource & Tiled) => void)) {
|
||||
const self = this;
|
||||
const loadedTiles = new Set<number>()
|
||||
this.visitedTiles.addCallbackD(tiles => {
|
||||
if (tiles.size === 0) {
|
||||
// We don't do anything yet as probably not yet loaded from disk
|
||||
// We'll unregister later on
|
||||
return;
|
||||
}
|
||||
currentBounds.addCallbackAndRunD(bbox => {
|
||||
|
||||
if(self._layer.minzoomVisible > location.data.zoom){
|
||||
// Not enough zoom
|
||||
return;
|
||||
}
|
||||
|
||||
public static poison(layers: string[], lon: number, lat: number) {
|
||||
// Iterate over all available keys in the local storage, check which are needed and fresh enough
|
||||
for (const key of Array.from(tiles.keys())) {
|
||||
const tileFreshness = tiles.get(key)
|
||||
if (tileFreshness > self.initializeTime) {
|
||||
// This tile is loaded by another source
|
||||
continue
|
||||
}
|
||||
|
||||
registerFreshness(key, tileFreshness)
|
||||
const tileBbox = BBox.fromTileIndex(key)
|
||||
if (!bbox.overlapsWith(tileBbox)) {
|
||||
continue;
|
||||
}
|
||||
if (loadedTiles.has(key)) {
|
||||
// Already loaded earlier
|
||||
continue
|
||||
}
|
||||
loadedTiles.add(key)
|
||||
this.GetIdb(key).then((features: { feature: any, freshness: Date }[]) => {
|
||||
console.debug("Loaded tile " + self._layer.id + "_" + key + " from disk")
|
||||
const src = new SimpleFeatureSource(self._flayer, key, new UIEventSource<{ feature: any; freshness: Date }[]>(features))
|
||||
registerTile(src)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return true; // Remove the callback
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
public addTile(tile: FeatureSource & Tiled) {
|
||||
const self = this
|
||||
tile.features.addCallbackAndRunD(features => {
|
||||
const now = new Date()
|
||||
|
||||
if (features.length > 0) {
|
||||
self.SetIdb(tile.tileIndex, features)
|
||||
}
|
||||
// We _still_ write the time to know that this tile is empty!
|
||||
this.MarkVisited(tile.tileIndex, now)
|
||||
})
|
||||
}
|
||||
|
||||
public poison(lon: number, lat: number) {
|
||||
for (let z = 0; z < 25; z++) {
|
||||
|
||||
const {x, y} = Tiles.embedded_tile(lat, lon, z)
|
||||
const tileId = Tiles.tile_index(z, x, y)
|
||||
|
||||
for (const layerId of layers) {
|
||||
|
||||
const key = `${SaveTileToLocalStorageActor.storageKey}-${layerId}-${tileId}`
|
||||
localStorage.removeItem(key + "-time");
|
||||
localStorage.removeItem(key + "-format")
|
||||
localStorage.removeItem(key)
|
||||
}
|
||||
this.visitedTiles.data.delete(tileId)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public MarkVisited(tileId: number, freshness: Date) {
|
||||
this.visitedTiles.data.set(tileId, freshness)
|
||||
this.visitedTiles.ping()
|
||||
}
|
||||
|
||||
private SetIdb(tileIndex, data) {
|
||||
IdbLocalStorage.SetDirectly(this._layer.id + "_" + tileIndex, data)
|
||||
}
|
||||
|
||||
private GetIdb(tileIndex) {
|
||||
return IdbLocalStorage.GetDirectly(this._layer.id + "_" + tileIndex)
|
||||
}
|
||||
}
|
|
@ -5,15 +5,12 @@ 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";
|
||||
import TiledFromLocalStorageSource from "./TiledFeatureSource/TiledFromLocalStorageSource";
|
||||
import SaveTileToLocalStorageActor from "./Actors/SaveTileToLocalStorageActor";
|
||||
import DynamicGeoJsonTileSource from "./TiledFeatureSource/DynamicGeoJsonTileSource";
|
||||
import {TileHierarchyMerger} from "./TiledFeatureSource/TileHierarchyMerger";
|
||||
|
@ -22,11 +19,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 +47,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>;
|
||||
|
||||
|
@ -72,33 +56,15 @@ export default class FeaturePipeline {
|
|||
private readonly oldestAllowedDate: Date;
|
||||
private readonly osmSourceZoomLevel
|
||||
|
||||
private readonly localStorageSavers = new Map<string, SaveTileToLocalStorageActor>()
|
||||
|
||||
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
|
||||
const expiryInSeconds = Math.min(...state.layoutToUse.layers.map(l => l.maxAgeOfCache))
|
||||
for (const layer of state.layoutToUse.layers) {
|
||||
TiledFromLocalStorageSource.cleanCacheForLayer(layer)
|
||||
}
|
||||
this.oldestAllowedDate = new Date(new Date().getTime() - expiryInSeconds);
|
||||
this.osmSourceZoomLevel = state.osmApiTileSize.data;
|
||||
const useOsmApi = state.locationControl.map(l => l.zoom > (state.overpassMaxZoom.data ?? 12))
|
||||
|
@ -109,7 +75,7 @@ export default class FeaturePipeline {
|
|||
.map(ch => ch.changes)
|
||||
.filter(coor => coor["lat"] !== undefined && coor["lon"] !== undefined)
|
||||
.forEach(coor => {
|
||||
SaveTileToLocalStorageActor.poison(state.layoutToUse.layers.map(l => l.id), coor["lon"], coor["lat"])
|
||||
state.layoutToUse.layers.forEach(l => self.localStorageSavers.get(l.id).poison(coor["lon"], coor["lat"]))
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -138,7 +104,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,30 +134,37 @@ 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
|
||||
}
|
||||
|
||||
const localTileSaver = new SaveTileToLocalStorageActor(filteredLayer)
|
||||
this.localStorageSavers.set(filteredLayer.layerDef.id, localTileSaver)
|
||||
|
||||
if (source.geojsonSource === undefined) {
|
||||
// This is an OSM layer
|
||||
// We load the cached values and register them
|
||||
// Getting data from upstream happens a bit lower
|
||||
new TiledFromLocalStorageSource(filteredLayer,
|
||||
(src) => {
|
||||
new RegisteringAllFromFeatureSourceActor(src)
|
||||
hierarchy.registerTile(src);
|
||||
src.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(src))
|
||||
}, state)
|
||||
|
||||
TiledFromLocalStorageSource.GetFreshnesses(id).forEach((value, key) => {
|
||||
self.freshnesses.get(id).addTileLoad(key, value)
|
||||
})
|
||||
localTileSaver.LoadTilesFromDisk(
|
||||
state.currentBounds, state.locationControl,
|
||||
(tileIndex, freshness) => self.freshnesses.get(id).addTileLoad(tileIndex, freshness),
|
||||
(tile) => {
|
||||
new RegisteringAllFromFeatureSourceActor(tile)
|
||||
hierarchy.registerTile(tile);
|
||||
tile.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(tile))
|
||||
}
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
|
@ -237,7 +210,7 @@ export default class FeaturePipeline {
|
|||
handleTile: tile => {
|
||||
new RegisteringAllFromFeatureSourceActor(tile)
|
||||
if (tile.layer.layerDef.maxAgeOfCache > 0) {
|
||||
new SaveTileToLocalStorageActor(tile, tile.tileIndex)
|
||||
self.localStorageSavers.get(tile.layer.layerDef.id).addTile(tile)
|
||||
}
|
||||
perLayerHierarchy.get(tile.layer.layerDef.id).registerTile(tile)
|
||||
tile.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(tile))
|
||||
|
@ -246,10 +219,17 @@ export default class FeaturePipeline {
|
|||
state: state,
|
||||
markTileVisited: (tileId) =>
|
||||
state.filteredLayers.data.forEach(flayer => {
|
||||
if (flayer.layerDef.maxAgeOfCache > 0) {
|
||||
SaveTileToLocalStorageActor.MarkVisited(flayer.layerDef.id, tileId, new Date())
|
||||
const layer = flayer.layerDef
|
||||
if (layer.maxAgeOfCache > 0) {
|
||||
const saver = self.localStorageSavers.get(layer.id)
|
||||
if(saver === undefined){
|
||||
console.warn("No local storage saver found for ", layer.id)
|
||||
}else{
|
||||
|
||||
saver.MarkVisited(tileId, new Date())
|
||||
}
|
||||
self.freshnesses.get(flayer.layerDef.id).addTileLoad(tileId, new Date())
|
||||
}
|
||||
self.freshnesses.get(layer.id).addTileLoad(tileId, new Date())
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -279,10 +259,8 @@ export default class FeaturePipeline {
|
|||
maxFeatureCount: state.layoutToUse.clustering.minNeededElements,
|
||||
maxZoomLevel: state.layoutToUse.clustering.maxZoom,
|
||||
registerTile: (tile) => {
|
||||
// We save the tile data for the given layer to local storage
|
||||
if (source.layer.layerDef.source.geojsonSource === undefined || source.layer.layerDef.source.isOsmCacheLayer == true) {
|
||||
new SaveTileToLocalStorageActor(tile, tile.tileIndex)
|
||||
}
|
||||
// We save the tile data for the given layer to local storage - data sourced from overpass
|
||||
self.localStorageSavers.get(tile.layer.layerDef.id).addTile(tile)
|
||||
perLayerHierarchy.get(source.layer.layerDef.id).registerTile(new RememberingSource(tile))
|
||||
tile.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(tile))
|
||||
|
||||
|
@ -444,7 +422,7 @@ export default class FeaturePipeline {
|
|||
const tileIndex = Tiles.tile_index(paddedToZoomLevel, x, y)
|
||||
downloadedLayers.forEach(layer => {
|
||||
self.freshnesses.get(layer.id).addTileLoad(tileIndex, date)
|
||||
SaveTileToLocalStorageActor.MarkVisited(layer.id, tileIndex, date)
|
||||
self.localStorageSavers.get(layer.id).MarkVisited(tileIndex, date)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -73,28 +73,6 @@ export default class RenderingMultiPlexerFeatureSource {
|
|||
|
||||
}
|
||||
|
||||
|
||||
if (feat.geometry.type === "MultiLineString") {
|
||||
// Multilinestrings get a special treatment: we split them into their subparts before rendering
|
||||
const lineList: [number, number][][] = feat.geometry.coordinates
|
||||
|
||||
for (let i1 = 0; i1 < lineList.length; i1++) {
|
||||
const coordinates = lineList[i1];
|
||||
|
||||
for (let i = 0; i < lineRenderObjects.length; i++) {
|
||||
const orig = {
|
||||
...feat,
|
||||
lineRenderingIndex: i,
|
||||
multiLineStringIndex: i1
|
||||
}
|
||||
orig.geometry.coordinates = coordinates
|
||||
orig.geometry.type = "LineString"
|
||||
withIndex.push(orig)
|
||||
}
|
||||
}
|
||||
|
||||
}else{
|
||||
|
||||
// AT last, add it 'as is' to what we should render
|
||||
for (let i = 0; i < lineRenderObjects.length; i++) {
|
||||
withIndex.push({
|
||||
|
@ -102,8 +80,6 @@ export default class RenderingMultiPlexerFeatureSource {
|
|||
lineRenderingIndex: i
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,133 +0,0 @@
|
|||
import FilteredLayer from "../../../Models/FilteredLayer";
|
||||
import {FeatureSourceForLayer, Tiled} from "../FeatureSource";
|
||||
import {UIEventSource} from "../../UIEventSource";
|
||||
import TileHierarchy from "./TileHierarchy";
|
||||
import SaveTileToLocalStorageActor from "../Actors/SaveTileToLocalStorageActor";
|
||||
import {Tiles} from "../../../Models/TileRange";
|
||||
import {BBox} from "../../BBox";
|
||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
|
||||
|
||||
export default class TiledFromLocalStorageSource implements TileHierarchy<FeatureSourceForLayer & Tiled> {
|
||||
public readonly loadedTiles: Map<number, FeatureSourceForLayer & Tiled> = new Map<number, FeatureSourceForLayer & Tiled>();
|
||||
private readonly layer: FilteredLayer;
|
||||
private readonly handleFeatureSource: (src: FeatureSourceForLayer & Tiled, index: number) => void;
|
||||
private readonly undefinedTiles: Set<number>;
|
||||
|
||||
constructor(layer: FilteredLayer,
|
||||
handleFeatureSource: (src: FeatureSourceForLayer & Tiled, index: number) => void,
|
||||
state: {
|
||||
currentBounds: UIEventSource<BBox>
|
||||
}) {
|
||||
this.layer = layer;
|
||||
this.handleFeatureSource = handleFeatureSource;
|
||||
|
||||
|
||||
this.undefinedTiles = new Set<number>()
|
||||
const prefix = SaveTileToLocalStorageActor.storageKey + "-" + layer.layerDef.id + "-"
|
||||
const knownTiles: number[] = Object.keys(localStorage)
|
||||
.filter(key => {
|
||||
return key.startsWith(prefix) && !key.endsWith("-time") && !key.endsWith("-format");
|
||||
})
|
||||
.map(key => {
|
||||
return Number(key.substring(prefix.length));
|
||||
})
|
||||
.filter(i => !isNaN(i))
|
||||
|
||||
console.debug("Layer", layer.layerDef.id, "has following tiles in available in localstorage", knownTiles.map(i => Tiles.tile_from_index(i).join("/")).join(", "))
|
||||
for (const index of knownTiles) {
|
||||
|
||||
const prefix = SaveTileToLocalStorageActor.storageKey + "-" + layer.layerDef.id + "-" + index;
|
||||
const version = localStorage.getItem(prefix + "-format")
|
||||
if (version === undefined || version !== SaveTileToLocalStorageActor.formatVersion) {
|
||||
// Invalid version! Remove this tile from local storage
|
||||
localStorage.removeItem(prefix)
|
||||
localStorage.removeItem(prefix + "-time")
|
||||
localStorage.removeItem(prefix + "-format")
|
||||
this.undefinedTiles.add(index)
|
||||
console.log("Dropped old format tile", prefix)
|
||||
}
|
||||
}
|
||||
|
||||
const self = this
|
||||
state.currentBounds.map(bounds => {
|
||||
|
||||
if (bounds === undefined) {
|
||||
return;
|
||||
}
|
||||
for (const knownTile of knownTiles) {
|
||||
|
||||
if (this.loadedTiles.has(knownTile)) {
|
||||
continue;
|
||||
}
|
||||
if (this.undefinedTiles.has(knownTile)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!bounds.overlapsWith(BBox.fromTileIndex(knownTile))) {
|
||||
continue;
|
||||
}
|
||||
self.loadTile(knownTile)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
public static GetFreshnesses(layerId: string): Map<number, Date> {
|
||||
const prefix = SaveTileToLocalStorageActor.storageKey + "-" + layerId + "-"
|
||||
const freshnesses = new Map<number, Date>()
|
||||
for (const key of Object.keys(localStorage)) {
|
||||
if (!(key.startsWith(prefix) && key.endsWith("-time"))) {
|
||||
continue
|
||||
}
|
||||
const index = Number(key.substring(prefix.length, key.length - "-time".length))
|
||||
const time = Number(localStorage.getItem(key))
|
||||
const freshness = new Date()
|
||||
freshness.setTime(time)
|
||||
freshnesses.set(index, freshness)
|
||||
}
|
||||
return freshnesses
|
||||
}
|
||||
|
||||
static cleanCacheForLayer(layer: LayerConfig) {
|
||||
const now = new Date()
|
||||
const prefix = SaveTileToLocalStorageActor.storageKey + "-" + layer.id + "-"
|
||||
console.log("Cleaning tiles of ", prefix, "with max age", layer.maxAgeOfCache)
|
||||
for (const key of Object.keys(localStorage)) {
|
||||
if (!(key.startsWith(prefix) && key.endsWith("-time"))) {
|
||||
continue
|
||||
}
|
||||
const index = Number(key.substring(prefix.length, key.length - "-time".length))
|
||||
const time = Number(localStorage.getItem(key))
|
||||
const timeDiff = (now.getTime() - time) / 1000
|
||||
|
||||
if (timeDiff >= layer.maxAgeOfCache) {
|
||||
const k = prefix + index;
|
||||
localStorage.removeItem(k)
|
||||
localStorage.removeItem(k + "-format")
|
||||
localStorage.removeItem(k + "-time")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private loadTile(neededIndex: number) {
|
||||
try {
|
||||
const key = SaveTileToLocalStorageActor.storageKey + "-" + this.layer.layerDef.id + "-" + neededIndex
|
||||
const data = localStorage.getItem(key)
|
||||
const features = JSON.parse(data)
|
||||
const src = {
|
||||
layer: this.layer,
|
||||
features: new UIEventSource<{ feature: any; freshness: Date }[]>(features),
|
||||
name: "FromLocalStorage(" + key + ")",
|
||||
tileIndex: neededIndex,
|
||||
bbox: BBox.fromTileIndex(neededIndex)
|
||||
}
|
||||
this.handleFeatureSource(src, neededIndex)
|
||||
this.loadedTiles.set(neededIndex, src)
|
||||
} catch (e) {
|
||||
console.error("Could not load data tile from local storage due to", e)
|
||||
this.undefinedTiles.add(neededIndex)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -30,12 +30,12 @@ export class GeoOperations {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the distance between the two points in kilometers
|
||||
* Returns the distance between the two points in meters
|
||||
* @param lonlat0
|
||||
* @param lonlat1
|
||||
*/
|
||||
static distanceBetween(lonlat0: [number, number], lonlat1: [number, number]) {
|
||||
return turf.distance(lonlat0, lonlat1)
|
||||
return turf.distance(lonlat0, lonlat1) * 1000
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -111,12 +111,12 @@ export default class CreateNewNodeAction extends OsmChangeAction {
|
|||
// We check that it isn't close to an already existing point
|
||||
let reusedPointId = undefined;
|
||||
const prev = <[number, number]>geojson.geometry.coordinates[index]
|
||||
if (GeoOperations.distanceBetween(prev, <[number, number]>projected.geometry.coordinates) * 1000 < this._reusePointDistance) {
|
||||
if (GeoOperations.distanceBetween(prev, <[number, number]>projected.geometry.coordinates) < this._reusePointDistance) {
|
||||
// We reuse this point instead!
|
||||
reusedPointId = this._snapOnto.nodes[index]
|
||||
}
|
||||
const next = <[number, number]>geojson.geometry.coordinates[index + 1]
|
||||
if (GeoOperations.distanceBetween(next, <[number, number]>projected.geometry.coordinates) * 1000 < this._reusePointDistance) {
|
||||
if (GeoOperations.distanceBetween(next, <[number, number]>projected.geometry.coordinates) < this._reusePointDistance) {
|
||||
// We reuse this point instead!
|
||||
reusedPointId = this._snapOnto.nodes[index + 1]
|
||||
}
|
||||
|
|
|
@ -225,7 +225,7 @@ export default class CreateWayWithPointReuseAction extends OsmChangeAction {
|
|||
const coor = coordinates[i]
|
||||
// Check closeby (and probably identical) point further in the coordinate list, mark them as duplicate
|
||||
for (let j = i + 1; j < coordinates.length; j++) {
|
||||
if (1000 * GeoOperations.distanceBetween(coor, coordinates[j]) < 0.1) {
|
||||
if (GeoOperations.distanceBetween(coor, coordinates[j]) < 0.1) {
|
||||
coordinateInfo[j] = {
|
||||
lngLat: coor,
|
||||
identicalTo: i
|
||||
|
@ -244,7 +244,7 @@ export default class CreateWayWithPointReuseAction extends OsmChangeAction {
|
|||
}[] = []
|
||||
for (const node of allNodes) {
|
||||
const center = node.geometry.coordinates
|
||||
const d = 1000 * GeoOperations.distanceBetween(coor, center)
|
||||
const d = GeoOperations.distanceBetween(coor, center)
|
||||
if (d > maxDistance) {
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction {
|
|||
continue
|
||||
}
|
||||
for (let j = i + 1; j < coordinates.length; j++) {
|
||||
const d = 1000 * GeoOperations.distanceBetween(coordinates[i], coordinates[j])
|
||||
const d = GeoOperations.distanceBetween(coordinates[i], coordinates[j])
|
||||
if (d < 0.1) {
|
||||
console.log("Identical coordinates detected: ", i, " and ", j, ": ", coordinates[i], coordinates[j], "distance is", d)
|
||||
this.identicalTo[j] = i
|
||||
|
|
|
@ -172,7 +172,7 @@ export class Changes {
|
|||
return Math.min(...changedObjectCoordinates.map(coor =>
|
||||
Math.min(...recentLocationPoints.map(gpsPoint => {
|
||||
const otherCoor = GeoOperations.centerpointCoordinates(gpsPoint)
|
||||
return GeoOperations.distanceBetween(coor, otherCoor) * 1000
|
||||
return GeoOperations.distanceBetween(coor, otherCoor)
|
||||
}))
|
||||
))
|
||||
}
|
||||
|
@ -263,8 +263,13 @@ export class Changes {
|
|||
if(count === 0){
|
||||
return undefined
|
||||
}
|
||||
const maxD =maxDistances[i]
|
||||
let key = `change_within_${maxD}m`
|
||||
if(maxD === Number.MAX_VALUE){
|
||||
key = `change_over_${maxDistances[i - 1]}m`
|
||||
}
|
||||
return {
|
||||
key: "change_within_"+maxDistances[i]+"m",
|
||||
key ,
|
||||
value: count,
|
||||
aggregate:true
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ import {UIEventSource} from "../UIEventSource";
|
|||
import BaseLayer from "../../Models/BaseLayer";
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
||||
import AvailableBaseLayers from "../Actors/AvailableBaseLayers";
|
||||
import BackgroundLayerResetter from "../Actors/BackgroundLayerResetter";
|
||||
import Attribution from "../../UI/BigComponents/Attribution";
|
||||
import Minimap, {MinimapObj} from "../../UI/Base/Minimap";
|
||||
import {Tiles} from "../../Models/TileRange";
|
||||
|
@ -16,6 +15,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 +53,12 @@ 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.
|
||||
* Time in seconds
|
||||
*/
|
||||
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
|
||||
|
@ -76,34 +83,16 @@ export default class MapState extends UserRelatedState {
|
|||
|
||||
this.availableBackgroundLayers = AvailableBaseLayers.AvailableLayersAt(this.locationControl);
|
||||
|
||||
this.backgroundLayer = this.backgroundLayerId.map(
|
||||
(selectedId: string) => {
|
||||
if (selectedId === undefined) {
|
||||
return AvailableBaseLayers.osmCarto;
|
||||
}
|
||||
|
||||
let defaultLayer = AvailableBaseLayers.osmCarto
|
||||
const available = this.availableBackgroundLayers.data;
|
||||
for (const layer of available) {
|
||||
if (layer.id === selectedId) {
|
||||
return layer;
|
||||
if (this.backgroundLayerId.data === layer.id) {
|
||||
defaultLayer = layer;
|
||||
}
|
||||
}
|
||||
return AvailableBaseLayers.osmCarto;
|
||||
},
|
||||
[this.availableBackgroundLayers],
|
||||
(layer) => layer.id
|
||||
);
|
||||
|
||||
|
||||
/*
|
||||
* Selects a different background layer if the background layer has no coverage at the current location
|
||||
*/
|
||||
new BackgroundLayerResetter(
|
||||
this.backgroundLayer,
|
||||
this.locationControl,
|
||||
this.availableBackgroundLayers,
|
||||
this.layoutToUse.defaultBackgroundId
|
||||
);
|
||||
const self = this
|
||||
this.backgroundLayer = new UIEventSource<BaseLayer>(defaultLayer)
|
||||
this.backgroundLayer.addCallbackAndRunD(layer => self.backgroundLayerId.setData(layer.id))
|
||||
|
||||
const attr = new Attribution(
|
||||
this.locationControl,
|
||||
|
@ -181,49 +170,82 @@ export default class MapState extends UserRelatedState {
|
|||
}
|
||||
}
|
||||
|
||||
private initGpsLocation(){
|
||||
private initGpsLocation() {
|
||||
// Initialize the gps layer data. This is emtpy for now, the actual writing happens in the Geolocationhandler
|
||||
let gpsLayerDef: FilteredLayer = this.filteredLayers.data.filter(l => l.layerDef.id === "gps_location")[0]
|
||||
this.currentUserLocation = new SimpleFeatureSource(gpsLayerDef, Tiles.tile_index(0, 0, 0));
|
||||
}
|
||||
|
||||
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")
|
||||
private initUserLocationTrail() {
|
||||
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()) < 1000 * this.gpsLocationHistoryRetentionTime.data)
|
||||
features.ping()
|
||||
const self = this;
|
||||
let i = 0
|
||||
this.currentUserLocation.features.addCallbackAndRunD(([location]) => {
|
||||
if(location === undefined){
|
||||
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
|
||||
)
|
||||
let timeDiff = Number.MAX_VALUE // in seconds
|
||||
const olderLocation = features.data[features.data.length - 2]
|
||||
if (olderLocation !== undefined) {
|
||||
timeDiff = (new Date(previousLocation.freshness).getTime() - new Date(olderLocation.freshness).getTime()) / 1000
|
||||
}
|
||||
if (d < 20 && timeDiff < 60) {
|
||||
// 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() {
|
||||
|
@ -246,7 +268,7 @@ export default class MapState extends UserRelatedState {
|
|||
feature: {
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"id":"home",
|
||||
"id": "home",
|
||||
"user:home": "yes",
|
||||
"_lon": homeLonLat[0],
|
||||
"_lat": homeLonLat[1]
|
||||
|
@ -293,10 +315,7 @@ export default class MapState extends UserRelatedState {
|
|||
const filtersPerName = new Map<string, FilterConfig>()
|
||||
layer.filters.forEach(f => filtersPerName.set(f.id, f))
|
||||
const qp = QueryParameters.GetQueryParameter("filter-" + layer.id, "", "Filtering state for a layer")
|
||||
flayer.appliedFilters.map(filters => {
|
||||
filters = filters ?? []
|
||||
return filters.map(f => f.filter.id + "." + f.selected).join(",")
|
||||
}, [], textual => {
|
||||
flayer.appliedFilters.map(filters => (filters ?? []).map(f => f.filter.id + "." + f.selected).join(","), [], textual => {
|
||||
if (textual.length === 0) {
|
||||
return empty
|
||||
}
|
||||
|
|
|
@ -223,6 +223,7 @@ export class UIEventSource<T> {
|
|||
for (const callback of this._callbacks) {
|
||||
if (callback(this.data) === true) {
|
||||
// This callback wants to be deleted
|
||||
// Note: it has to return precisely true in order to avoid accidental deletions
|
||||
if (toDelete === undefined) {
|
||||
toDelete = [callback]
|
||||
} else {
|
||||
|
|
24
Logic/Web/IdbLocalStorage.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import {UIEventSource} from "../UIEventSource";
|
||||
import * as idb from "idb-keyval"
|
||||
/**
|
||||
* UIEventsource-wrapper around indexedDB key-value
|
||||
*/
|
||||
export class IdbLocalStorage {
|
||||
|
||||
|
||||
public static Get<T>(key: string, options: { defaultValue?: T }): UIEventSource<T>{
|
||||
const src = new UIEventSource<T>(options.defaultValue, "idb-local-storage:"+key)
|
||||
idb.get(key).then(v => src.setData(v ?? options.defaultValue))
|
||||
src.addCallback(v => idb.set(key, v))
|
||||
return src;
|
||||
|
||||
}
|
||||
|
||||
public static SetDirectly(key: string, value){
|
||||
idb.set(key, value)
|
||||
}
|
||||
|
||||
static GetDirectly(key: string) {
|
||||
return idb.get(key)
|
||||
}
|
||||
}
|
|
@ -2,9 +2,8 @@ import {Utils} from "../Utils";
|
|||
|
||||
export default class Constants {
|
||||
|
||||
public static vNumber = "0.12.7";
|
||||
public static vNumber = "0.12.10";
|
||||
public static ImgurApiKey = '7070e7167f0a25a'
|
||||
public static readonly mapillary_client_token_v3 = 'TXhLaWthQ1d4RUg0czVxaTVoRjFJZzowNDczNjUzNmIyNTQyYzI2'
|
||||
public static readonly mapillary_client_token_v4 = "MLY|4441509239301885|b40ad2d3ea105435bd40c7e76993ae85"
|
||||
|
||||
public static defaultOverpassUrls = [
|
||||
|
@ -51,7 +50,7 @@ export default class Constants {
|
|||
* If a user makes multiple changes, all these distances are put into multiple bins, depending on this distance.
|
||||
* For every bin, the totals are uploaded as metadata
|
||||
*/
|
||||
static distanceToChangeObjectBins = [25,50,100,500,1000,5000]
|
||||
static distanceToChangeObjectBins = [25,50,100,500,1000,5000, Number.MAX_VALUE]
|
||||
|
||||
private static isRetina(): boolean {
|
||||
if (Utils.runningFromConsole) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import LineRenderingConfigJson from "./Json/LineRenderingConfigJson";
|
||||
import PointRenderingConfig from "./PointRenderingConfig";
|
||||
|
||||
export default class LegacyJsonConvert {
|
||||
|
||||
|
@ -28,15 +29,17 @@ export default class LegacyJsonConvert {
|
|||
}
|
||||
}
|
||||
|
||||
if (config.mapRendering === undefined && config.id !== "sidewalks") {
|
||||
if (config.mapRendering === undefined) {
|
||||
config.mapRendering = []
|
||||
// This is a legacy format, lets create a pointRendering
|
||||
let location: ("point" | "centroid")[] = ["point"]
|
||||
let wayHandling: number = config["wayHandling"] ?? 0
|
||||
if (wayHandling !== 0) {
|
||||
location = ["point", "centroid"]
|
||||
}
|
||||
config.mapRendering = [
|
||||
{
|
||||
if(config["icon"] ?? config["label"] !== undefined){
|
||||
|
||||
const pointConfig = {
|
||||
icon: config["icon"],
|
||||
iconBadges: config["iconOverlays"],
|
||||
label: config["label"],
|
||||
|
@ -44,7 +47,9 @@ export default class LegacyJsonConvert {
|
|||
location,
|
||||
rotation: config["rotation"]
|
||||
}
|
||||
]
|
||||
config.mapRendering.push(pointConfig)
|
||||
}
|
||||
|
||||
|
||||
if (wayHandling !== 1) {
|
||||
const lineRenderConfig = <LineRenderingConfigJson>{
|
||||
|
@ -56,6 +61,9 @@ export default class LegacyJsonConvert {
|
|||
config.mapRendering.push(lineRenderConfig)
|
||||
}
|
||||
}
|
||||
if(config.mapRendering.length === 0){
|
||||
throw "Could not convert the legacy theme into a new theme: no renderings defined for layer "+config.id
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -42,6 +42,9 @@ export default class TagRenderingConfig {
|
|||
}[]
|
||||
|
||||
constructor(json: string | TagRenderingConfigJson, context?: string) {
|
||||
if (json === undefined) {
|
||||
throw "Initing a TagRenderingConfig with undefined in " + context;
|
||||
}
|
||||
|
||||
if (json === "questions") {
|
||||
// Very special value
|
||||
|
@ -55,14 +58,11 @@ export default class TagRenderingConfig {
|
|||
|
||||
|
||||
if (typeof json === "number") {
|
||||
this.render = Translations.T("" + json, context + ".render")
|
||||
return;
|
||||
json = ""+json
|
||||
}
|
||||
|
||||
|
||||
if (json === undefined) {
|
||||
throw "Initing a TagRenderingConfig with undefined in " + context;
|
||||
}
|
||||
|
||||
if (typeof json === "string") {
|
||||
this.render = Translations.T(json, context + ".render");
|
||||
this.multiAnswer = false;
|
||||
|
@ -75,6 +75,8 @@ export default class TagRenderingConfig {
|
|||
throw "Invalid ID in "+context+": an id can only contain [a-zA-Z0-0_-] as characters. The offending id is: "+this.id
|
||||
}
|
||||
|
||||
|
||||
|
||||
this.group = json.group ?? "";
|
||||
this.render = Translations.T(json.render, context + ".render");
|
||||
this.question = Translations.T(json.question, context + ".question");
|
||||
|
|
|
@ -7,8 +7,13 @@ export default class WithContextLoader {
|
|||
protected readonly _context: string;
|
||||
private readonly _json: any;
|
||||
|
||||
public static getKnownTagRenderings : ((id: string) => TagRenderingConfigJson)= function(id) {
|
||||
return SharedTagRenderings.SharedTagRenderingJson.get(id)
|
||||
public static getKnownTagRenderings : ((id: string) => TagRenderingConfigJson[])= function(id) {
|
||||
const found = SharedTagRenderings.SharedTagRenderingJson.get(id)
|
||||
if(found !== undefined){
|
||||
return [found]
|
||||
}else{
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
constructor(json: any, context: string) {
|
||||
|
@ -64,36 +69,59 @@ export default class WithContextLoader {
|
|||
}
|
||||
|
||||
const context = this._context
|
||||
const renderings: TagRenderingConfig[] = []
|
||||
options = options ?? {}
|
||||
if (options.prepConfig === undefined) {
|
||||
options.prepConfig = c => c
|
||||
}
|
||||
const preparedConfigs : TagRenderingConfigJson[] = []
|
||||
for (let i = 0; i < tagRenderings.length; i++) {
|
||||
let renderingJson = tagRenderings[i]
|
||||
if(renderingJson === "questions"){
|
||||
renderingJson = {
|
||||
id: "questions"
|
||||
}
|
||||
}
|
||||
if (typeof renderingJson === "string") {
|
||||
renderingJson = {builtin: renderingJson, override: undefined}
|
||||
}
|
||||
|
||||
if (renderingJson["builtin"] !== undefined) {
|
||||
if (renderingJson["builtin"] === undefined) {
|
||||
const patchedConfig = options.prepConfig(<TagRenderingConfigJson>renderingJson)
|
||||
preparedConfigs.push(patchedConfig)
|
||||
continue
|
||||
|
||||
}
|
||||
|
||||
|
||||
const renderingId = renderingJson["builtin"]
|
||||
let sharedJson = WithContextLoader.getKnownTagRenderings(renderingId)
|
||||
if (sharedJson === undefined) {
|
||||
let sharedJsons = []
|
||||
if(typeof renderingId === "string"){
|
||||
sharedJsons = WithContextLoader.getKnownTagRenderings(renderingId)
|
||||
}else{
|
||||
sharedJsons = [].concat( ...(<string[]>renderingId).map(id => WithContextLoader.getKnownTagRenderings(id) ) )
|
||||
}
|
||||
|
||||
if (sharedJsons.length === 0) {
|
||||
const keys = Array.from(SharedTagRenderings.SharedTagRenderingJson.keys());
|
||||
throw `Predefined tagRendering ${renderingId} not found in ${context}.\n Try one of ${keys.join(
|
||||
", "
|
||||
)}\n If you intent to output this text literally, use {\"render\": <your text>} instead"}`;
|
||||
}
|
||||
for (let sharedJson of sharedJsons) {
|
||||
if (renderingJson["override"] !== undefined) {
|
||||
sharedJson = Utils.Merge(renderingJson["override"], JSON.parse(JSON.stringify(sharedJson)))
|
||||
}
|
||||
renderingJson = sharedJson
|
||||
|
||||
const patchedConfig = options.prepConfig(<TagRenderingConfigJson>sharedJson)
|
||||
preparedConfigs.push(patchedConfig)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const patchedConfig = options.prepConfig(<TagRenderingConfigJson>renderingJson)
|
||||
|
||||
const tr = new TagRenderingConfig(patchedConfig, `${context}.tagrendering[${i}]`);
|
||||
const renderings: TagRenderingConfig[] = []
|
||||
for (let i = 0; i < preparedConfigs.length; i++){
|
||||
const preparedConfig = preparedConfigs[i];
|
||||
const tr = new TagRenderingConfig(preparedConfig, `${context}.tagrendering[${i}]`);
|
||||
if(options.readOnlyMode && tr.question !== undefined){
|
||||
throw "A question is defined for "+`${context}.tagrendering[${i}], but this is not allowed at this position - probably because this rendering is an icon, badge or label`
|
||||
}
|
||||
|
|
|
@ -13,7 +13,8 @@ export interface MinimapOptions {
|
|||
attribution?: BaseUIElement | boolean,
|
||||
onFullyLoaded?: (leaflet: L.Map) => void,
|
||||
leafletMap?: UIEventSource<any>,
|
||||
lastClickLocation?: UIEventSource<{ lat: number, lon: number }>
|
||||
lastClickLocation?: UIEventSource<{ lat: number, lon: number }>,
|
||||
addLayerControl?: boolean | false
|
||||
}
|
||||
|
||||
export interface MinimapObj {
|
||||
|
|
|
@ -10,6 +10,7 @@ import Minimap, {MinimapObj, MinimapOptions} from "./Minimap";
|
|||
import {BBox} from "../../Logic/BBox";
|
||||
import 'leaflet-polylineoffset'
|
||||
import {SimpleMapScreenshoter} from "leaflet-simple-map-screenshoter";
|
||||
import BackgroundMapSwitch from "../BigComponents/BackgroundMapSwitch";
|
||||
|
||||
export default class MinimapImplementation extends BaseUIElement implements MinimapObj {
|
||||
private static _nextId = 0;
|
||||
|
@ -24,6 +25,7 @@ export default class MinimapImplementation extends BaseUIElement implements Mini
|
|||
private readonly _attribution: BaseUIElement | boolean;
|
||||
private readonly _lastClickLocation: UIEventSource<{ lat: number; lon: number }>;
|
||||
private readonly _bounds: UIEventSource<BBox> | undefined;
|
||||
private readonly _addLayerControl: boolean;
|
||||
|
||||
private constructor(options: MinimapOptions) {
|
||||
super()
|
||||
|
@ -38,6 +40,7 @@ export default class MinimapImplementation extends BaseUIElement implements Mini
|
|||
this._onFullyLoaded = options.onFullyLoaded
|
||||
this._attribution = options.attribution
|
||||
this._lastClickLocation = options.lastClickLocation;
|
||||
this._addLayerControl = options.addLayerControl ?? false
|
||||
MinimapImplementation._nextId++
|
||||
|
||||
}
|
||||
|
@ -131,6 +134,17 @@ export default class MinimapImplementation extends BaseUIElement implements Mini
|
|||
});
|
||||
|
||||
resizeObserver.observe(div);
|
||||
|
||||
if (this._addLayerControl) {
|
||||
const switcher = new BackgroundMapSwitch({
|
||||
locationControl: this._location,
|
||||
backgroundLayer: this._background
|
||||
},
|
||||
this._background
|
||||
).SetClass("top-0 right-0 z-above-map absolute")
|
||||
wrapper.appendChild(switcher.ConstructElement())
|
||||
}
|
||||
|
||||
return wrapper;
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import {Utils} from "../Utils";
|
||||
import {UIEventSource} from "../Logic/UIEventSource";
|
||||
|
||||
/**
|
||||
* A thin wrapper around a html element, which allows to generate a HTML-element.
|
||||
|
@ -12,7 +11,6 @@ export default abstract class BaseUIElement {
|
|||
private clss: Set<string> = new Set<string>();
|
||||
private style: string;
|
||||
private _onClick: () => void;
|
||||
private _onHover: UIEventSource<boolean>;
|
||||
|
||||
public onClick(f: (() => void)) {
|
||||
this._onClick = f;
|
||||
|
@ -138,18 +136,6 @@ export default abstract class BaseUIElement {
|
|||
el.classList.add("pointer-events-none", "cursor-pointer");
|
||||
}
|
||||
|
||||
if (this._onHover !== undefined) {
|
||||
const self = this;
|
||||
el.addEventListener('mouseover', () => self._onHover.setData(true));
|
||||
el.addEventListener('mouseout', () => self._onHover.setData(false));
|
||||
}
|
||||
|
||||
if (this._onHover !== undefined) {
|
||||
const self = this;
|
||||
el.addEventListener('mouseover', () => self._onHover.setData(true));
|
||||
el.addEventListener('mouseout', () => self._onHover.setData(false));
|
||||
}
|
||||
|
||||
return el
|
||||
} catch (e) {
|
||||
const domExc = e as DOMException;
|
||||
|
|
196
UI/BigComponents/BackgroundMapSwitch.ts
Normal file
|
@ -0,0 +1,196 @@
|
|||
import Combine from "../Base/Combine";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import Loc from "../../Models/Loc";
|
||||
import Svg from "../../Svg";
|
||||
import Toggle from "../Input/Toggle";
|
||||
import BaseLayer from "../../Models/BaseLayer";
|
||||
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
import {GeoOperations} from "../../Logic/GeoOperations";
|
||||
|
||||
class SingleLayerSelectionButton extends Toggle {
|
||||
|
||||
public readonly activate: () => void
|
||||
|
||||
/**
|
||||
*
|
||||
* The SingeLayerSelectionButton also acts as an actor to keep the layers in check
|
||||
*
|
||||
* It works the following way:
|
||||
*
|
||||
* - It has a boolean state to indicate wether or not the button is active
|
||||
* - It keeps track of the available layers
|
||||
*/
|
||||
constructor(
|
||||
locationControl: UIEventSource<Loc>,
|
||||
options: {
|
||||
currentBackground: UIEventSource<BaseLayer>,
|
||||
preferredType: string,
|
||||
preferredLayer?: BaseLayer,
|
||||
notAvailable?: () => void
|
||||
}) {
|
||||
|
||||
|
||||
const prefered = options.preferredType
|
||||
const previousLayer = new UIEventSource(options.preferredLayer)
|
||||
|
||||
const unselected = SingleLayerSelectionButton.getIconFor(prefered)
|
||||
.SetClass("rounded-lg p-1 h-12 w-12 overflow-hidden subtle-background border-invisible")
|
||||
|
||||
const selected = SingleLayerSelectionButton.getIconFor(prefered)
|
||||
.SetClass("rounded-lg p-1 h-12 w-12 overflow-hidden subtle-background border-attention-catch")
|
||||
|
||||
|
||||
const available = AvailableBaseLayers
|
||||
.SelectBestLayerAccordingTo(locationControl, new UIEventSource<string | string[]>(options.preferredType))
|
||||
|
||||
let toggle: BaseUIElement = new Toggle(
|
||||
selected,
|
||||
unselected,
|
||||
options.currentBackground.map(bg => bg.category === options.preferredType)
|
||||
)
|
||||
|
||||
|
||||
super(
|
||||
toggle,
|
||||
undefined,
|
||||
available.map(av => av.category === options.preferredType)
|
||||
);
|
||||
|
||||
/**
|
||||
* Checks that the previous layer is still usable on the current location.
|
||||
* If not, clears the 'previousLayer'
|
||||
*/
|
||||
function checkPreviousLayer() {
|
||||
if (previousLayer.data === undefined) {
|
||||
return
|
||||
}
|
||||
if (previousLayer.data.feature === null || previousLayer.data.feature === undefined) {
|
||||
// Global layer
|
||||
return
|
||||
}
|
||||
const loc = locationControl.data
|
||||
if (!GeoOperations.inside([loc.lon, loc.lat], previousLayer.data.feature)) {
|
||||
// The previous layer is out of bounds
|
||||
previousLayer.setData(undefined)
|
||||
}
|
||||
}
|
||||
|
||||
unselected.onClick(() => {
|
||||
// Note: a check if 'available' has the correct type is not needed:
|
||||
// Unselected will _not_ be visible if availableBaseLayer has a wrong type!
|
||||
checkPreviousLayer()
|
||||
|
||||
previousLayer.setData(previousLayer.data ?? available.data)
|
||||
options.currentBackground.setData(previousLayer.data)
|
||||
})
|
||||
|
||||
|
||||
available.addCallbackAndRunD(availableLayer => {
|
||||
|
||||
if (previousLayer.data === undefined) {
|
||||
// PreviousLayer is unset -> we definitively weren't using this category -> no need to switch
|
||||
return;
|
||||
}
|
||||
if (options.currentBackground.data?.id !== previousLayer.data?.id) {
|
||||
// The previously used layer doesn't match the current layer -> no need to switch
|
||||
return;
|
||||
}
|
||||
|
||||
if (availableLayer.category === options.preferredType) {
|
||||
// Allright, we can set this different layer
|
||||
options.currentBackground.setData(availableLayer)
|
||||
previousLayer.setData(availableLayer)
|
||||
} else {
|
||||
// Uh oh - no correct layer is available... We pass the torch!
|
||||
if (options.notAvailable !== undefined) {
|
||||
options.notAvailable()
|
||||
} else {
|
||||
// Fallback to OSM carto
|
||||
options.currentBackground.setData(AvailableBaseLayers.osmCarto)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
options.currentBackground.addCallbackAndRunD(background => {
|
||||
if (background.category === options.preferredType) {
|
||||
previousLayer.setData(background)
|
||||
}
|
||||
})
|
||||
|
||||
this.activate = () => {
|
||||
checkPreviousLayer()
|
||||
if (available.data.category !== options.preferredType) {
|
||||
// This object can't help either - pass the torch!
|
||||
if (options.notAvailable !== undefined) {
|
||||
options.notAvailable()
|
||||
} else {
|
||||
// Fallback to OSM carto
|
||||
options.currentBackground.setData(AvailableBaseLayers.osmCarto)
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
previousLayer.setData(previousLayer.data ?? available.data)
|
||||
options.currentBackground.setData(previousLayer.data)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static getIconFor(type: string) {
|
||||
switch (type) {
|
||||
case "map":
|
||||
return Svg.generic_map_svg()
|
||||
case "photo":
|
||||
return Svg.satellite_svg()
|
||||
case "osmbasedmap":
|
||||
return Svg.osm_logo_svg()
|
||||
default:
|
||||
return Svg.generic_map_svg()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default class BackgroundMapSwitch extends Combine {
|
||||
|
||||
constructor(
|
||||
state: {
|
||||
locationControl: UIEventSource<Loc>,
|
||||
backgroundLayer: UIEventSource<BaseLayer>
|
||||
},
|
||||
currentBackground: UIEventSource<BaseLayer>,
|
||||
preferredCategory?: string
|
||||
) {
|
||||
const allowedCategories = ["osmbasedmap", "photo", "map"]
|
||||
|
||||
const previousLayer = state.backgroundLayer.data
|
||||
const buttons = []
|
||||
let activatePrevious: () => void = undefined
|
||||
for (const category of allowedCategories) {
|
||||
let preferredLayer = undefined
|
||||
if (previousLayer.category === category) {
|
||||
preferredLayer = previousLayer
|
||||
}
|
||||
|
||||
const button = new SingleLayerSelectionButton(
|
||||
state.locationControl,
|
||||
{
|
||||
preferredType: category,
|
||||
preferredLayer: preferredLayer,
|
||||
currentBackground: currentBackground,
|
||||
notAvailable: activatePrevious
|
||||
})
|
||||
activatePrevious = button.activate
|
||||
if (category === preferredCategory) {
|
||||
button.activate()
|
||||
}
|
||||
buttons.push(button)
|
||||
}
|
||||
|
||||
// Selects the initial map
|
||||
|
||||
super(buttons)
|
||||
this.SetClass("flex")
|
||||
}
|
||||
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
import Combine from "../Base/Combine";
|
||||
import Translations from "../i18n/Translations";
|
||||
import Attribution from "./Attribution";
|
||||
import State from "../../State";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||
import * as licenses from "../../assets/generated/license_info.json"
|
||||
|
@ -21,6 +19,8 @@ import Loc from "../../Models/Loc";
|
|||
import Toggle from "../Input/Toggle";
|
||||
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
|
||||
import Constants from "../../Models/Constants";
|
||||
import PrivacyPolicy from "./PrivacyPolicy";
|
||||
import ContributorCount from "../../Logic/ContributorCount";
|
||||
|
||||
/**
|
||||
* The attribution panel shown on mobile
|
||||
|
@ -35,7 +35,7 @@ export default class CopyrightPanel extends Combine {
|
|||
currentBounds: UIEventSource<BBox>,
|
||||
locationControl: UIEventSource<Loc>,
|
||||
osmConnection: OsmConnection
|
||||
}, contributions: UIEventSource<Map<string, number>>) {
|
||||
}) {
|
||||
|
||||
const t = Translations.t.general.attribution
|
||||
const layoutToUse = state.layoutToUse
|
||||
|
@ -102,6 +102,8 @@ export default class CopyrightPanel extends Combine {
|
|||
maintainer = Translations.t.general.attribution.themeBy.Subs({author: layoutToUse.maintainer})
|
||||
}
|
||||
|
||||
const contributions = new ContributorCount(state).Contributors
|
||||
|
||||
super([
|
||||
Translations.t.general.attribution.attributionContent,
|
||||
new FixedUiElement("MapComplete "+Constants.vNumber).SetClass("font-bold"),
|
||||
|
|
|
@ -17,6 +17,9 @@ import UserRelatedState from "../../Logic/State/UserRelatedState";
|
|||
import Loc from "../../Models/Loc";
|
||||
import BaseLayer from "../../Models/BaseLayer";
|
||||
import FilteredLayer from "../../Models/FilteredLayer";
|
||||
import CopyrightPanel from "./CopyrightPanel";
|
||||
import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline";
|
||||
import PrivacyPolicy from "./PrivacyPolicy";
|
||||
|
||||
export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
||||
|
||||
|
@ -29,6 +32,7 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
|||
featureSwitchShareScreen: UIEventSource<boolean>,
|
||||
featureSwitchMoreQuests: UIEventSource<boolean>,
|
||||
locationControl: UIEventSource<Loc>,
|
||||
featurePipeline: FeaturePipeline,
|
||||
backgroundLayer: UIEventSource<BaseLayer>,
|
||||
filteredLayers: UIEventSource<FilteredLayer[]>
|
||||
} & UserRelatedState) {
|
||||
|
@ -46,6 +50,7 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
|||
osmConnection: OsmConnection,
|
||||
featureSwitchShareScreen: UIEventSource<boolean>,
|
||||
featureSwitchMoreQuests: UIEventSource<boolean>,
|
||||
featurePipeline: FeaturePipeline,
|
||||
locationControl: UIEventSource<Loc>, backgroundLayer: UIEventSource<BaseLayer>, filteredLayers: UIEventSource<FilteredLayer[]>
|
||||
} & UserRelatedState,
|
||||
isShown: UIEventSource<boolean>):
|
||||
|
@ -55,16 +60,8 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
|||
|
||||
const tabs: { header: string | BaseUIElement, content: BaseUIElement }[] = [
|
||||
{header: `<img src='${state.layoutToUse.icon}'>`, content: welcome},
|
||||
{
|
||||
header: Svg.osm_logo_img,
|
||||
content: Translations.t.general.openStreetMapIntro.SetClass("link-underline")
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
if (state.featureSwitchShareScreen.data) {
|
||||
tabs.push({header: Svg.share_img, content: new ShareScreen(state)});
|
||||
}
|
||||
|
||||
if (state.featureSwitchMoreQuests.data) {
|
||||
tabs.push({
|
||||
|
@ -77,6 +74,31 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
if (state.featureSwitchShareScreen.data) {
|
||||
tabs.push({header: Svg.share_img, content: new ShareScreen(state)});
|
||||
}
|
||||
|
||||
const copyright = {
|
||||
header: Svg.copyright_svg(),
|
||||
content:
|
||||
new Combine(
|
||||
[
|
||||
Translations.t.general.openStreetMapIntro.SetClass("link-underline"),
|
||||
Translations.t.general.attribution.attributionTitle,
|
||||
new CopyrightPanel(state)
|
||||
|
||||
]
|
||||
)
|
||||
}
|
||||
tabs.push(copyright)
|
||||
|
||||
const privacy = {
|
||||
header: Svg.eye_svg(),
|
||||
content: new PrivacyPolicy()
|
||||
}
|
||||
tabs.push(privacy)
|
||||
|
||||
return tabs;
|
||||
}
|
||||
|
||||
|
@ -85,6 +107,7 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
|||
osmConnection: OsmConnection,
|
||||
featureSwitchShareScreen: UIEventSource<boolean>,
|
||||
featureSwitchMoreQuests: UIEventSource<boolean>,
|
||||
featurePipeline: FeaturePipeline,
|
||||
locationControl: UIEventSource<Loc>, backgroundLayer: UIEventSource<BaseLayer>, filteredLayers: UIEventSource<FilteredLayer[]>
|
||||
} & UserRelatedState, currentTab: UIEventSource<number>, isShown: UIEventSource<boolean>) {
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import Combine from "../Base/Combine";
|
||||
import ScrollableFullScreen from "../Base/ScrollableFullScreen";
|
||||
import Translations from "../i18n/Translations";
|
||||
import CopyrightPanel from "./CopyrightPanel";
|
||||
import ContributorCount from "../../Logic/ContributorCount";
|
||||
import Toggle from "../Input/Toggle";
|
||||
import MapControlButton from "../MapControlButton";
|
||||
import Svg from "../../Svg";
|
||||
|
@ -16,6 +14,7 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
|||
import FilteredLayer from "../../Models/FilteredLayer";
|
||||
import BaseLayer from "../../Models/BaseLayer";
|
||||
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
|
||||
import BackgroundMapSwitch from "./BackgroundMapSwitch";
|
||||
|
||||
export default class LeftControls extends Combine {
|
||||
|
||||
|
@ -38,23 +37,6 @@ export default class LeftControls extends Combine {
|
|||
copyrightViewIsOpened: UIEventSource<boolean>
|
||||
}) {
|
||||
|
||||
const toggledCopyright = new ScrollableFullScreen(
|
||||
() => Translations.t.general.attribution.attributionTitle.Clone(),
|
||||
() =>
|
||||
new CopyrightPanel(
|
||||
state,
|
||||
new ContributorCount(state).Contributors
|
||||
),
|
||||
"copyright",
|
||||
guiState.copyrightViewIsOpened
|
||||
);
|
||||
|
||||
const copyrightButton = new Toggle(
|
||||
toggledCopyright,
|
||||
new MapControlButton(Svg.copyright_svg())
|
||||
.onClick(() => toggledCopyright.isShown.setData(true)),
|
||||
toggledCopyright.isShown
|
||||
).SetClass("p-0.5");
|
||||
|
||||
const toggledDownload = new Toggle(
|
||||
new AllDownloads(
|
||||
|
@ -93,10 +75,10 @@ export default class LeftControls extends Combine {
|
|||
state.featureSwitchFilter
|
||||
);
|
||||
|
||||
|
||||
super([filterButton,
|
||||
downloadButtonn,
|
||||
copyrightButton])
|
||||
new BackgroundMapSwitch(state, state.backgroundLayer)
|
||||
])
|
||||
|
||||
this.SetClass("flex flex-col")
|
||||
|
||||
|
|
26
UI/BigComponents/PrivacyPolicy.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import Combine from "../Base/Combine";
|
||||
import Translations from "../i18n/Translations";
|
||||
import Title from "../Base/Title";
|
||||
|
||||
export default class PrivacyPolicy extends Combine {
|
||||
constructor() {
|
||||
const t = Translations.t.privacy
|
||||
super([
|
||||
new Title(t.title, 2),
|
||||
t.intro,
|
||||
|
||||
new Title(t.trackingTitle),
|
||||
t.tracking,
|
||||
new Title(t.geodataTitle),
|
||||
t.geodata,
|
||||
new Title(t.editingTitle),
|
||||
t.editing,
|
||||
new Title(t.miscCookiesTitle),
|
||||
t.miscCookies,
|
||||
new Title(t.whileYoureHere),
|
||||
t.surveillance,
|
||||
|
||||
]);
|
||||
this.SetClass("link-underline")
|
||||
}
|
||||
}
|
|
@ -136,7 +136,7 @@ export default class LengthInput extends InputElement<string> {
|
|||
if (leaflet) {
|
||||
const first = leaflet.layerPointToLatLng(firstClickXY)
|
||||
const last = leaflet.layerPointToLatLng([dx, dy])
|
||||
const geoDist = Math.floor(GeoOperations.distanceBetween([first.lng, first.lat], [last.lng, last.lat]) * 10000) / 10
|
||||
const geoDist = Math.floor(GeoOperations.distanceBetween([first.lng, first.lat], [last.lng, last.lat]) * 10) / 10
|
||||
self.value.setData("" + geoDist)
|
||||
}
|
||||
|
||||
|
|
|
@ -150,7 +150,8 @@ export default class LocationInput extends InputElement<Loc> implements MinimapO
|
|||
background: this.mapBackground,
|
||||
attribution: this.mapBackground !== State.state?.backgroundLayer,
|
||||
lastClickLocation: this.clickLocation,
|
||||
bounds: this._bounds
|
||||
bounds: this._bounds,
|
||||
addLayerControl: true
|
||||
}
|
||||
)
|
||||
this.leafletMap = this.map.leafletMap
|
||||
|
|
|
@ -18,6 +18,7 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
|||
import MoveConfig from "../../Models/ThemeConfig/MoveConfig";
|
||||
import {ElementStorage} from "../../Logic/ElementStorage";
|
||||
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers";
|
||||
import BaseLayer from "../../Models/BaseLayer";
|
||||
|
||||
interface MoveReason {
|
||||
text: Translation | string,
|
||||
|
@ -133,10 +134,12 @@ export default class MoveWizard extends Toggle {
|
|||
background = reason.background
|
||||
}
|
||||
|
||||
const preferredBackground = AvailableBaseLayers.SelectBestLayerAccordingTo(loc, new UIEventSource(background)).data
|
||||
const locationInput = new LocationInput({
|
||||
minZoom: reason.minZoom,
|
||||
centerLocation: loc,
|
||||
mapBackground: AvailableBaseLayers.SelectBestLayerAccordingTo(loc, new UIEventSource(background))
|
||||
mapBackground: new UIEventSource<BaseLayer>(preferredBackground) // We detach the layer
|
||||
|
||||
})
|
||||
|
||||
if (reason.lockBounds) {
|
||||
|
|
|
@ -93,7 +93,7 @@ export default class SplitRoadWizard extends Toggle {
|
|||
function onMapClick(coordinates) {
|
||||
// First, we check if there is another, already existing point nearby
|
||||
const points = splitPoints.data.map((f, i) => [f.feature, i])
|
||||
.filter(p => GeoOperations.distanceBetween(p[0].geometry.coordinates, coordinates) * 1000 < 5)
|
||||
.filter(p => GeoOperations.distanceBetween(p[0].geometry.coordinates, coordinates) < 5)
|
||||
.map(p => p[1])
|
||||
.sort((a, b) => a - b)
|
||||
.reverse()
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
28
Utils.ts
|
@ -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) {
|
||||
|
|
|
@ -122,7 +122,7 @@
|
|||
"question": {
|
||||
"en": "What is the type of this artwork?",
|
||||
"nl": "Wat voor soort kunstwerk is dit?",
|
||||
"fr": "Quel est le type de cette œuvre d'art?",
|
||||
"fr": "Quel est le type de cette œuvre d'art ?",
|
||||
"de": "Was ist die Art dieses Kunstwerks?",
|
||||
"it": "Che tipo di opera d’arte è questo?",
|
||||
"ru": "К какому типу относится эта работа?",
|
||||
|
|
|
@ -34,7 +34,8 @@
|
|||
"en": "Bollard",
|
||||
"nl": "Paaltje",
|
||||
"de": "Poller",
|
||||
"ru": "Прикол"
|
||||
"ru": "Прикол",
|
||||
"fr": "Bollard"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -53,7 +54,8 @@
|
|||
"en": "Bollard",
|
||||
"nl": "Paaltje",
|
||||
"de": "Poller",
|
||||
"ru": "Прикол"
|
||||
"ru": "Прикол",
|
||||
"fr": "Bollard"
|
||||
},
|
||||
"tags": [
|
||||
"barrier=bollard"
|
||||
|
@ -107,7 +109,8 @@
|
|||
"then": {
|
||||
"en": "A cyclist can go past this.",
|
||||
"nl": "Een fietser kan hier langs.",
|
||||
"de": "Ein Radfahrer kann hindurchfahren."
|
||||
"de": "Ein Radfahrer kann hindurchfahren.",
|
||||
"fr": "Un cycliste peut franchir ceci."
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -115,7 +118,8 @@
|
|||
"then": {
|
||||
"en": "A cyclist can not go past this.",
|
||||
"nl": "Een fietser kan hier niet langs.",
|
||||
"de": "Ein Radfahrer kann nicht hindurchfahren."
|
||||
"de": "Ein Radfahrer kann nicht hindurchfahren.",
|
||||
"fr": "Un cycliste ne peut pas franchir ceci."
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -125,7 +129,8 @@
|
|||
"question": {
|
||||
"en": "What kind of bollard is this?",
|
||||
"nl": "Wat voor soort paal is dit?",
|
||||
"de": "Um was für einen Poller handelt es sich?"
|
||||
"de": "Um was für einen Poller handelt es sich?",
|
||||
"fr": "Quel est le type de bollard (borne) ?"
|
||||
},
|
||||
"condition": "barrier=bollard",
|
||||
"mappings": [
|
||||
|
@ -134,7 +139,8 @@
|
|||
"then": {
|
||||
"en": "Removable bollard",
|
||||
"nl": "Verwijderbare paal",
|
||||
"de": "Entfernbarer Poller"
|
||||
"de": "Entfernbarer Poller",
|
||||
"fr": "Bollard amovible"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -142,7 +148,8 @@
|
|||
"then": {
|
||||
"en": "Fixed bollard",
|
||||
"nl": "Vaste paal",
|
||||
"de": "Feststehender Poller"
|
||||
"de": "Feststehender Poller",
|
||||
"fr": "Bollard fixe"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -150,7 +157,8 @@
|
|||
"then": {
|
||||
"en": "Bollard that can be folded down",
|
||||
"nl": "Paal die platgevouwen kan worden",
|
||||
"de": "Umlegbarer Poller"
|
||||
"de": "Umlegbarer Poller",
|
||||
"fr": "Bollard qui peut être couché"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -158,7 +166,8 @@
|
|||
"then": {
|
||||
"en": "Flexible bollard, usually plastic",
|
||||
"nl": "Flexibele paal, meestal plastic",
|
||||
"de": "Flexibler Poller, meist aus Kunststoff"
|
||||
"de": "Flexibler Poller, meist aus Kunststoff",
|
||||
"fr": "Bollard flexible, généralement en plastique"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -166,7 +175,8 @@
|
|||
"then": {
|
||||
"en": "Rising bollard",
|
||||
"nl": "Verzonken poller",
|
||||
"de": "Ausfahrender Poller"
|
||||
"de": "Ausfahrender Poller",
|
||||
"fr": "Bollard rétractable"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -219,7 +229,8 @@
|
|||
"render": {
|
||||
"en": "Maximum width: {maxwidth:physical} m",
|
||||
"nl": "Maximumbreedte: {maxwidth:physical} m",
|
||||
"de": "Maximale Durchfahrtsbreite: {maxwidth:physical} m"
|
||||
"de": "Maximale Durchfahrtsbreite: {maxwidth:physical} m",
|
||||
"fr": "Largeur maximale: {maxwidth:physical} m"
|
||||
},
|
||||
"question": {
|
||||
"en": "How wide is the gap left over besides the barrier?",
|
||||
|
@ -273,7 +284,8 @@
|
|||
"render": {
|
||||
"en": "Width of opening: {width:opening} m",
|
||||
"nl": "Breedte van de opening: {width:opening} m",
|
||||
"de": "Breite der Öffnung: {width:opening} m"
|
||||
"de": "Breite der Öffnung: {width:opening} m",
|
||||
"fr": "Largeur de l'ouverture : {width:opening} m"
|
||||
},
|
||||
"question": {
|
||||
"en": "How wide is the smallest opening next to the barriers?",
|
||||
|
|
|
@ -125,7 +125,8 @@
|
|||
{
|
||||
"if": "bench=yes",
|
||||
"then": {
|
||||
"en": "There is a normal, sit-down bench here"
|
||||
"en": "There is a normal, sit-down bench here",
|
||||
"fr": "Il y a un banc normal pour s'asseoir ici"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -144,7 +145,8 @@
|
|||
{
|
||||
"if": "bench=no",
|
||||
"then": {
|
||||
"en": "There is no bench here"
|
||||
"en": "There is no bench here",
|
||||
"fr": "Il n'y a pas de banc ici"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -239,7 +239,8 @@
|
|||
"question": {
|
||||
"en": "What is the email address of the maintainer?",
|
||||
"nl": "Wat is het email-adres van de beheerder?",
|
||||
"de": "Wie lautet die E-Mail-Adresse des Betreuers?"
|
||||
"de": "Wie lautet die E-Mail-Adresse des Betreuers?",
|
||||
"fr": "Quelle est l'adresse email du service de maintenance ?"
|
||||
},
|
||||
"freeform": {
|
||||
"key": "email",
|
||||
|
@ -252,7 +253,8 @@
|
|||
"question": {
|
||||
"en": "What is the phone number of the maintainer?",
|
||||
"nl": "Wat is het telefoonnummer van de beheerder?",
|
||||
"de": "Wie lautet die Telefonnummer des Betreibers?"
|
||||
"de": "Wie lautet die Telefonnummer des Betreibers?",
|
||||
"fr": "Quel est le numéro de téléphone du service de maintenance ?"
|
||||
},
|
||||
"freeform": {
|
||||
"key": "phone",
|
||||
|
|
|
@ -89,7 +89,8 @@
|
|||
"description": {
|
||||
"en": "A telescope or pair of binoculars mounted on a pole, available to the public to look around. <img src='./assets/layers/binocular/binoculars_example.jpg' style='height: 300px; width: auto; display: block;' />",
|
||||
"nl": "Een telescoop of verrekijker die op een vaste plaats gemonteerd staat waar iedereen door mag kijken. <img src='./assets/layers/binocular/binoculars_example.jpg' style='height: 300px; width: auto; display: block;' />",
|
||||
"de": "Ein fest installiertes Teleskop oder Fernglas, für die öffentliche Nutzung. <img src='./assets/layers/binocular/binoculars_example.jpg' style='height: 300px; width: auto; display: block;' />"
|
||||
"de": "Ein fest installiertes Teleskop oder Fernglas, für die öffentliche Nutzung. <img src='./assets/layers/binocular/binoculars_example.jpg' style='height: 300px; width: auto; display: block;' />",
|
||||
"fr": "Une longue-vue ou une paire de jumelles montée sur un poteau, disponible au public pour scruter les environs.\n<img src='./assets/layers/binocular/binoculars_example.jpg' style='height: 300px; width: auto; display: block;' />"
|
||||
},
|
||||
"preciseInput": {
|
||||
"preferredBackground": "photo"
|
||||
|
|
|
@ -241,7 +241,8 @@
|
|||
"question": {
|
||||
"nl": "Rolstoeltoegankelijk",
|
||||
"en": "Wheelchair accessible",
|
||||
"de": "Zugänglich für Rollstuhlfahrer"
|
||||
"de": "Zugänglich für Rollstuhlfahrer",
|
||||
"fr": "Accessible aux fauteuils roulants"
|
||||
},
|
||||
"osmTags": {
|
||||
"or": [
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
"name": {
|
||||
"nl": "Cafés",
|
||||
"en": "Cafés and pubs",
|
||||
"de": "Cafés und Kneipen"
|
||||
"de": "Cafés und Kneipen",
|
||||
"fr": "Cafés et pubs"
|
||||
},
|
||||
"source": {
|
||||
"osmTags": {
|
||||
|
@ -94,12 +95,14 @@
|
|||
"question": {
|
||||
"nl": "Wat is de naam van dit café?",
|
||||
"en": "What is the name of this pub?",
|
||||
"de": "Wie heißt diese Kneipe?"
|
||||
"de": "Wie heißt diese Kneipe?",
|
||||
"fr": "Quel est le nom de ce pub ?"
|
||||
},
|
||||
"render": {
|
||||
"nl": "De naam van dit café is {name}",
|
||||
"en": "This pub is named {name}",
|
||||
"de": "Diese Kneipe heißt {name}"
|
||||
"de": "Diese Kneipe heißt {name}",
|
||||
"fr": "Ce pub se nomme {name}"
|
||||
},
|
||||
"freeform": {
|
||||
"key": "name"
|
||||
|
@ -164,7 +167,8 @@
|
|||
"question": {
|
||||
"en": "Opened now",
|
||||
"nl": "Nu geopened",
|
||||
"de": "Jetzt geöffnet"
|
||||
"de": "Jetzt geöffnet",
|
||||
"fr": "Ouvert maintenant"
|
||||
},
|
||||
"osmTags": "_isOpen=yes"
|
||||
}
|
||||
|
|
|
@ -2995,7 +2995,8 @@
|
|||
"#": "In some cases, charging is free but one has to be authenticated. We only ask for authentication if fee is no (or unset). By default one sees the questions for either the payment options or the authentication options, but normally not both",
|
||||
"question": {
|
||||
"en": "What kind of authentication is available at the charging station?",
|
||||
"nl": "Hoe kan men zich aanmelden aan dit oplaadstation?"
|
||||
"nl": "Hoe kan men zich aanmelden aan dit oplaadstation?",
|
||||
"fr": "Quelle sorte d'authentification est disponible à cette station de charge ?"
|
||||
},
|
||||
"multiAnswer": true,
|
||||
"mappings": [
|
||||
|
@ -3004,7 +3005,8 @@
|
|||
"ifnot": "authentication:membership_card=no",
|
||||
"then": {
|
||||
"en": "Authentication by a membership card",
|
||||
"nl": "Aanmelden met een lidkaart is mogelijk"
|
||||
"nl": "Aanmelden met een lidkaart is mogelijk",
|
||||
"fr": "Authentification par carte de membre"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -3012,7 +3014,8 @@
|
|||
"ifnot": "authentication:app=no",
|
||||
"then": {
|
||||
"en": "Authentication by an app",
|
||||
"nl": "Aanmelden via een applicatie is mogelijk"
|
||||
"nl": "Aanmelden via een applicatie is mogelijk",
|
||||
"fr": "Authentification par une app"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -3020,7 +3023,8 @@
|
|||
"ifnot": "authentication:phone_call=no",
|
||||
"then": {
|
||||
"en": "Authentication via phone call is available",
|
||||
"nl": "Aanmelden door te bellen naar een telefoonnummer is mogelijk"
|
||||
"nl": "Aanmelden door te bellen naar een telefoonnummer is mogelijk",
|
||||
"fr": "Authentification par appel téléphonique est disponible"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -3028,7 +3032,8 @@
|
|||
"ifnot": "authentication:short_message=no",
|
||||
"then": {
|
||||
"en": "Authentication via SMS is available",
|
||||
"nl": "Aanmelden via SMS is mogelijk"
|
||||
"nl": "Aanmelden via SMS is mogelijk",
|
||||
"fr": "Authentification par SMS est disponible"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -3036,7 +3041,8 @@
|
|||
"ifnot": "authentication:nfc=no",
|
||||
"then": {
|
||||
"en": "Authentication via NFC is available",
|
||||
"nl": "Aanmelden via NFC is mogelijk"
|
||||
"nl": "Aanmelden via NFC is mogelijk",
|
||||
"fr": "Authentification par NFC est disponible"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -3052,7 +3058,8 @@
|
|||
"ifnot": "authentication:debit_card=no",
|
||||
"then": {
|
||||
"en": "Authentication via debit card is available",
|
||||
"nl": "Aanmelden met een betaalkaart is mogelijk"
|
||||
"nl": "Aanmelden met een betaalkaart is mogelijk",
|
||||
"fr": "Authentification par carte de débit est disponible"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -3060,7 +3067,8 @@
|
|||
"ifnot": "authentication:none=no",
|
||||
"then": {
|
||||
"en": "Charging here is (also) possible without authentication",
|
||||
"nl": "Hier opladen is (ook) mogelijk zonder aan te melden"
|
||||
"nl": "Hier opladen is (ook) mogelijk zonder aan te melden",
|
||||
"fr": "Charger ici est (aussi) possible sans authentification"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -3140,8 +3148,8 @@
|
|||
{
|
||||
"if": "no:network=yes",
|
||||
"then": {
|
||||
"en": "Not part of a bigger network",
|
||||
"nl": "Maakt geen deel uit van een groter netwerk"
|
||||
"en": "Not part of a bigger network, e.g. because the charging station is maintained by a local business",
|
||||
"nl": "Maakt geen deel uit van een groter netwerk, een lokale zaak of organisatie beheert dit oplaadpunt"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -3161,8 +3169,32 @@
|
|||
"then": "Blink"
|
||||
},
|
||||
{
|
||||
"if": "network=eVgo",
|
||||
"then": "eVgo"
|
||||
"if": "network=EVgo",
|
||||
"then": "EVgo",
|
||||
"addExtraTags": [
|
||||
"brand:wikidata=Q61803820"
|
||||
]
|
||||
},
|
||||
{
|
||||
"if": "network=Allego",
|
||||
"then": "Allego",
|
||||
"addExtraTags": [
|
||||
"brand:wikidata=Q75560554"
|
||||
]
|
||||
},
|
||||
{
|
||||
"if": "network=Blue Corner",
|
||||
"then": "Blue Corner",
|
||||
"addExtraTags": [
|
||||
"brand:wikidata=Q106902344"
|
||||
]
|
||||
},
|
||||
{
|
||||
"if": "network=Tesla",
|
||||
"then": "Tesla",
|
||||
"addExtraTags": [
|
||||
"brand:wikidata=Q478214"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -3195,7 +3227,8 @@
|
|||
],
|
||||
"hideInAnswer": "operator="
|
||||
}
|
||||
]
|
||||
],
|
||||
"condition": "network="
|
||||
},
|
||||
{
|
||||
"id": "phone",
|
||||
|
@ -3520,20 +3553,23 @@
|
|||
{
|
||||
"question": {
|
||||
"en": "All vehicle types",
|
||||
"nl": "Alle voertuigen"
|
||||
"nl": "Alle voertuigen",
|
||||
"fr": "Tout type de véhicule"
|
||||
}
|
||||
},
|
||||
{
|
||||
"question": {
|
||||
"en": "Charging station for bicycles",
|
||||
"nl": "Oplaadpunten voor fietsen"
|
||||
"nl": "Oplaadpunten voor fietsen",
|
||||
"fr": "Station de charge pour vélos"
|
||||
},
|
||||
"osmTags": "bicycle=yes"
|
||||
},
|
||||
{
|
||||
"question": {
|
||||
"en": "Charging station for cars",
|
||||
"nl": "Oplaadpunten voor auto's"
|
||||
"nl": "Oplaadpunten voor auto's",
|
||||
"fr": "Station de charge pour automobiles"
|
||||
},
|
||||
"osmTags": {
|
||||
"or": [
|
||||
|
|
|
@ -438,8 +438,8 @@
|
|||
{
|
||||
"if": "no:network=yes",
|
||||
"then": {
|
||||
"en": "Not part of a bigger network",
|
||||
"nl": "Maakt geen deel uit van een groter netwerk"
|
||||
"en": "Not part of a bigger network, e.g. because the charging station is maintained by a local business",
|
||||
"nl": "Maakt geen deel uit van een groter netwerk, een lokale zaak of organisatie beheert dit oplaadpunt"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -459,8 +459,32 @@
|
|||
"then": "Blink"
|
||||
},
|
||||
{
|
||||
"if": "network=eVgo",
|
||||
"then": "eVgo"
|
||||
"if": "network=EVgo",
|
||||
"then": "EVgo",
|
||||
"addExtraTags": [
|
||||
"brand:wikidata=Q61803820"
|
||||
]
|
||||
},
|
||||
{
|
||||
"if": "network=Allego",
|
||||
"then": "Allego",
|
||||
"addExtraTags": [
|
||||
"brand:wikidata=Q75560554"
|
||||
]
|
||||
},
|
||||
{
|
||||
"if": "network=Blue Corner",
|
||||
"then": "Blue Corner",
|
||||
"addExtraTags": [
|
||||
"brand:wikidata=Q106902344"
|
||||
]
|
||||
},
|
||||
{
|
||||
"if": "network=Tesla",
|
||||
"then": "Tesla",
|
||||
"addExtraTags": [
|
||||
"brand:wikidata=Q478214"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -493,7 +517,8 @@
|
|||
],
|
||||
"hideInAnswer": "operator="
|
||||
}
|
||||
]
|
||||
],
|
||||
"condition": "network="
|
||||
},
|
||||
{
|
||||
"id": "phone",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
"calculatedTags": [
|
||||
"_closest_other_drinking_water=feat.closestn('drinking_water', 1, undefined, 5000).map(f => ({id: f.feat.id, distance: ''+f.distance}))[0]",
|
||||
"_closest_other_drinking_water_id=JSON.parse(feat.properties._closest_other_drinking_water)?.id",
|
||||
"_closest_other_drinking_water_distance=Math.floor(Number(JSON.parse(feat.properties._closest_other_drinking_water)?.distance) * 1000)"
|
||||
"_closest_other_drinking_water_distance=Math.floor(Number(JSON.parse(feat.properties._closest_other_drinking_water)?.distance))"
|
||||
],
|
||||
"minzoom": 13,
|
||||
"presets": [
|
||||
|
|
|
@ -138,7 +138,13 @@
|
|||
"en": "{multi_apply(_same_name_ids, name:etymology:wikidata;name:etymology, Auto-applying data on all segments with the same name, true)}"
|
||||
}
|
||||
},
|
||||
"wikipedia"
|
||||
{
|
||||
"id": "wikipedia",
|
||||
"render": {
|
||||
"en": "A Wikipedia article about this <b>street</b> exists:<br/>{wikipedia():max-height:25rem}"
|
||||
},
|
||||
"condition": "wikidata~*"
|
||||
}
|
||||
],
|
||||
"mapRendering": [
|
||||
{
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
"nb_NO": "Spøkelsessykler",
|
||||
"pl": "Duch roweru",
|
||||
"pt_BR": "Bicicleta fantasma",
|
||||
"ru": "Велосипед Ghost",
|
||||
"ru": "Велосипед ghost",
|
||||
"sv": "Spökcykel",
|
||||
"zh_Hant": "幽靈單車",
|
||||
"pt": "Bicicleta fantasma"
|
||||
|
@ -65,7 +65,20 @@
|
|||
"nl": "Witte fiets",
|
||||
"de": "Geisterrad",
|
||||
"it": "Bici fantasma",
|
||||
"fr": "Vélo fantôme"
|
||||
"fr": "Vélo fantôme",
|
||||
"eo": "Fantombiciklo",
|
||||
"es": "Bicicleta blanca",
|
||||
"fi": "Haamupyörä",
|
||||
"gl": "Bicicleta pantasma",
|
||||
"hu": "Emlékkerékpár",
|
||||
"ja": "ゴーストバイク",
|
||||
"nb_NO": "Spøkelsessykler",
|
||||
"pl": "Duch roweru",
|
||||
"pt": "Bicicleta fantasma",
|
||||
"pt_BR": "Bicicleta fantasma",
|
||||
"ru": "Велосипед ghost",
|
||||
"sv": "Spökcykel",
|
||||
"zh_Hant": "幽靈單車"
|
||||
},
|
||||
"tags": [
|
||||
"historic=memorial",
|
||||
|
|
11
assets/layers/gps_location_history/gps_location_history.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"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",
|
||||
"#": "Cache is disabled here as these points are kept seperately",
|
||||
"maxCacheAge": 0
|
||||
},
|
||||
"mapRendering": null
|
||||
}
|
|
@ -1,27 +1,33 @@
|
|||
{
|
||||
"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.",
|
||||
"nl": "Dit is waar je was sinds je deze website hebt geopened. Dit is enkel zichtbaar voor jou en niemand anders, je locatie wordt niet verstuurd"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -309,7 +309,10 @@
|
|||
"brand=Little Free Library",
|
||||
"nobrand="
|
||||
]
|
||||
}
|
||||
},
|
||||
"addExtraTags": [
|
||||
"brand:wikidata=Q6650101"
|
||||
]
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
|
|
|
@ -469,7 +469,9 @@
|
|||
"toilets:position!=urinal"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"level",
|
||||
"description"
|
||||
],
|
||||
"filter": [
|
||||
{
|
||||
|
|
|
@ -1,4 +1,53 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.08 8.86C8.13 8.53 8.24 8.24 8.38 7.99C8.52 7.74 8.72 7.53 8.97 7.37C9.21 7.22 9.51 7.15 9.88 7.14C10.11 7.15 10.32 7.19 10.51 7.27C10.71 7.36 10.89 7.48 11.03 7.63C11.17 7.78 11.28 7.96 11.37 8.16C11.46 8.36 11.5 8.58 11.51 8.8H13.3C13.28 8.33 13.19 7.9 13.02 7.51C12.85 7.12 12.62 6.78 12.32 6.5C12.02 6.22 11.66 6 11.24 5.84C10.82 5.68 10.36 5.61 9.85 5.61C9.2 5.61 8.63 5.72 8.15 5.95C7.67 6.18 7.27 6.48 6.95 6.87C6.63 7.26 6.39 7.71 6.24 8.23C6.09 8.75 6 9.29 6 9.87V10.14C6 10.72 6.08 11.26 6.23 11.78C6.38 12.3 6.62 12.75 6.94 13.13C7.26 13.51 7.66 13.82 8.14 14.04C8.62 14.26 9.19 14.38 9.84 14.38C10.31 14.38 10.75 14.3 11.16 14.15C11.57 14 11.93 13.79 12.24 13.52C12.55 13.25 12.8 12.94 12.98 12.58C13.16 12.22 13.27 11.84 13.28 11.43H11.49C11.48 11.64 11.43 11.83 11.34 12.01C11.25 12.19 11.13 12.34 10.98 12.47C10.83 12.6 10.66 12.7 10.46 12.77C10.27 12.84 10.07 12.86 9.86 12.87C9.5 12.86 9.2 12.79 8.97 12.64C8.72 12.48 8.52 12.27 8.38 12.02C8.24 11.77 8.13 11.47 8.08 11.14C8.03 10.81 8 10.47 8 10.14V9.87C8 9.52 8.03 9.19 8.08 8.86V8.86ZM10 0C4.48 0 0 4.48 0 10C0 15.52 4.48 20 10 20C15.52 20 20 15.52 20 10C20 4.48 15.52 0 10 0ZM10 18C5.59 18 2 14.41 2 10C2 5.59 5.59 2 10 2C14.41 2 18 5.59 18 10C18 14.41 14.41 18 10 18Z"
|
||||
fill="white"/>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<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"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="copyright.svg"
|
||||
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
|
||||
style="fill:none">
|
||||
<metadata
|
||||
id="metadata10">
|
||||
<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="defs8" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="640"
|
||||
inkscape:window-height="480"
|
||||
id="namedview6"
|
||||
showgrid="false"
|
||||
inkscape:zoom="16.68772"
|
||||
inkscape:cx="7.5715453"
|
||||
inkscape:cy="7.3459724"
|
||||
inkscape:current-layer="svg4" />
|
||||
<path
|
||||
d="M 8.4941968,9.1059293 C 8.5334104,8.8471194 8.6196804,8.6196804 8.7294785,8.4236123 8.8392767,8.2275441 8.9961312,8.0628469 9.1921993,7.9373633 9.3804247,7.8197224 9.6157065,7.7648234 9.9058877,7.7569806 c 0.1803823,0.00784 0.3450793,0.039214 0.4940913,0.1019555 0.156854,0.070584 0.298024,0.1646972 0.407822,0.2823381 0.109798,0.1176408 0.196068,0.2588099 0.266652,0.4156644 0.07058,0.1568545 0.101956,0.3293944 0.109798,0.5019344 h 1.403848 C 12.572414,8.6902649 12.501829,8.3530277 12.368503,8.0471615 12.235177,7.7412952 12.054794,7.4746425 11.819512,7.2550462 11.58423,7.0354499 11.301892,6.86291 10.972498,6.7374264 10.643103,6.6119428 10.282338,6.5570437 9.8823595,6.5570437 c -0.5097775,0 -0.9568128,0.08627 -1.3332636,0.2666526 C 8.1726451,7.004079 7.8589361,7.2393608 7.6079689,7.5452271 7.3570017,7.8510933 7.1687762,8.204016 7.0511354,8.6118377 6.9334945,9.0196594 6.86291,9.4431665 6.86291,9.8980443 v 0.2117537 c 0,0.454878 0.062742,0.878385 0.1803826,1.286207 0.1176409,0.407822 0.3058663,0.760744 0.5568335,1.058768 0.2509672,0.298024 0.5646762,0.541148 0.941127,0.713688 0.3764508,0.17254 0.8234862,0.266653 1.3332637,0.266653 0.3686072,0 0.7136872,-0.06274 1.0352392,-0.180383 0.321552,-0.117641 0.60389,-0.282338 0.847014,-0.494092 0.243125,-0.211753 0.439193,-0.454878 0.580362,-0.737216 0.141169,-0.282338 0.227439,-0.580362 0.235282,-0.901913 h -1.403848 c -0.0078,0.164697 -0.04706,0.313709 -0.117641,0.454878 -0.07058,0.141169 -0.164697,0.25881 -0.282338,0.360765 -0.117641,0.101956 -0.250967,0.180383 -0.407822,0.235282 -0.149011,0.0549 -0.305866,0.07058 -0.4705628,0.07843 C 9.6078637,12.243019 9.372582,12.18812 9.1921993,12.070479 8.9961312,11.944996 8.8392767,11.780299 8.7294785,11.58423 8.6196804,11.388162 8.5334104,11.152881 8.4941968,10.894071 8.4549832,10.635261 8.431455,10.368608 8.431455,10.109798 V 9.8980443 c 0,-0.2744951 0.023528,-0.533305 0.062742,-0.792115 z M 10,2.1572749 c -4.3291842,0 -7.8427251,3.5135409 -7.8427251,7.8427248 0,4.3291843 3.5135409,7.8427253 7.8427251,7.8427253 4.329184,0 7.842725,-3.513541 7.842725,-7.8427253 C 17.842725,5.6708158 14.329184,2.1572749 10,2.1572749 Z M 10,16.27418 c -3.4586418,0 -6.2741801,-2.815538 -6.2741801,-6.2741803 0,-3.4586415 2.8155383,-6.2741798 6.2741801,-6.2741798 3.458642,0 6.27418,2.8155383 6.27418,6.2741798 0,3.4586423 -2.815538,6.2741803 -6.27418,6.2741803 z"
|
||||
id="path2"
|
||||
style="fill:#000000;fill-opacity:1;stroke-width:0.78427249"
|
||||
inkscape:connector-curvature="0" />
|
||||
</svg>
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 3.8 KiB |
59
assets/svg/eye.svg
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<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"
|
||||
viewBox="0 -256 1850 1850"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.48.3.1 r9886"
|
||||
width="100%"
|
||||
height="100%"
|
||||
sodipodi:docname="eye_open_font_awesome.svg">
|
||||
<metadata
|
||||
id="metadata12">
|
||||
<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="defs10" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="640"
|
||||
inkscape:window-height="480"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.13169643"
|
||||
inkscape:cx="896"
|
||||
inkscape:cy="896"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="25"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" />
|
||||
<g
|
||||
transform="matrix(1,0,0,-1,30.372881,1259.8983)"
|
||||
id="g4">
|
||||
<path
|
||||
d="m 1664,576 q -152,236 -381,353 61,-104 61,-225 0,-185 -131.5,-316.5 Q 1081,256 896,256 711,256 579.5,387.5 448,519 448,704 448,825 509,929 280,812 128,576 261,371 461.5,249.5 662,128 896,128 1130,128 1330.5,249.5 1531,371 1664,576 z M 944,960 q 0,20 -14,34 -14,14 -34,14 -125,0 -214.5,-89.5 Q 592,829 592,704 q 0,-20 14,-34 14,-14 34,-14 20,0 34,14 14,14 14,34 0,86 61,147 61,61 147,61 20,0 34,14 14,14 14,34 z m 848,-384 q 0,-34 -20,-69 Q 1632,277 1395.5,138.5 1159,0 896,0 633,0 396.5,139 160,278 20,507 0,542 0,576 q 0,34 20,69 140,229 376.5,368 236.5,139 499.5,139 263,0 499.5,-139 236.5,-139 376.5,-368 20,-35 20,-69 z"
|
||||
id="path6"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:currentColor" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
48
assets/svg/generic_map.svg
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 53 53" style="enable-background:new 0 0 53 53;" xml:space="preserve">
|
||||
<polygon style="fill:#43B05C;" points="18,23.243 2.256,7.499 0,8 0,51 2.099,50.534 18,34.632 "/>
|
||||
<polygon style="fill:#48A0DC;" points="9.372,48.917 18,47 18,40.289 "/>
|
||||
<polygon style="fill:#43B05C;" points="18,4 6.884,6.47 18,17.586 "/>
|
||||
<polygon style="fill:#43B05C;" points="49.449,3.184 35,8 35,17.632 "/>
|
||||
<polygon style="fill:#48A0DC;" points="35,23.289 35,50 53,43 53,5.289 "/>
|
||||
<polygon style="fill:#3D994F;" points="26.523,26.109 35,17.632 35,8 18,4 18,17.586 "/>
|
||||
<polygon style="fill:#3D994F;" points="18,23.243 18,34.632 23.695,28.938 "/>
|
||||
<polygon style="fill:#4393BF;" points="18,40.289 18,47 35,50 35,23.289 "/>
|
||||
<polygon style="fill:#EFCE4A;" points="18,17.586 6.884,6.47 2.256,7.499 18,23.243 "/>
|
||||
<polygon style="fill:#EFCE4A;" points="18,34.632 2.099,50.534 9.372,48.917 18,40.289 "/>
|
||||
<polygon style="fill:#EFCE4A;" points="35,23.289 53,5.289 53,2 49.449,3.184 35,17.632 "/>
|
||||
<polygon style="fill:#D6B445;" points="26.523,26.109 18,17.586 18,23.243 23.695,28.938 18,34.632 18,40.289 35,23.289 35,17.632
|
||||
"/>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -487,6 +487,16 @@
|
|||
"authors": [],
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"path": "eye.svg",
|
||||
"license": "CC-BY-SA 3.0 Unported",
|
||||
"authors": [
|
||||
"Dave Gandy"
|
||||
],
|
||||
"sources": [
|
||||
"https://en.wikipedia.org/wiki/File:Eye_open_font_awesome.svg"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "filter.svg",
|
||||
"license": "CC0",
|
||||
|
@ -617,6 +627,16 @@
|
|||
"https://www.iconpacks.net/free-icon-pack/gender-107.html"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "generic_map.svg",
|
||||
"license": "CC0",
|
||||
"authors": [
|
||||
"Svg Repo"
|
||||
],
|
||||
"sources": [
|
||||
"https://www.svgrepo.com/svg/22182/map"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "hand.svg",
|
||||
"license": "CC0",
|
||||
|
@ -1115,6 +1135,16 @@
|
|||
"authors": [],
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"path": "satellite.svg",
|
||||
"license": "CC0",
|
||||
"authors": [
|
||||
"SVG Repo"
|
||||
],
|
||||
"sources": [
|
||||
"https://www.svgrepo.com/svg/80960/satellite"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "scissors.svg",
|
||||
"license": "CC-BY 3.0",
|
||||
|
@ -1299,6 +1329,14 @@
|
|||
"authors": [],
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"path": "upload.svg",
|
||||
"license": "CC0",
|
||||
"authors": [
|
||||
"Pieter Vander Vennet"
|
||||
],
|
||||
"sources": []
|
||||
},
|
||||
{
|
||||
"path": "wikidata.svg",
|
||||
"license": "Logo; All rights reserved",
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns="http://www.w3.org/2000/svg" width="256" height="256" id="svg3038" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 256 256"
|
||||
id="svg3038" version="1.1"
|
||||
inkscape:version="0.48.2 r9819" sodipodi:docname="Public-images-osm_logo.svg"
|
||||
inkscape:export-filename="/home/fred/bla.png" inkscape:export-xdpi="180" inkscape:export-ydpi="180"
|
||||
sodipodi:version="0.32" inkscape:output_extension="org.inkscape.output.svg.inkscape">
|
||||
|
|
Before Width: | Height: | Size: 168 KiB After Width: | Height: | Size: 168 KiB |
285
assets/svg/satellite.svg
Normal file
|
@ -0,0 +1,285 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 19.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="Capa_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 512 512"
|
||||
style="enable-background:new 0 0 512 512;"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="satellite.svg"
|
||||
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"><metadata
|
||||
id="metadata105"><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="defs103" /><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="namedview101"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.4609375"
|
||||
inkscape:cx="256"
|
||||
inkscape:cy="256"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Capa_1" />
|
||||
<g
|
||||
id="g68"
|
||||
transform="rotate(135,260.34904,265.22801)">
|
||||
<polygon
|
||||
style="fill:none"
|
||||
points="243.312,72 256,72 268.687,72 256,59.313 "
|
||||
id="polygon2" />
|
||||
<polygon
|
||||
style="fill:#5c546a"
|
||||
points="280,432 280,416 256,416 232,416 232,432 256,432 "
|
||||
id="polygon4" />
|
||||
<path
|
||||
style="fill:#888693"
|
||||
d="m 240,456 h 16 v -24 h -24 -8 c -8.82,0 -16,7.176 -16,16 v 48 c 0,8.824 7.18,16 16,16 h 8 v -48 c 0,-4.418 3.582,-8 8,-8 z"
|
||||
id="path6"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:#b6b8be"
|
||||
d="m 256,456 h 16 c 4.418,0 8,3.582 8,8 v 48 h 8 c 8.82,0 16,-7.176 16,-16 v -48 c 0,-8.824 -7.18,-16 -16,-16 h -8 -24 z"
|
||||
id="path8"
|
||||
inkscape:connector-curvature="0" />
|
||||
<rect
|
||||
x="88"
|
||||
y="280"
|
||||
style="fill:#5c546a"
|
||||
width="72"
|
||||
height="48"
|
||||
id="rect10" />
|
||||
<path
|
||||
style="fill:#5c546a"
|
||||
d="m 88,392 h 48 c 13.234,0 24,-10.766 24,-24 V 344 H 88 Z"
|
||||
id="path12"
|
||||
inkscape:connector-curvature="0" />
|
||||
<rect
|
||||
y="280"
|
||||
style="fill:#5c546a"
|
||||
width="72"
|
||||
height="48"
|
||||
id="rect14"
|
||||
x="0" />
|
||||
<path
|
||||
style="fill:#5c546a"
|
||||
d="m 0,344 v 24 c 0,13.234 10.766,24 24,24 h 48 v -48 z"
|
||||
id="path16"
|
||||
inkscape:connector-curvature="0" />
|
||||
<rect
|
||||
y="216"
|
||||
style="fill:#5c546a"
|
||||
width="72"
|
||||
height="48"
|
||||
id="rect18"
|
||||
x="0" />
|
||||
<rect
|
||||
x="88"
|
||||
y="216"
|
||||
style="fill:#5c546a"
|
||||
width="72"
|
||||
height="48"
|
||||
id="rect20" />
|
||||
<path
|
||||
style="fill:#5c546a"
|
||||
d="M 72,152 H 24 C 10.766,152 0,162.766 0,176 v 24 h 72 z"
|
||||
id="path22"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:#5c546a"
|
||||
d="m 160,200 v -24 c 0,-13.234 -10.766,-24 -24,-24 H 88 v 48 z"
|
||||
id="path24"
|
||||
inkscape:connector-curvature="0" />
|
||||
<polygon
|
||||
style="fill:#888693"
|
||||
points="0,200 0,216 72,216 72,264 0,264 0,280 72,280 72,328 0,328 0,344 72,344 72,392 88,392 88,344 160,344 160,328 88,328 88,280 160,280 176,280 176,264 160,264 88,264 88,216 160,216 160,200 88,200 88,152 72,152 72,200 "
|
||||
id="polygon26" />
|
||||
<rect
|
||||
x="440"
|
||||
y="280"
|
||||
style="fill:#5c546a"
|
||||
width="72"
|
||||
height="48"
|
||||
id="rect28" />
|
||||
<path
|
||||
style="fill:#5c546a"
|
||||
d="m 352,344 v 24 c 0,13.234 10.766,24 24,24 h 48 v -48 z"
|
||||
id="path30"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:#5c546a"
|
||||
d="m 440,392 h 48 c 13.234,0 24,-10.766 24,-24 v -24 h -72 z"
|
||||
id="path32"
|
||||
inkscape:connector-curvature="0" />
|
||||
<rect
|
||||
x="352"
|
||||
y="280"
|
||||
style="fill:#5c546a"
|
||||
width="72"
|
||||
height="48"
|
||||
id="rect34" />
|
||||
<path
|
||||
style="fill:#5c546a"
|
||||
d="m 424,152 h -48 c -13.234,0 -24,10.766 -24,24 v 24 h 72 z"
|
||||
id="path36"
|
||||
inkscape:connector-curvature="0" />
|
||||
<rect
|
||||
x="440"
|
||||
y="216"
|
||||
style="fill:#5c546a"
|
||||
width="72"
|
||||
height="48"
|
||||
id="rect38" />
|
||||
<path
|
||||
style="fill:#5c546a"
|
||||
d="m 488,152 h -48 v 48 h 72 v -24 c 0,-13.234 -10.766,-24 -24,-24 z"
|
||||
id="path40"
|
||||
inkscape:connector-curvature="0" />
|
||||
<rect
|
||||
x="352"
|
||||
y="216"
|
||||
style="fill:#5c546a"
|
||||
width="72"
|
||||
height="48"
|
||||
id="rect42" />
|
||||
<polygon
|
||||
style="fill:#888693"
|
||||
points="336,280 352,280 424,280 424,328 352,328 352,344 424,344 424,392 440,392 440,344 512,344 512,328 440,328 440,280 512,280 512,264 440,264 440,216 512,216 512,200 440,200 440,152 424,152 424,200 352,200 352,216 424,216 424,264 352,264 336,264 "
|
||||
id="polygon44" />
|
||||
<path
|
||||
style="fill:#5c546a"
|
||||
d="m 272,200 v -17.209 c -5.217,0.792 -10.559,1.209 -16,1.209 -5.441,0 -10.783,-0.417 -16,-1.209 V 200 h 16 z"
|
||||
id="path46"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:#ffcf00"
|
||||
d="m 336,216 c 0,-8.824 -7.18,-16 -16,-16 h -48 -16 v 40 h 16 c 13.255,0 24,10.745 24,24 v 152 h 16 c 13.234,0 24,-10.766 24,-24 V 280 264 Z"
|
||||
id="path48"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:#ff9300"
|
||||
d="m 240,240 h 16 v -40 h -16 -48 c -8.82,0 -16,7.176 -16,16 v 48 16 112 c 0,13.234 10.766,24 24,24 h 16 V 264 c 0,-13.255 10.745,-24 24,-24 z"
|
||||
id="path50"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:#5c546a"
|
||||
d="M 256,59.313 268.687,72 h 22.625 L 264,44.688 V 38.921 C 262.822,39.603 261.459,40 260,40 h -8 c -1.459,0 -2.822,-0.397 -4,-1.079 v 5.767 L 220.687,72 h 22.625 z"
|
||||
id="path52"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:#888693"
|
||||
d="m 256,72 h -12.688 -22.625 -52.281 c -4.484,0 -8.797,1.906 -11.828,5.227 -3.047,3.332 -4.555,7.824 -4.148,12.32 4.413,48.436 40.882,86.15 87.57,93.244 5.217,0.792 10.559,1.209 16,1.209 z"
|
||||
id="path54"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:#ffcf00"
|
||||
d="m 260,40 c 1.459,0 2.822,-0.397 4,-1.079 2.389,-1.384 4,-3.961 4,-6.921 V 8 c 0,-4.418 -3.582,-8 -8,-8 h -8 c -4.418,0 -8,3.582 -8,8 v 24 c 0,2.959 1.611,5.537 4,6.921 1.178,0.682 2.541,1.079 4,1.079 z"
|
||||
id="path56"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:#ff7400"
|
||||
d="m 256,240 h -16 c -13.255,0 -24,10.745 -24,24 v 152 h 16 24 z"
|
||||
id="path58"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:#b6b8be"
|
||||
d="m 256,72 v 112 c 5.441,0 10.783,-0.417 16,-1.209 46.688,-7.093 83.157,-44.808 87.57,-93.244 0.406,-4.496 -1.102,-8.988 -4.148,-12.32 C 352.39,73.906 348.078,72 343.594,72 h -52.281 -22.625 z"
|
||||
id="path60"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:#ff9300"
|
||||
d="m 272,240 h -16 v 176 h 24 16 V 264 c 0,-13.255 -10.745,-24 -24,-24 z"
|
||||
id="path62"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:#5c546a"
|
||||
d="m 240,456 c -4.418,0 -8,3.582 -8,8 v 48 h 24 v -56 z"
|
||||
id="path64"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:#888693"
|
||||
d="m 272,456 h -16 v 56 h 24 v -48 c 0,-4.419 -3.582,-8 -8,-8 z"
|
||||
id="path66"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
id="g70"
|
||||
transform="rotate(-135,253.45828,262.13625)">
|
||||
</g>
|
||||
<g
|
||||
id="g72"
|
||||
transform="rotate(-135,253.45828,262.13625)">
|
||||
</g>
|
||||
<g
|
||||
id="g74"
|
||||
transform="rotate(-135,253.45828,262.13625)">
|
||||
</g>
|
||||
<g
|
||||
id="g76"
|
||||
transform="rotate(-135,253.45828,262.13625)">
|
||||
</g>
|
||||
<g
|
||||
id="g78"
|
||||
transform="rotate(-135,253.45828,262.13625)">
|
||||
</g>
|
||||
<g
|
||||
id="g80"
|
||||
transform="rotate(-135,253.45828,262.13625)">
|
||||
</g>
|
||||
<g
|
||||
id="g82"
|
||||
transform="rotate(-135,253.45828,262.13625)">
|
||||
</g>
|
||||
<g
|
||||
id="g84"
|
||||
transform="rotate(-135,253.45828,262.13625)">
|
||||
</g>
|
||||
<g
|
||||
id="g86"
|
||||
transform="rotate(-135,253.45828,262.13625)">
|
||||
</g>
|
||||
<g
|
||||
id="g88"
|
||||
transform="rotate(-135,253.45828,262.13625)">
|
||||
</g>
|
||||
<g
|
||||
id="g90"
|
||||
transform="rotate(-135,253.45828,262.13625)">
|
||||
</g>
|
||||
<g
|
||||
id="g92"
|
||||
transform="rotate(-135,253.45828,262.13625)">
|
||||
</g>
|
||||
<g
|
||||
id="g94"
|
||||
transform="rotate(-135,253.45828,262.13625)">
|
||||
</g>
|
||||
<g
|
||||
id="g96"
|
||||
transform="rotate(-135,253.45828,262.13625)">
|
||||
</g>
|
||||
<g
|
||||
id="g98"
|
||||
transform="rotate(-135,253.45828,262.13625)">
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 8 KiB |
72
assets/svg/upload.svg
Normal 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 |
|
@ -17,7 +17,8 @@
|
|||
"pt": "Qual é a entidade Wikidata correspondente?",
|
||||
"hu": "Mi a megfelelő Wikidata-elem?",
|
||||
"it": "Qual è l’elemento Wikidata corrispondente?",
|
||||
"nb_NO": "Hva er respektivt Wikipedia-element?"
|
||||
"nb_NO": "Hva er respektivt Wikipedia-element?",
|
||||
"fr": "Quelle est l'entité Wikidata correspondante ?"
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
|
@ -29,7 +30,8 @@
|
|||
"pt": "Ainda não foi vinculada nenhuma página da Wikipédia",
|
||||
"hu": "Még nincs Wikipédia-oldal belinkelve",
|
||||
"it": "Nessuna pagina Wikipedia è ancora stata collegata",
|
||||
"nb_NO": "Ingen Wikipedia-side lenket enda"
|
||||
"nb_NO": "Ingen Wikipedia-side lenket enda",
|
||||
"fr": "Pas encore de lien vers une page Wikipedia"
|
||||
},
|
||||
"hideInAnswer": true
|
||||
}
|
||||
|
@ -99,7 +101,8 @@
|
|||
"pt": "Não vinculado à Wikipédia",
|
||||
"hu": "Nincs belinkelve a Wikipédiához",
|
||||
"it": "Non collegato a Wikipedia",
|
||||
"nb_NO": "Ikke lenket med Wikipedia"
|
||||
"nb_NO": "Ikke lenket med Wikipedia",
|
||||
"fr": "Non lié avec Wikipedia"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -184,7 +187,8 @@
|
|||
"de": "Dieser Ort ist speziell für Rollstuhlfahrer eingerichtet",
|
||||
"hu": "Ez a hely kifejezetten kerekesszékeseknek lett kialakítva",
|
||||
"it": "Questo luogo è stato adattato per favorire le persone in sedia a rotelle",
|
||||
"nb_NO": "Dette stedet er spesielt tilpasset rullestolsbrukere"
|
||||
"nb_NO": "Dette stedet er spesielt tilpasset rullestolsbrukere",
|
||||
"fr": "Cet endroit est spécialement adapté pour les usagers de fauteuils roulants"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -201,7 +205,8 @@
|
|||
"de": "Dieser Ort ist mit einem Rollstuhl leicht zu erreichen",
|
||||
"hu": "Ez a hely könnyedén elérhető kerekesszékkel",
|
||||
"it": "Questo luogo è facilmente raggiungibile con una sedia a rotelle",
|
||||
"nb_NO": "Dette stedet kan enkelt besøkes med rullestol"
|
||||
"nb_NO": "Dette stedet kan enkelt besøkes med rullestol",
|
||||
"fr": "Cet endroit est facilement accessible avec un fauteuil roulant"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -275,7 +280,7 @@
|
|||
"nl": "honden zijn <b>niet</b> toegelaten",
|
||||
"pt": "Os cães <b>não</b> são permitidos",
|
||||
"de": "Hunde sind <b>nicht</b> erlaubt",
|
||||
"fr": "Les chiens <b>ne</b> sont <b>pas</b> admis",
|
||||
"fr": "Chiens <b>non</b> admis",
|
||||
"eo": "Hundoj estas <b>malpermesataj</b>",
|
||||
"hu": "Kutya <b>nem</b> vihető be",
|
||||
"it": "I cani <b>non</b> sono ammessi",
|
||||
|
@ -371,34 +376,41 @@
|
|||
"#": "service:socket describes if a pub, restaurant or café offers electricity to their customers.",
|
||||
"question": {
|
||||
"en": "Does this amenity have electrical outlets, available to customers when they are inside?",
|
||||
"nl": "Zijn er stekkers beschikbaar voor klanten die binnen zitten?"
|
||||
"nl": "Zijn er stekkers beschikbaar voor klanten die binnen zitten?",
|
||||
"pt": "Esta infraestrutura tem tomadas elétricas, disponíveis para os clientes quando estão no interior?"
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"then": {
|
||||
"en": "There are plenty of domestic sockets available to customers seated indoors, where they can charge their electronics",
|
||||
"nl": "Er zijn binnen veel stekkers beschikbaar voor klanten die electronica wensen op te laden"
|
||||
"nl": "Er zijn binnen veel stekkers beschikbaar voor klanten die electronica wensen op te laden",
|
||||
"pt": "Há muitas tomadas elétricas disponíveis para clientes sentados no interior, onde estes podem carregar os seus dispositivos eletrónicos"
|
||||
},
|
||||
"if": "service:electricity=yes"
|
||||
},
|
||||
{
|
||||
"then": {
|
||||
"en": "There are a few domestic sockets available to customers seated indoors, where they can charge their electronics",
|
||||
"nl": "Er zijn binnen enkele stekkers beschikbaar voor klanten die electronica wensen op te laden"
|
||||
"nl": "Er zijn binnen enkele stekkers beschikbaar voor klanten die electronica wensen op te laden",
|
||||
"pt": "Há algumas tomadas elétricas disponíveis para clientes sentados no interior, onde estes podem carregar os seus dispositivos eletrónicos"
|
||||
},
|
||||
"if": "service:electricity=limited"
|
||||
},
|
||||
{
|
||||
"then": {
|
||||
"en": "There are no sockets available indoors to customers, but charging might be possible if the staff is asked",
|
||||
"nl": "Er zijn binnen geen stekkers beschikbaar, maar electronica opladen kan indien men dit aan het personeel vraagt"
|
||||
"nl": "Er zijn binnen geen stekkers beschikbaar, maar electronica opladen kan indien men dit aan het personeel vraagt",
|
||||
"fr": "Il n'y a pas de prises disponibles à l'intérieur pour les clients, mais la recharge est peut-être possible sur demande auprès des employés",
|
||||
"pt": "Não há tomadas elétricas disponíveis para clientes sentados no interior, mas pode-se pedir aos funcionários para carregar dispositivos eletrónicos"
|
||||
},
|
||||
"if": "service:electricity=ask"
|
||||
},
|
||||
{
|
||||
"then": {
|
||||
"en": "There are a no domestic sockets available to customers seated indoors",
|
||||
"nl": "Er zijn binnen geen stekkers beschikbaar"
|
||||
"nl": "Er zijn binnen geen stekkers beschikbaar",
|
||||
"fr": "Il n'y a pas de prises secteur disponibles pour les clients assis à l'intérieur",
|
||||
"pt": "Não há tomadas elétricas disponíveis para clientes sentados no interior"
|
||||
},
|
||||
"if": "service:electricity=no"
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 d’infrastructures 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 t’il 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": "新增垃圾站,這通常是提供露營駕駛丟棄廢水與化學性廁所廢水的地方,也會有飲用水與電力。"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
10
assets/themes/cyclenodes/README.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
This is a theme for mapping cycle node networks.
|
||||
The intention is to make it easy to map the cycle nodes itself while riding
|
||||
the bike and then continue mapping at home and add the routes itself
|
||||
and connecting the nodes.
|
||||
|
||||
This theme is based on a version first developed by Seppe Santes here:
|
||||
https://github.com/seppesantens/MapComplete-Themes/blob/main/cyclenodenetworks/cyclenodenetworks.json
|
||||
|
||||
A description on how to map cycle node networks in OSM can be found
|
||||
[on the OSM wiki](https://wiki.openstreetmap.org/wiki/Cycle_Node_Network_Tagging).
|
184
assets/themes/cyclenodes/cyclenodes.json
Normal file
|
@ -0,0 +1,184 @@
|
|||
{
|
||||
"id": "cyclenodes",
|
||||
"title": {
|
||||
"en": "Cycle Node Networks",
|
||||
"de": "Fahrrad-Knotenpunktnetzwerke"
|
||||
},
|
||||
"description": {
|
||||
"en": "This map shows cycle node networks and allows you to add new nodes easily",
|
||||
"de": "Diese Karte zeigt Knotenpunktnetzwerke für Radfahrer und erlaubt auch neue Knoten zu mappen"
|
||||
},
|
||||
"language": [
|
||||
"en",
|
||||
"de"
|
||||
],
|
||||
"maintainer": "Sebastian Kürten",
|
||||
"icon": "./assets/themes/cyclenodes/logo.svg",
|
||||
"version": "0",
|
||||
"hideFromOverview": true,
|
||||
"startLat": 51.755515,
|
||||
"startLon": 14.327545,
|
||||
"startZoom": 11,
|
||||
"widenFactor": 0.05,
|
||||
"clustering": {
|
||||
"maxZoom": 11,
|
||||
"minNeededElements": 1000000
|
||||
},
|
||||
"layers": [
|
||||
{
|
||||
"id": "node2node",
|
||||
"name": {
|
||||
"en": "node to node links"
|
||||
},
|
||||
"source": {
|
||||
"osmTags": {
|
||||
"and": [
|
||||
"network=rcn",
|
||||
"network:type=node_network",
|
||||
"route=bicycle"
|
||||
]
|
||||
}
|
||||
},
|
||||
"minzoom": 12,
|
||||
"title": {
|
||||
"render": {
|
||||
"en": "node to node link"
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"if": "ref~*",
|
||||
"then": {
|
||||
"en": "node to node link <strong>{ref}</strong>"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"mapRendering": [
|
||||
{
|
||||
"width": {
|
||||
"render": "4"
|
||||
},
|
||||
"color": {
|
||||
"render": "#00a703"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tagRenderings": [
|
||||
{
|
||||
"question": {
|
||||
"en": "When was this node to node link last surveyed?"
|
||||
},
|
||||
"render": {
|
||||
"en": "This node to node link was last surveyed on {survey:date}"
|
||||
},
|
||||
"freeform": {
|
||||
"key": "survey:date",
|
||||
"type": "date"
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"if": "survey:date:={_now:date}",
|
||||
"then": "Surveyed today!"
|
||||
}
|
||||
],
|
||||
"id": "node2node-survey:date"
|
||||
},
|
||||
"export_as_gpx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "node",
|
||||
"name": {
|
||||
"en": "nodes"
|
||||
},
|
||||
"source": {
|
||||
"osmTags": {
|
||||
"and": [
|
||||
"rcn_ref~*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"mapRendering": [
|
||||
{
|
||||
"location": [
|
||||
"point",
|
||||
"centroid"
|
||||
],
|
||||
"label": {
|
||||
"mappings": [
|
||||
{
|
||||
"if": "rcn_ref~*",
|
||||
"then": "<div style='position: absolute; top: 10px; right: 10px; color: white; background-color: #00a703; width: 20px; height: 20px; border-radius: 100%'>{rcn_ref}</div>"
|
||||
},
|
||||
{
|
||||
"if": "rcn_ref=",
|
||||
"then": "<div style='position: absolute; top: 10px; right: 10px; color: white; background-color: #00a703; width: 20px; height: 20px; border-radius: 100%'>?</div>"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"minzoom": 12,
|
||||
"title": {
|
||||
"render": {
|
||||
"en": "cycle node <strong>{rcn_ref}</strong>",
|
||||
"de": "Knotenpunkt"
|
||||
}
|
||||
},
|
||||
"presets": [
|
||||
{
|
||||
"title": {
|
||||
"de": "Knotenpunkt"
|
||||
},
|
||||
"tags": [
|
||||
"network:type=node_network"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": {
|
||||
"de": "Knotenpunkt im Netzwerk Spree-Neiße"
|
||||
},
|
||||
"tags": [
|
||||
"network:type=node_network",
|
||||
"cycle_network=Knotenpunktwegweisung Spree-Neiße"
|
||||
]
|
||||
}
|
||||
],
|
||||
"tagRenderings": [
|
||||
{
|
||||
"question": {
|
||||
"en": "When was this cycle node last surveyed?"
|
||||
},
|
||||
"render": {
|
||||
"en": "This cycle node was last surveyed on {survey:date}"
|
||||
},
|
||||
"freeform": {
|
||||
"key": "survey:date",
|
||||
"type": "date"
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"if": "survey:date:={_now:date}",
|
||||
"then": "Surveyed today!"
|
||||
}
|
||||
],
|
||||
"id": "node-survey:date"
|
||||
},
|
||||
{
|
||||
"question": {
|
||||
"en": "How many other cycle nodes does this node link to?"
|
||||
},
|
||||
"render": {
|
||||
"en": "This node links to {expected_rcn_route_relations} other cycle nodes."
|
||||
},
|
||||
"freeform": {
|
||||
"key": "expected_rcn_route_relations",
|
||||
"type": "int"
|
||||
},
|
||||
"id": "node-expected_rcn_route_relations"
|
||||
},
|
||||
"images"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
120
assets/themes/cyclenodes/logo.svg
Normal file
|
@ -0,0 +1,120 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<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"
|
||||
width="500"
|
||||
height="500"
|
||||
viewBox="0 0 132.29166 132.29167"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
sodipodi:docname="logo.svg"
|
||||
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.569"
|
||||
inkscape:cx="-196.08654"
|
||||
inkscape:cy="261.47827"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer2"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:window-width="1680"
|
||||
inkscape:window-height="993"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:snap-page="true" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="Pin">
|
||||
<rect
|
||||
style="opacity:1;vector-effect:none;fill:#aaaaaa;fill-opacity:1;stroke:none;stroke-width:2.11666656;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect871"
|
||||
width="11.492589"
|
||||
height="47.045895"
|
||||
x="60.399536"
|
||||
y="85.245773" />
|
||||
<path
|
||||
style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#aa0000;stroke-width:2.64583325;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="m 55.107867,115.30538 -42.756693,0.17248 -6.4523616,-9.40542 6.3992456,-9.444341 42.756693,-0.172484 z"
|
||||
id="rect890-8"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccccc" />
|
||||
<path
|
||||
style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#aa0000;stroke-width:2.64583325;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="m 77.183794,96.628102 42.756736,-0.172484 6.45236,9.405422 -6.39925,9.44434 -42.756726,0.17248 z"
|
||||
id="rect890-8-6"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccccc" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer3"
|
||||
inkscape:label="Arrows" />
|
||||
<g
|
||||
inkscape:label="Number"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-164.70831)">
|
||||
<g
|
||||
id="g869"
|
||||
transform="matrix(1.6101695,0,0,1.6101695,-32.98414,-127.23614)"
|
||||
style="stroke-width:0.62105262">
|
||||
<rect
|
||||
rx="3.96875"
|
||||
y="186.94827"
|
||||
x="37.922958"
|
||||
height="47.306705"
|
||||
width="47.283943"
|
||||
id="rect857"
|
||||
style="opacity:1;vector-effect:none;fill:#aa0000;fill-opacity:1;stroke:none;stroke-width:0.41080043;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<circle
|
||||
r="17.907059"
|
||||
cy="210.60162"
|
||||
cx="61.56493"
|
||||
id="path859"
|
||||
style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#f1f1f1;stroke-width:1.31456137;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<text
|
||||
id="text863"
|
||||
y="217.9334"
|
||||
x="50.124737"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:20.75391769px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.16432019"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:20.75391769px;font-family:Arial;-inkscape-font-specification:'Arial, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke-width:0.16432019"
|
||||
y="217.9334"
|
||||
x="50.124737"
|
||||
id="tspan861"
|
||||
sodipodi:role="line">25</tspan></text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.1 KiB |
|
@ -2,8 +2,8 @@
|
|||
"id": "natuurpunt",
|
||||
"customCss": "./assets/themes/natuurpunt/natuurpunt.css",
|
||||
"title": {
|
||||
"nl": "Natuurgebieden",
|
||||
"en": "Nature Reserves",
|
||||
"nl": "De Natuurpunt-kaart",
|
||||
"en": "The map of Natuurpunt",
|
||||
"de": "Naturschutzgebiete",
|
||||
"nb_NO": "Naturreservater",
|
||||
"it": "Riserve naturali"
|
||||
|
|
|
@ -760,14 +760,14 @@ video {
|
|||
top: 0px;
|
||||
}
|
||||
|
||||
.left-0 {
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.right-0 {
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.left-0 {
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.isolate {
|
||||
isolation: isolate;
|
||||
}
|
||||
|
@ -1920,6 +1920,10 @@ li::marker {
|
|||
border: 5px solid var(--catch-detail-color);
|
||||
}
|
||||
|
||||
.border-invisible {
|
||||
border: 5px solid #00000000;
|
||||
}
|
||||
|
||||
.border-attention {
|
||||
border-color: var(--catch-detail-color);
|
||||
}
|
||||
|
|
|
@ -208,6 +208,10 @@ li::marker {
|
|||
border: 5px solid var(--catch-detail-color);
|
||||
}
|
||||
|
||||
.border-invisible {
|
||||
border: 5px solid #00000000;
|
||||
}
|
||||
|
||||
.border-attention {
|
||||
border-color: var(--catch-detail-color);
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
@ -299,5 +302,19 @@
|
|||
"partOfAWay": "This feature is part of another way. Use another editor to move it.",
|
||||
"partOfRelation": "This feature is part of a relation. Use another editor to move it.",
|
||||
"cancel": "Cancel move"
|
||||
},
|
||||
"privacy": {
|
||||
"title": "Privacy policy",
|
||||
"intro":"Privacy is important - for both the individual and for society. MapComplete tries to respect your privacy as much as possible - up to the point no annoying cookie banner is needed. However, we still would like to inform you which information is gathered and shared, under which circumstances and why these trade-offs are made.",
|
||||
"trackingTitle": "Statistical data",
|
||||
"tracking": "To gather some insight in whom visits our website, some technical information is collected. This is included the country you visited the webpage from, which website referred you to MapComplete, the type of your device and the screensize. A coockie is placed on your device to indicate that you visited MapComplete earlier today. This data is not detailed enough to personally identify you. These statistics are only available to anyone in aggregate and are <a href='https://pietervdvn.goatcounter.com' target='_blank'>publicly available to anyone</a>",
|
||||
"geodataTitle": "Your geolocation",
|
||||
"geodata": "When MapComplete gets your geolocation, your geolocation and previously visited locations stay on your device. Your location data is never automatically sent to anywhere else - unless some (future) functionality clearly states otherwise.",
|
||||
"editingTitle": "When making changes",
|
||||
"editing": "When you make a change to the map, this change is recorded on OpenStreetMap and is publicly available to anyone. A changeset made with MapComplete includes the following data: <ul><li>The changes you made</li><li>Your username</li><li>When this change is made</li><li>The theme you used while making the change</li><li>The language of the user interface</li><li>An indication of how close you were to changed objects. Other mappers can use this information to determine if a change was made based on survey or on remote research</li></ul> Please refer to <a href='https://wiki.osmfoundation.org/wiki/Privacy_Policy' target='_blank'>the privacy policy on OpenStreetMap.org</a> for detailed information. We'd like to remind you that you can use a fictional name when signing up.",
|
||||
"miscCookiesTitle": "Other cookies",
|
||||
"miscCookies":"MapComplete integrates with various other services, especially to load images of features. Images are hosted on various third-party servers, which might set cookies on their own.",
|
||||
"whileYoureHere": "Do you care about privacy?",
|
||||
"surveillance": "As you are reading the privacy policy, you probably care about privacy - so do we! We even made <a href='https://mapcomplete.osm.be/surveillance'>a theme showing surveillance cameras.</a> Feel free to map them all!"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,9 @@
|
|||
"uploadDone": "Votre photo est ajoutée. Merci beaucoup !",
|
||||
"dontDelete": "Annuler",
|
||||
"doDelete": "Supprimer l'image",
|
||||
"isDeleted": "Supprimé"
|
||||
"isDeleted": "Supprimé",
|
||||
"toBig": "Votre image est trop large car elle est de {actual_size}. Veuillez utiliser des images d'au maximum {max_size}",
|
||||
"uploadMultipleDone": "{count} images ont été ajoutées. Merci de votre contribution !"
|
||||
},
|
||||
"centerMessage": {
|
||||
"loadingData": "Chargement des données…",
|
||||
|
@ -25,7 +27,8 @@
|
|||
"#": "Ces textes sont affichés au dessus des boutons de thème quand aucun thème n'est chargé",
|
||||
"title": "Bienvenue sur MapComplete",
|
||||
"intro": "MapComplete est une application qui permet de voir des informations d'OpenStreetMap sur un thème spécifique et de les éditer.",
|
||||
"pickTheme": "Choisissez un thème ci-dessous pour commencer."
|
||||
"pickTheme": "Choisissez un thème ci-dessous pour commencer.",
|
||||
"featuredThemeTitle": "Thème de la semaine"
|
||||
},
|
||||
"general": {
|
||||
"loginWithOpenStreetMap": "Se connecter avec OpenStreeMap",
|
||||
|
@ -173,9 +176,24 @@
|
|||
"plz_login": "Connectez vous pour laisser un avis"
|
||||
},
|
||||
"split": {
|
||||
"cancel": "Annuler"
|
||||
"cancel": "Annuler",
|
||||
"inviteToSplit": "Couper cette route en plusieurs segments. Cela permet d'appliquer des attributs différents pour chaque partie de la route.",
|
||||
"hasBeenSplit": "Cette route a été coupée",
|
||||
"split": "Couper",
|
||||
"loginToSplit": "Vous devez être connecté pour couper une route",
|
||||
"splitTitle": "Choisissez sur la carte où couper cette route"
|
||||
},
|
||||
"delete": {
|
||||
"cancel": "Annuler"
|
||||
"cancel": "Annuler",
|
||||
"delete": "Supprimer",
|
||||
"loginToDelete": "Vous devez être connecté pour supprimer un objet",
|
||||
"useSomethingElse": "Utilisez un autre éditeur OpenStreetMap pour le supprimer",
|
||||
"safeDelete": "Ce point peut être supprimé sans risque.",
|
||||
"notEnoughExperience": "Ce point a été créé par une autre personne.",
|
||||
"isDeleted": "Cet objet est supprimé",
|
||||
"isntAPoint": "Seul les points peuvent être supprimés, l'objet sélectionné est une ligne, un polygone ou une relation.",
|
||||
"onlyEditedByLoggedInUser": "Ce point a été édité seulement par vous et peut donc être supprimé.",
|
||||
"cannotBeDeleted": "Cet objet ne peut être supprimé",
|
||||
"partOfOthers": "Ce point faire partie d'un autre objet et ne peut être supprimé directement."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1197,7 +1197,7 @@
|
|||
"Network": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Not part of a bigger network"
|
||||
"then": "Not part of a bigger network, e.g. because the charging station is maintained by a local business"
|
||||
},
|
||||
"1": {
|
||||
"then": "Not part of a bigger network"
|
||||
|
@ -2670,6 +2670,9 @@
|
|||
"street-name-sign-image": {
|
||||
"render": "{image_carousel(image:streetsign)}<br/>{image_upload(image:streetsign, Add image of a street name sign)}"
|
||||
},
|
||||
"wikipedia": {
|
||||
"render": "A Wikipedia article about this <b>street</b> exists:<br/>{wikipedia():max-height:25rem}"
|
||||
},
|
||||
"wikipedia-etymology": {
|
||||
"question": "What is the Wikidata-item that this object is named after?",
|
||||
"render": "<h3>Wikipedia article of the name giver</h3>{wikipedia(name:etymology:wikidata):max-height:20rem}"
|
||||
|
|
|
@ -78,6 +78,11 @@
|
|||
},
|
||||
"ghost_bike": {
|
||||
"name": "Fantombiciklo",
|
||||
"presets": {
|
||||
"0": {
|
||||
"title": "Fantombiciklo"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"render": "Fantombiciklo"
|
||||
}
|
||||
|
|
|
@ -136,6 +136,11 @@
|
|||
},
|
||||
"ghost_bike": {
|
||||
"name": "Bicicleta blanca",
|
||||
"presets": {
|
||||
"0": {
|
||||
"title": "Bicicleta blanca"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"render": "Bicicleta blanca"
|
||||
}
|
||||
|
|
|
@ -112,6 +112,11 @@
|
|||
},
|
||||
"ghost_bike": {
|
||||
"name": "Haamupyörä",
|
||||
"presets": {
|
||||
"0": {
|
||||
"title": "Haamupyörä"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"render": "Haamupyörä"
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
"then": "Carrelage"
|
||||
}
|
||||
},
|
||||
"question": "Quel est le type de cette œuvre d'art?",
|
||||
"question": "Quel est le type de cette œuvre d'art ?",
|
||||
"render": "Type d'œuvre : {artwork_type}"
|
||||
},
|
||||
"artwork-website": {
|
||||
|
@ -72,6 +72,58 @@
|
|||
"render": "Œuvre d'art"
|
||||
}
|
||||
},
|
||||
"barrier": {
|
||||
"presets": {
|
||||
"0": {
|
||||
"title": "Bollard"
|
||||
}
|
||||
},
|
||||
"tagRenderings": {
|
||||
"Bollard type": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Bollard amovible"
|
||||
},
|
||||
"1": {
|
||||
"then": "Bollard fixe"
|
||||
},
|
||||
"2": {
|
||||
"then": "Bollard qui peut être couché"
|
||||
},
|
||||
"3": {
|
||||
"then": "Bollard flexible, généralement en plastique"
|
||||
},
|
||||
"4": {
|
||||
"then": "Bollard rétractable"
|
||||
}
|
||||
},
|
||||
"question": "Quel est le type de bollard (borne) ?"
|
||||
},
|
||||
"MaxWidth": {
|
||||
"render": "Largeur maximale: {maxwidth:physical} m"
|
||||
},
|
||||
"Width of opening (cyclebarrier)": {
|
||||
"render": "Largeur de l'ouverture : {width:opening} m"
|
||||
},
|
||||
"bicycle=yes/no": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Un cycliste peut franchir ceci."
|
||||
},
|
||||
"1": {
|
||||
"then": "Un cycliste ne peut pas franchir ceci."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Bollard"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"bench": {
|
||||
"name": "Bancs",
|
||||
"presets": {
|
||||
|
@ -167,8 +219,14 @@
|
|||
"tagRenderings": {
|
||||
"bench_at_pt-bench_type": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Il y a un banc normal pour s'asseoir ici"
|
||||
},
|
||||
"1": {
|
||||
"then": "Banc assis debout"
|
||||
},
|
||||
"2": {
|
||||
"then": "Il n'y a pas de banc ici"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -529,6 +587,9 @@
|
|||
},
|
||||
"question": "Est-ce que cette pompe est électrique ?"
|
||||
},
|
||||
"bike_repair_station-email": {
|
||||
"question": "Quelle est l'adresse email du service de maintenance ?"
|
||||
},
|
||||
"bike_repair_station-manometer": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
|
@ -558,6 +619,9 @@
|
|||
"question": "Qui maintient cette pompe à vélo ?",
|
||||
"render": "Mantenue par {operator}"
|
||||
},
|
||||
"bike_repair_station-phone": {
|
||||
"question": "Quel est le numéro de téléphone du service de maintenance ?"
|
||||
},
|
||||
"bike_repair_station-valves": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
|
@ -748,6 +812,87 @@
|
|||
"render": "Objet cycliste"
|
||||
}
|
||||
},
|
||||
"binocular": {
|
||||
"presets": {
|
||||
"0": {
|
||||
"description": "Une longue-vue ou une paire de jumelles montée sur un poteau, disponible au public pour scruter les environs.\n<img src='./assets/layers/binocular/binoculars_example.jpg' style='height: 300px; width: auto; display: block;' />"
|
||||
}
|
||||
}
|
||||
},
|
||||
"birdhide": {
|
||||
"filter": {
|
||||
"0": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Accessible aux fauteuils roulants"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cafe_pub": {
|
||||
"filter": {
|
||||
"0": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Ouvert maintenant"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "Cafés et pubs",
|
||||
"tagRenderings": {
|
||||
"Name": {
|
||||
"question": "Quel est le nom de ce pub ?",
|
||||
"render": "Ce pub se nomme {name}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"charging_station": {
|
||||
"filter": {
|
||||
"0": {
|
||||
"options": {
|
||||
"0": {
|
||||
"question": "Tout type de véhicule"
|
||||
},
|
||||
"1": {
|
||||
"question": "Station de charge pour vélos"
|
||||
},
|
||||
"2": {
|
||||
"question": "Station de charge pour automobiles"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tagRenderings": {
|
||||
"Authentication": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Authentification par carte de membre"
|
||||
},
|
||||
"1": {
|
||||
"then": "Authentification par une app"
|
||||
},
|
||||
"2": {
|
||||
"then": "Authentification par appel téléphonique est disponible"
|
||||
},
|
||||
"3": {
|
||||
"then": "Authentification par SMS est disponible"
|
||||
},
|
||||
"4": {
|
||||
"then": "Authentification par NFC est disponible"
|
||||
},
|
||||
"6": {
|
||||
"then": "Authentification par carte de débit est disponible"
|
||||
},
|
||||
"7": {
|
||||
"then": "Charger ici est (aussi) possible sans authentification"
|
||||
}
|
||||
},
|
||||
"question": "Quelle sorte d'authentification est disponible à cette station de charge ?"
|
||||
}
|
||||
}
|
||||
},
|
||||
"defibrillator": {
|
||||
"name": "Défibrillateurs",
|
||||
"presets": {
|
||||
|
|
|
@ -396,6 +396,11 @@
|
|||
},
|
||||
"ghost_bike": {
|
||||
"name": "Bicicleta pantasma",
|
||||
"presets": {
|
||||
"0": {
|
||||
"title": "Bicicleta pantasma"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"render": "Bicicleta pantasma"
|
||||
}
|
||||
|
|
|
@ -213,6 +213,11 @@
|
|||
},
|
||||
"ghost_bike": {
|
||||
"name": "Emlékkerékpár",
|
||||
"presets": {
|
||||
"0": {
|
||||
"title": "Emlékkerékpár"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"render": "Emlékkerékpár"
|
||||
}
|
||||
|
|
|
@ -92,6 +92,11 @@
|
|||
},
|
||||
"ghost_bike": {
|
||||
"name": "ゴーストバイク",
|
||||
"presets": {
|
||||
"0": {
|
||||
"title": "ゴーストバイク"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"render": "ゴーストバイク"
|
||||
}
|
||||
|
|
|
@ -176,6 +176,11 @@
|
|||
},
|
||||
"ghost_bike": {
|
||||
"name": "Spøkelsessykler",
|
||||
"presets": {
|
||||
"0": {
|
||||
"title": "Spøkelsessykler"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"render": "Spøkelsessykler"
|
||||
}
|
||||
|
|
|
@ -1273,7 +1273,7 @@
|
|||
"Network": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Maakt geen deel uit van een groter netwerk"
|
||||
"then": "Maakt geen deel uit van een groter netwerk, een lokale zaak of organisatie beheert dit oplaadpunt"
|
||||
},
|
||||
"1": {
|
||||
"then": "Maakt geen deel uit van een groter netwerk"
|
||||
|
@ -3009,6 +3009,13 @@
|
|||
"render": "Witte Fiets"
|
||||
}
|
||||
},
|
||||
"gps_track": {
|
||||
"tagRenderings": {
|
||||
"Privacy notice": {
|
||||
"render": "Dit is waar je was sinds je deze website hebt geopened. Dit is enkel zichtbaar voor jou en niemand anders, je locatie wordt niet verstuurd"
|
||||
}
|
||||
}
|
||||
},
|
||||
"grass_in_parks": {
|
||||
"name": "Toegankelijke grasvelden in parken",
|
||||
"title": {
|
||||
|
|
|
@ -198,6 +198,11 @@
|
|||
},
|
||||
"ghost_bike": {
|
||||
"name": "Duch roweru",
|
||||
"presets": {
|
||||
"0": {
|
||||
"title": "Duch roweru"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"render": "Duch roweru"
|
||||
}
|
||||
|
|
|
@ -524,6 +524,11 @@
|
|||
},
|
||||
"ghost_bike": {
|
||||
"name": "Bicicleta fantasma",
|
||||
"presets": {
|
||||
"0": {
|
||||
"title": "Bicicleta fantasma"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"render": "Bicicleta fantasma"
|
||||
}
|
||||
|
|
|
@ -536,6 +536,11 @@
|
|||
},
|
||||
"ghost_bike": {
|
||||
"name": "Bicicleta fantasma",
|
||||
"presets": {
|
||||
"0": {
|
||||
"title": "Bicicleta fantasma"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"render": "Bicicleta fantasma"
|
||||
}
|
||||
|
|
|
@ -745,7 +745,12 @@
|
|||
}
|
||||
},
|
||||
"ghost_bike": {
|
||||
"name": "Велосипед Ghost",
|
||||
"name": "Велосипед ghost",
|
||||
"presets": {
|
||||
"0": {
|
||||
"title": "Велосипед ghost"
|
||||
}
|
||||
},
|
||||
"tagRenderings": {
|
||||
"ghost_bike-inscription": {
|
||||
"render": "<i>{inscription}</i>"
|
||||
|
|
|
@ -16,6 +16,11 @@
|
|||
},
|
||||
"ghost_bike": {
|
||||
"name": "Spökcykel",
|
||||
"presets": {
|
||||
"0": {
|
||||
"title": "Spökcykel"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"render": "Spökcykel"
|
||||
}
|
||||
|
|
|
@ -450,6 +450,11 @@
|
|||
},
|
||||
"ghost_bike": {
|
||||
"name": "幽靈單車",
|
||||
"presets": {
|
||||
"0": {
|
||||
"title": "幽靈單車"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"render": "幽靈單車"
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
"emailOf": "Wat is het email-adres van {category}?",
|
||||
"emailIs": "Het email-adres van {category} is <a href=\"mailto:{email}\" target=\"_blank\">{email}</a>"
|
||||
},
|
||||
"openStreetMapIntro": "<h3>Een open kaart</h3><p>Zou het niet fantastisch zijn als er een open kaart zou zijn die door iedereen aangepast én gebruikt kan worden? Een kaart waar iedereen zijn interesses aan zou kunnen toevoegen? Dan zouden er geen duizend-en-één verschillende kleine kaartjes, websites, ... meer nodig zijn</p><p><b><a href='https://OpenStreetMap.org' target='_blank'>OpenStreetMap</a></b> is deze open kaart. Je mag de kaartdata gratis gebruiken (mits <a href='https://osm.org/copyright' target='_blank'>bronvermelding en herpublicatie van aanpassingen</a>). Daarenboven mag je de kaart ook gratis aanpassen als je een account maakt. Ook deze website is gebaseerd op OpenStreetMap. Als je hier een vraag beantwoord, gaat het antwoord daar ook naartoe</p><p>Tenslotte zijn er reeds vele gebruikers van OpenStreetMap. Denk maar <a href='https://organicmaps.app//' target='_blank'>Organic Maps</a>, <a href='https://osmAnd.net' target='_blank'>OsmAnd</a>, verschillende gespecialiseerde routeplanners, de achtergrondkaarten op Facebook, Instagram,...<br/>Zelfs Apple Maps en Bing-Maps gebruiken OpenStreetMap in hun kaarten!</p></p><p>Kortom, als je hier een punt toevoegd of een vraag beantwoord, zal dat na een tijdje ook in al dié applicaties te zien zijn.</p>",
|
||||
"openStreetMapIntro": "<h3>Een open kaart</h3><p>Zou het niet fantastisch zijn als er een open kaart zou zijn die door iedereen aangepast én gebruikt kan worden? Een kaart waar iedereen zijn interesses aan zou kunnen toevoegen? Dan zouden er geen duizend-en-één verschillende kleine kaartjes, websites, ... meer nodig zijn</p><p><b><a href=\"https://OpenStreetMap.org\" target=\"_blank\">OpenStreetMap</a></b> is deze open kaart. Je mag de kaartdata gratis gebruiken (mits <a href=\"https://osm.org/copyright\" target=\"_blank\">bronvermelding en herpublicatie van aanpassingen</a>). Daarenboven mag je de kaart ook gratis aanpassen als je een account maakt. Ook deze website is gebaseerd op OpenStreetMap. Als je hier een vraag beantwoord, gaat het antwoord daar ook naartoe</p><p>Tenslotte zijn er reeds vele gebruikers van OpenStreetMap. Denk maar <a href=\"https://organicmaps.app//\" target=\"_blank\">Organic Maps</a>, <a href=\"https://osmAnd.net\" target=\"_blank\">OsmAnd</a>, verschillende gespecialiseerde routeplanners, de achtergrondkaarten op Facebook, Instagram,...<br/>;Zelfs Apple Maps en Bing-Maps gebruiken OpenStreetMap in hun kaarten!</p><p></p><p>Kortom, als je hier een punt toevoegd of een vraag beantwoord, zal dat na een tijdje ook in al dié applicaties te zien zijn.</p>",
|
||||
"attribution": {
|
||||
"attributionTitle": "Met dank aan",
|
||||
"attributionContent": "<p>Alle data is voorzien door <a href='https://osm.org' target='_blank'>OpenStreetMap</a>, gratis en vrij te hergebruiken onder <a href='https://osm.org/copyright' target='_blank'>de Open DataBase Licentie</a>.</p>",
|
||||
|
@ -209,7 +209,7 @@
|
|||
"downloadAsPdfHelper": "Perfect om de huidige kaart af te printen",
|
||||
"downloadAsPdf": "Download een PDF van de huidig zichtbare kaart",
|
||||
"title": "Download de zichtbare data",
|
||||
"exporting": "Aan het exporteren..."
|
||||
"exporting": "Aan het exporteren…"
|
||||
},
|
||||
"cancel": "Annuleren",
|
||||
"testing": "Testmode - wijzigingen worden niet opgeslaan",
|
||||
|
|
254
langs/pt.json
|
@ -26,17 +26,263 @@
|
|||
"index": {
|
||||
"#": "Estes textos são mostrados acima dos botões do tema quando nenhum tema é carregado",
|
||||
"title": "Bem-vindo(a) ao MapComplete",
|
||||
"intro": "O MapComplete é um visualizador e editor do OpenStreetMap, que mostra informações sobre um tema específico.",
|
||||
"intro": "O MapComplete é um visualizador e editor do OpenStreetMap, que mostra informações sobre elementos de um tema específico permitindo atualizar a informação.",
|
||||
"pickTheme": "Escolha um tema abaixo para começar.",
|
||||
"featuredThemeTitle": "Destaque desta semana"
|
||||
},
|
||||
"delete": {
|
||||
"reasons": {
|
||||
"notFound": "Não foi possível encontrar este elemento"
|
||||
"notFound": "Não foi possível encontrar este elemento",
|
||||
"disused": "Este elemento não está a ser utilizado ou foi removido",
|
||||
"test": "Isto foi um ponto de teste - o elemento nunca existiu na realidade",
|
||||
"duplicate": "Este ponto é um duplicado de outro elemento"
|
||||
},
|
||||
"explanations": {
|
||||
"selectReason": "Por favor, selecione a razão porque este elemento deve ser eliminado",
|
||||
"hardDelete": "Este ponto será eliminado no OpenStreetMap. Pode ser recuperado por um contribuidor com experiência"
|
||||
}
|
||||
"hardDelete": "Este ponto será eliminado no OpenStreetMap. Pode ser recuperado por um contribuidor com experiência",
|
||||
"softDelete": "Este elemento será atualizado e ocultado nesta aplicação. <span class=\"sutil\">{reason}</span>"
|
||||
},
|
||||
"readMessages": "Tem mensagens por ler. Leia-as antes de eliminar um ponto - alguém pode ter algo a dizer sobre isso",
|
||||
"useSomethingElse": "Use antes outro editor do OpenStreetMap para eliminá-lo",
|
||||
"loading": "A inspecionar as propriedades para verificar se este elemento pode ser eliminado.",
|
||||
"isDeleted": "Este elemento está eliminado",
|
||||
"isntAPoint": "Apenas os pontos podem ser eliminados, o elemento selecionado é uma linha, área ou relação.",
|
||||
"loginToDelete": "Tem de estar autenticado para eliminar um ponto",
|
||||
"delete": "Eliminar",
|
||||
"cancel": "Cancelar",
|
||||
"cannotBeDeleted": "Este elemento não pode ser eliminado",
|
||||
"safeDelete": "Este ponto pode ser eliminado com segurança.",
|
||||
"onlyEditedByLoggedInUser": "Este ponto só foi editado por si, pode eliminá-lo com segurança.",
|
||||
"notEnoughExperience": "Este ponto foi adicionado por outra pessoa.",
|
||||
"partOfOthers": "Este ponto faz parte de alguma linha ou relação e não pode ser eliminado diretamente.",
|
||||
"whyDelete": "Porque é que este ponto deve ser eliminado?"
|
||||
},
|
||||
"split": {
|
||||
"loginToSplit": "Tem de estar autenticado para dividir uma estrada",
|
||||
"cancel": "Cancelar",
|
||||
"splitTitle": "Escolha no mapa onde dividir esta estrada",
|
||||
"inviteToSplit": "Dividir esta estrada em segmentos mais pequenos. Isto permite atribuir propriedades diferentes a várias partes da estrada.",
|
||||
"hasBeenSplit": "Esta linha foi dividida",
|
||||
"split": "Dividir"
|
||||
},
|
||||
"general": {
|
||||
"skip": "Ignorar esta pergunta",
|
||||
"skippedQuestions": "Algumas perguntas são ignoradas",
|
||||
"number": "número",
|
||||
"add": {
|
||||
"zoomInFurther": "Amplie mais para adicionar um ponto.",
|
||||
"openLayerControl": "Abra a caixa de controlo da camada",
|
||||
"disableFilters": "Desativar todos os filtros",
|
||||
"addNew": "Adicionar uma nova {categoria} aqui",
|
||||
"intro": "Clicou em algum lugar onde ainda não há dados conhecidos.<br>",
|
||||
"addNewMapLabel": "Adicionar novo item",
|
||||
"presetInfo": "O novo POI terá {tags}",
|
||||
"title": "Adicionar um novo ponto?",
|
||||
"pleaseLogin": "<a class=\"activate-osm-authentication\">Por favor, inicie a sessão para adicionar um novo ponto</a>",
|
||||
"disableFiltersExplanation": "Alguns elementos podem estar escondidos por um filtro",
|
||||
"stillLoading": "Os dados ainda estão a ser carregados. Aguarde um pouco antes de adicionar um novo ponto.",
|
||||
"confirmButton": "Adicione uma {category} aqui.<br><div class=\"alert\">Esta adição será visível a todos</div>",
|
||||
"layerNotEnabled": "A camada {layer} não está ativada. Ative esta camada para adicionar um ponto",
|
||||
"confirmIntro": "<h3>Adicionar {title} aqui?</h3>O ponto que criar aqui será <b>visível a todos</b>. Por favor, só adicione coisas ao mapa se elas realmente existirem. Muitas aplicações usam estes dados.",
|
||||
"zoomInMore": "Amplie mais para importar este elemento",
|
||||
"warnVisibleForEveryone": "A sua adição será visível para todos",
|
||||
"hasBeenImported": "Este ponto já foi importado"
|
||||
},
|
||||
"questions": {
|
||||
"websiteOf": "Qual é o site de {category}?",
|
||||
"emailOf": "Qual é o endereço de e-mail de {categoria}?",
|
||||
"emailIs": "O endereço de e-mail de {categoria} é <a href=\"mailto:{email}\" target=\"_blank\">{email}</a>",
|
||||
"websiteIs": "Site: <a href=\"{website}\" target=\"_blank\">{website}</a>",
|
||||
"phoneNumberIs": "O número de telefone de {category} é <a target=\"_blank\">{phone}</a>",
|
||||
"phoneNumberOf": "Qual é o número de telefone de {category}?"
|
||||
},
|
||||
"openTheMap": "Abrir o mapa",
|
||||
"download": {
|
||||
"downloadAsPdfHelper": "Ideal para imprimir o mapa atual",
|
||||
"noDataLoaded": "Ainda não foram descarregados dados. O descarregamento estará disponível brevemente",
|
||||
"downloadGeojson": "Descarregar dados visíveis como GeoJSON",
|
||||
"title": "Descarregar dados visíveis",
|
||||
"downloadAsPdf": "Descarregar um PDF do mapa atual",
|
||||
"exporting": "A exportar…",
|
||||
"downloadGeoJsonHelper": "Compatível com QGIS, ArcGIS, ESRI…",
|
||||
"downloadCSVHelper": "Compatível com LibreOffice Calc, Excel…",
|
||||
"includeMetaData": "Incluir metadados (último editor, valores calculados…)",
|
||||
"licenseInfo": "<h3>Aviso de direitos de autor</h3>Os dados fornecidos estão disponíveis sob a licença ODbL. A sua reutilização é gratuita para qualquer finalidade, mas <ul><li> é obrigatória a atribuição <b>© Contribuidores do OpenStreetMap</b></li><li>Qualquer alteração deve ser feita usando a licença</li></ul>Por favor, leia na íntegra o documento <a href=\"https://www.openstreetmap.org/copyright\" target=\"_blank\">Licença e Direitos de Autor</a> para mais informações.",
|
||||
"downloadCSV": "Descarregar dados visíveis como CSV"
|
||||
},
|
||||
"opening_hours": {
|
||||
"opensAt": "de",
|
||||
"closed_permanently": "Fechado por tempo desconhecido",
|
||||
"closed_until": "Fechado até {date}",
|
||||
"ph_open": "aberto",
|
||||
"ph_open_as_usual": "aberto como de costume",
|
||||
"open_during_ph": "Num feriado, isto",
|
||||
"loadingCountry": "A determinar o país…",
|
||||
"openTill": "até",
|
||||
"not_all_rules_parsed": "Estes horários de funcionamento são complicados. As seguintes regras são ignoradas no elemento de entrada:",
|
||||
"open_24_7": "Aberto 24 horas por dia, todos os dias",
|
||||
"ph_not_known": " ",
|
||||
"ph_closed": "fechado",
|
||||
"error_loading": "Erro: não foi possível visualizar estes horários de funcionamento."
|
||||
},
|
||||
"pdf": {
|
||||
"versionInfo": "v {version} - gerado em {date}",
|
||||
"generatedWith": "Gerado com o MapComplete.osm.be",
|
||||
"attr": "Dados do mapa © colaboradores do OpenStreetMap, reutilizáveis sob a licença ODbL",
|
||||
"attrBackground": "Camada de fundo: {background}"
|
||||
},
|
||||
"backgroundMap": "Mapa de fundo",
|
||||
"loginOnlyNeededToEdit": "se quiser editar o mapa",
|
||||
"weekdays": {
|
||||
"abbreviations": {
|
||||
"wednesday": "Qua",
|
||||
"tuesday": "Ter",
|
||||
"thursday": "Qui",
|
||||
"monday": "Seg",
|
||||
"saturday": "Sáb",
|
||||
"sunday": "Dom",
|
||||
"friday": "Sex"
|
||||
},
|
||||
"saturday": "Sábado",
|
||||
"monday": "Segunda-feira",
|
||||
"tuesday": "Terça-feira",
|
||||
"wednesday": "Quarta-feira",
|
||||
"thursday": "Quinta-feira",
|
||||
"friday": "Sexta-feira",
|
||||
"sunday": "Domingo"
|
||||
},
|
||||
"histogram": {
|
||||
"error_loading": "Não foi possível carregar o histograma"
|
||||
},
|
||||
"wikipedia": {
|
||||
"searchWikidata": "Pesquisar no Wikidata",
|
||||
"wikipediaboxTitle": "Wikipédia",
|
||||
"failed": "O carregamento da entrada da Wikipédia falhou",
|
||||
"loading": "A carregar a Wikipédia...",
|
||||
"noWikipediaPage": "Este item do Wikidata ainda não tem uma página correspondente na Wikipédia.",
|
||||
"noResults": "Nada encontrado para <i>{search}</i>",
|
||||
"doSearch": "Pesquise acima para ver os resultados",
|
||||
"createNewWikidata": "Criar um novo item do Wikidata"
|
||||
},
|
||||
"sharescreen": {
|
||||
"fsUserbadge": "Ativar o botão de iniciar sessão",
|
||||
"fsIncludeCurrentLayers": "Incluir as opções de camada atuais",
|
||||
"fsIncludeCurrentLocation": "Incluir localização atual",
|
||||
"intro": "<h3>Partilhar este mapa</h3> Partilhe este mapa copiando a hiperligação abaixo e enviando-a a amigos e familiares:",
|
||||
"addToHomeScreen": "<h3>Adicionar ao seu ecrã inicial</h3> Pode adicionar facilmente este site ao ecrã inicial do seu telemóvel. Para isso clique no botão 'Adicionar ao ecrã inicial' na barra de URL.",
|
||||
"embedIntro": "<h3>Incorporar no seu site</h3>Por favor, insira este mapa no seu site. <br>Encorajamos a fazê-lo - nem precisa de pedir permissão. <br> É grátis e sempre será. Quanto mais pessoas estiverem a usar isto, mais valioso se torna.",
|
||||
"copiedToClipboard": "Hiperligação copiada para a área de transferência",
|
||||
"thanksForSharing": "Obrigado por partilhar!",
|
||||
"editThemeDescription": "Adicionar ou alterar perguntas deste tema",
|
||||
"fsSearch": "Ativar a barra de pesquisa",
|
||||
"fsWelcomeMessage": "Mostrar a janela com a mensagem de boas-vindas e separadores associados",
|
||||
"fsLayers": "Ativar o controlo das camadas",
|
||||
"fsLayerControlToggle": "Começar com o controlo de camadas expandido",
|
||||
"fsAddNew": "Ativar o botão 'adicionar novo POI'",
|
||||
"fsGeolocation": "Ativar o botão 'localizar-me geograficamente' (apenas telemóvel)",
|
||||
"fsIncludeCurrentBackgroundMap": "Incluir a escolha de fundo atual <b>{name}</b>",
|
||||
"editThisTheme": "Editar este tema"
|
||||
},
|
||||
"attribution": {
|
||||
"themeBy": "Tema gerido por {autor}",
|
||||
"iconAttribution": {
|
||||
"title": "Ícones usados"
|
||||
},
|
||||
"attributionTitle": "Aviso de atribuição",
|
||||
"attributionContent": "<p>Todos os dados são fornecidos pelo <a href=\"https://osm.org\" target=\"_blank\">OpenStreetMap</a> e são livremente reutilizáveis sob a licença <a href=\"https://osm.org/copyright\" target=\"_blank\">the Open DataBase License</a>.</p>",
|
||||
"mapContributionsBy": "Os dados visíveis atuais têm edições feitas por {contributors}",
|
||||
"mapContributionsByAndHidden": "Os dados visíveis atuais têm edições feitas por {contributors} e mais {hiddenCount} contribuidores",
|
||||
"codeContributionsBy": "O MapComplete foi construído por {contributors} e <a href=\"https://github.com/pietervdvn/MapComplete/graphs/contributors\" target=\"_blank\"> _blank{hiddenCount} mais contribuintes</a>"
|
||||
},
|
||||
"testing": "Teste - as alterações não serão guardadas",
|
||||
"customThemeIntro": "<h3>Temas personalizados</h3>São temas visitados anteriormente criados pelos utilizadores.",
|
||||
"returnToTheMap": "Voltar ao mapa",
|
||||
"loginWithOpenStreetMap": "Iniciar sessão com OpenStreetMap",
|
||||
"search": {
|
||||
"nothing": "Nada encontrado…",
|
||||
"search": "Procurar um local",
|
||||
"searching": "A procurar…",
|
||||
"error": "Alguma coisa correu mal…"
|
||||
},
|
||||
"save": "Guardar",
|
||||
"oneSkippedQuestion": "Uma pergunta é ignorada",
|
||||
"morescreen": {
|
||||
"requestATheme": "Se quiser um tema personalizado, solicite-o no rastreador de problemas",
|
||||
"streetcomplete": "Outra aplicação semelhante é o <a class=\"underline hover:text-blue-800\" href=\"https://play.google.com/store/apps/details?id=de.westnordost.streetcomplete\" target=\"_blank\">StreetComplete</a>.",
|
||||
"createYourOwnTheme": "Crie o seu próprio tema MapComplete a partir do zero",
|
||||
"previouslyHiddenTitle": "Temas ocultos visitados anteriormente",
|
||||
"hiddenExplanation": "Estes temas só são acessíveis a quem tem a hiperligação. Você descobriu {substituído} de {total_substituído} temas ocultos.",
|
||||
"intro": "<h3>Mais mapas temáticos?</h3>Gosta de colecionar dados geográficos? <br>Tem mais temas disponíveis."
|
||||
},
|
||||
"about": "Edite e adicione facilmente o OpenStreetMap para um determinado tema",
|
||||
"nameInlineQuestion": "O nome desta {category} é $$$",
|
||||
"noNameCategory": "{category} sem nome",
|
||||
"pickLanguage": "Escolha um idioma: ",
|
||||
"readYourMessages": "Por favor, leia todas as suas mensagens do OpenStreetMap antes de adicionar um novo ponto.",
|
||||
"fewChangesBefore": "Por favor, responda a algumas perguntas sobre os pontos existentes antes de adicionar um novo ponto.",
|
||||
"getStartedNewAccount": " ou <a href=\"https://www.openstreetmap.org/user/new\" target=\"_blank\">crie uma conta</a>",
|
||||
"noTagsSelected": "Nenhuma etiqueta selecionada",
|
||||
"goToInbox": "Abrir mensagens",
|
||||
"getStartedLogin": "Entrar com OpenStreetMap para começar",
|
||||
"aboutMapcomplete": "<h3>Sobre o MapComplete</h3><p>Use-o para adicionar informações ao OpenStreetMap <b>sobre um tema específico</b>. Responda a perguntas e em poucos minutos as suas contribuições estão disponíveis em todos os lugares. O <b>responsável pelo tema</b> define os elementos, as perguntas e os idiomas disponíveis nele.</p> <h3>Descubra mais</h3><p>O MapComplete <b>mostra sempre o próximo passo</b> para saber mais sobre o OpenStreetMap.</p><ul> <li>Quando incorporado num site, o iframe liga-se ao MapComplete em ecrã cheio.</li><li>A versão ecrã cheio fornece informações sobre o OpenStreetMap</li><li>A visualização funciona sem ser preciso autenticar-se, mas a edição requer uma conta no OpenStreetMap.</li> <li>Se não estiver autenticado, é solicitado a fazê-lo</li><li>Após responder a uma pergunta, pode adicionar novos pontos ao mapa</li><li>Depois de um tempo, as etiquetas reais do OpenStreetMap são mostradas, mais tarde vinculando-se à wiki</li></ul><p></p><br><p>Deparou-se com <b>um problema</b>? Quer uma <b>nova funcionalidade</b>? Quer <b>ajudar a traduzir</b>? Vá ao <a href=\"https://github.com/pietervdvn/MapComplete\" target=\"_blank\">código-fonte</a> ou <a href=\"https://github.com/pietervdvn/MapComplete/issues\" target=\"_blank\">rastreador de problemas</a>. </p> <p>Quer ver <b>o seu progresso</b>? Veja a contagem de edições em <a href=\"{osmcha_link}\" target=\"_blank\">OsmCha</a>.</p>",
|
||||
"layerSelection": {
|
||||
"zoomInToSeeThisLayer": "Amplie para ver esta camada",
|
||||
"title": "Selecionar camadas"
|
||||
},
|
||||
"welcomeBack": "Iniciou a sessão, bem-vindo de volta!",
|
||||
"loginToStart": "Inicie a sessão para responder a esta pergunta",
|
||||
"cancel": "Cancelar",
|
||||
"loading": "A carregar...",
|
||||
"openStreetMapIntro": "<h3>Um mapa aberto</h3><p>Um que todos podem usar e editar livremente. Um único lugar para armazenar todas as informações geográficas. Mapas diferentes, pequenos, incompatíveis e desatualizados não são necessários em nenhum lugar.</p> <p><b><a href=\"https://OpenStreetMap.org\" target=\"_blank\">OpenStreetMap</a></b> não é o mapa inimigo. Os dados do mapa podem ser usados livremente (com <a href=\"https://osm.org/copyright\" target=\"_blank\">atribuição e publicação das alterações a esses dados</a>). Todos podem adicionar novos dados e corrigir erros. Este site usa o OpenStreetMap. Todos os dados são de lá e suas respostas e correções são usadas em todos os lugares.</p> <p> Muitas pessoas e aplicações já usam o OpenStreetMap: <a href=\"https://organicmaps.app/\" target=\"_blank\">O Organic Maps</a>, <a href=\"https://osmAnd.net\" target=\"_blank\">OsmAnd</a>, mas também os mapas no Facebook, Instagram, Apple-maps e Bing-maps são (em parte) enriquecidos com dados do OpenStreetMap.</p>",
|
||||
"osmLinkTooltip": "Ver este objeto no OpenStreetMap para ver o histórico e mais opções de edição"
|
||||
},
|
||||
"favourite": {
|
||||
"loginNeeded": "<h3>Iniciar sessão</h3>O layout pessoal só está disponível para utilizadores do OpenStreetMap",
|
||||
"panelIntro": "<h3>O seu tema pessoal</h3>Ative as suas camadas favoritas a partir de todos os temas oficiais",
|
||||
"reload": "Recarregar dados"
|
||||
},
|
||||
"reviews": {
|
||||
"title": "{count} avaliações",
|
||||
"plz_login": "Inicie a sessão para deixar uma avaliação",
|
||||
"title_singular": "Uma avaliação",
|
||||
"name_required": "É necessário um nome para mostrar e criar avaliações",
|
||||
"i_am_affiliated": "<span>Eu sou afiliado a este objeto</span><br><span class=\"subtle\"><br><span class=\"subtle\">Marque isto se for proprietário, criador, funcionário…</span></span>",
|
||||
"saving_review": "A guardar…",
|
||||
"affiliated_reviewer_warning": "(avaliação de afiliado)",
|
||||
"saved": "<span class=\"thanks\">Avaliação guardada. Obrigado por partilhar!</span>",
|
||||
"tos": "Se criar uma avaliação, concorda com <a href=\"https://mangrove.reviews/terms\" target=\"_blank\">os termos do serviço e a política de privacidade de Mangrove.reviews</a>",
|
||||
"attribution": "As avaliações são fornecidas por <a href=\"https://mangrove.reviews/\" target=\"_blank\">Mangrove Reviews</a> e estão disponíveis sob a licença <a href=\"https://mangrove.reviews/terms#8-licensing-of-content\" target=\"_blank\">CC-BY 4.0</a>.",
|
||||
"no_rating": "Nenhuma classificação dada",
|
||||
"posting_as": "Publicar como",
|
||||
"no_reviews_yet": "Ainda não existem avaliações. Seja o primeiro a escrever uma e ajude a abrir os dados e os negócios!",
|
||||
"write_a_comment": "Deixar uma avaliação…"
|
||||
},
|
||||
"move": {
|
||||
"isRelation": "Este elemento é uma relação e não pode ser movido",
|
||||
"loginToMove": "Tem de iniciar sessão para mover um ponto",
|
||||
"moveTitle": "Mover este ponto",
|
||||
"whyMove": "Por que quer mudar este ponto?",
|
||||
"confirmMove": "Mover aqui",
|
||||
"pointIsMoved": "O ponto foi movido",
|
||||
"zoomInFurther": "Amplie mais para confirmar a ação de mover",
|
||||
"selectReason": "Qual o motivo para mover este objeto?",
|
||||
"reasons": {
|
||||
"reasonRelocation": "O elemento foi deslocado para um local totalmente diferente",
|
||||
"reasonInaccurate": "A localização deste elemento é imprecisa e deve ser movida alguns metros para o local correto"
|
||||
},
|
||||
"inviteToMove": {
|
||||
"generic": "Mover este ponto",
|
||||
"reasonInaccurate": "Melhorar a precisão deste ponto",
|
||||
"reasonRelocation": "Deslocar este elemento para outro lugar porque ele foi deslocado"
|
||||
},
|
||||
"cannotBeMoved": "Este elemento não pode ser movido.",
|
||||
"partOfRelation": "Este elemento faz parte de uma relação. Use outro editor para movê-lo.",
|
||||
"cancel": "Cancelar movimento",
|
||||
"inviteToMoveAgain": "Mover este ponto novamente",
|
||||
"isWay": "Este elemento é uma linha. Use outro editor do OpenStreetMap para movê-lo.",
|
||||
"partOfAWay": "Este elemento faz parte de outra linha. Use outro editor para movê-lo."
|
||||
},
|
||||
"multi_apply": {
|
||||
"autoApply": "Ao alterar os atributos {attr_names}, estes atributos serão alterados automaticamente em {count} outros objetos também"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"then": "Chiens admis"
|
||||
},
|
||||
"1": {
|
||||
"then": "Les chiens <b>ne</b> sont <b>pas</b> admis"
|
||||
"then": "Chiens <b>non</b> admis"
|
||||
},
|
||||
"2": {
|
||||
"then": "Les chiens sont admis, mais ils doivent être tenus en laisse"
|
||||
|
@ -59,11 +59,27 @@
|
|||
"phone": {
|
||||
"question": "Quel est le numéro de téléphone de {name} ?"
|
||||
},
|
||||
"service:electricity": {
|
||||
"mappings": {
|
||||
"2": {
|
||||
"then": "Il n'y a pas de prises disponibles à l'intérieur pour les clients, mais la recharge est peut-être possible sur demande auprès des employés"
|
||||
},
|
||||
"3": {
|
||||
"then": "Il n'y a pas de prises secteur disponibles pour les clients assis à l'intérieur"
|
||||
}
|
||||
}
|
||||
},
|
||||
"website": {
|
||||
"question": "Quel est le site web de {name} ?"
|
||||
},
|
||||
"wheelchair-access": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Cet endroit est spécialement adapté pour les usagers de fauteuils roulants"
|
||||
},
|
||||
"1": {
|
||||
"then": "Cet endroit est facilement accessible avec un fauteuil roulant"
|
||||
},
|
||||
"2": {
|
||||
"then": "Il est possible d'accéder à cet endroit en chaise roulante, mais ce n'est pas facile"
|
||||
},
|
||||
|
@ -72,6 +88,21 @@
|
|||
}
|
||||
},
|
||||
"question": "Est-ce que cet endroit est accessible en chaise roulante ?"
|
||||
},
|
||||
"wikipedia": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Pas encore de lien vers une page Wikipedia"
|
||||
}
|
||||
},
|
||||
"question": "Quelle est l'entité Wikidata correspondante ?"
|
||||
},
|
||||
"wikipedialink": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Non lié avec Wikipedia"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -59,6 +59,23 @@
|
|||
"phone": {
|
||||
"question": "Qual é o número de telefone de {name}?"
|
||||
},
|
||||
"service:electricity": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
"then": "Há muitas tomadas elétricas disponíveis para clientes sentados no interior, onde estes podem carregar os seus dispositivos eletrónicos"
|
||||
},
|
||||
"1": {
|
||||
"then": "Há algumas tomadas elétricas disponíveis para clientes sentados no interior, onde estes podem carregar os seus dispositivos eletrónicos"
|
||||
},
|
||||
"2": {
|
||||
"then": "Não há tomadas elétricas disponíveis para clientes sentados no interior, mas pode-se pedir aos funcionários para carregar dispositivos eletrónicos"
|
||||
},
|
||||
"3": {
|
||||
"then": "Não há tomadas elétricas disponíveis para clientes sentados no interior"
|
||||
}
|
||||
},
|
||||
"question": "Esta infraestrutura tem tomadas elétricas, disponíveis para os clientes quando estão no interior?"
|
||||
},
|
||||
"website": {
|
||||
"question": "Qual é o sítio web de {name}?"
|
||||
},
|
||||
|
|
|
@ -1084,7 +1084,7 @@
|
|||
"natuurpunt": {
|
||||
"description": "On this map you can find all the nature reserves that Natuurpunt offers ",
|
||||
"shortDescription": "This map shows the nature reserves of Natuurpunt",
|
||||
"title": "Nature Reserves"
|
||||
"title": "The map of Natuurpunt"
|
||||
},
|
||||
"observation_towers": {
|
||||
"description": "Publicly accessible towers to enjoy the view",
|
||||
|
|
|
@ -856,7 +856,7 @@
|
|||
"natuurpunt": {
|
||||
"description": "Op deze kaart vind je alle natuurgebieden die Natuurpunt ter beschikking stelt",
|
||||
"shortDescription": "Deze kaart toont de natuurgebieden van Natuurpunt",
|
||||
"title": "Natuurgebieden"
|
||||
"title": "De Natuurpunt-kaart"
|
||||
},
|
||||
"observation_towers": {
|
||||
"description": "Publieke uitkijktorens om van het panorama te genieten",
|
||||
|
|
|
@ -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": "咖啡廳與酒吧"
|
||||
}
|
||||
}
|
27
package-lock.json
generated
|
@ -26,6 +26,7 @@
|
|||
"email-validator": "^2.0.4",
|
||||
"escape-html": "^1.0.3",
|
||||
"i18next-client": "^1.11.4",
|
||||
"idb-keyval": "^6.0.3",
|
||||
"jquery": "^3.6.0",
|
||||
"jspdf": "^2.3.1",
|
||||
"latlon2country": "^1.1.3",
|
||||
|
@ -7435,6 +7436,14 @@
|
|||
"resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz",
|
||||
"integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0="
|
||||
},
|
||||
"node_modules/idb-keyval": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.0.3.tgz",
|
||||
"integrity": "sha512-yh8V7CnE6EQMu9YDwQXhRxwZh4nv+8xm/HV4ZqK4IiYFJBWYGjJuykADJbSP+F/GDXUBwCSSNn/14IpGL81TuA==",
|
||||
"dependencies": {
|
||||
"safari-14-idb-fix": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
|
@ -14366,6 +14375,11 @@
|
|||
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/safari-14-idb-fix": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/safari-14-idb-fix/-/safari-14-idb-fix-3.0.0.tgz",
|
||||
"integrity": "sha512-eBNFLob4PMq8JA1dGyFn6G97q3/WzNtFK4RnzT1fnLq+9RyrGknzYiM/9B12MnKAxuj1IXr7UKYtTNtjyKMBog=="
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
|
@ -24018,6 +24032,14 @@
|
|||
"resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz",
|
||||
"integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0="
|
||||
},
|
||||
"idb-keyval": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.0.3.tgz",
|
||||
"integrity": "sha512-yh8V7CnE6EQMu9YDwQXhRxwZh4nv+8xm/HV4ZqK4IiYFJBWYGjJuykADJbSP+F/GDXUBwCSSNn/14IpGL81TuA==",
|
||||
"requires": {
|
||||
"safari-14-idb-fix": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
|
@ -29713,6 +29735,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"safari-14-idb-fix": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/safari-14-idb-fix/-/safari-14-idb-fix-3.0.0.tgz",
|
||||
"integrity": "sha512-eBNFLob4PMq8JA1dGyFn6G97q3/WzNtFK4RnzT1fnLq+9RyrGknzYiM/9B12MnKAxuj1IXr7UKYtTNtjyKMBog=="
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
|
|
|
@ -74,6 +74,7 @@
|
|||
"email-validator": "^2.0.4",
|
||||
"escape-html": "^1.0.3",
|
||||
"i18next-client": "^1.11.4",
|
||||
"idb-keyval": "^6.0.3",
|
||||
"jquery": "^3.6.0",
|
||||
"jspdf": "^2.3.1",
|
||||
"latlon2country": "^1.1.3",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as fs from "fs";
|
||||
|
||||
function genImages() {
|
||||
function genImages(dryrun = false) {
|
||||
|
||||
console.log("Generating images")
|
||||
const dir = fs.readdirSync("./assets/svg")
|
||||
|
@ -17,7 +17,7 @@ function genImages() {
|
|||
throw "Non-svg file detected in the svg files: " + path;
|
||||
}
|
||||
|
||||
const svg = fs.readFileSync("./assets/svg/" + path, "utf-8")
|
||||
let svg = fs.readFileSync("./assets/svg/" + path, "utf-8")
|
||||
.replace(/<\?xml.*?>/, "")
|
||||
.replace(/fill: ?none;/g, "fill: none !important;") // This is such a brittle hack...
|
||||
.replace(/\n/g, " ")
|
||||
|
@ -26,12 +26,19 @@ function genImages() {
|
|||
.replace(/"/g, "\\\"")
|
||||
const name = path.substr(0, path.length - 4)
|
||||
.replace(/[ -]/g, "_");
|
||||
|
||||
if (dryrun) {
|
||||
svg = "xxx"
|
||||
}
|
||||
|
||||
module += ` public static ${name} = "${svg}"\n`
|
||||
module += ` public static ${name}_img = Img.AsImageElement(Svg.${name})\n`
|
||||
module += ` public static ${name}_svg() { return new Img(Svg.${name}, true);}\n`
|
||||
module += ` public static ${name}_ui() { return new FixedUiElement(Svg.${name}_img);}\n\n`
|
||||
if (!dryrun) {
|
||||
allNames.push(`"${path}": Svg.${name}`)
|
||||
}
|
||||
}
|
||||
module += `public static All = {${allNames.join(",")}};`
|
||||
module += "}\n";
|
||||
fs.writeFileSync("Svg.ts", module);
|
||||
|
|
|
@ -188,7 +188,7 @@ class LayerOverviewUtils {
|
|||
allTranslations
|
||||
.filter(t => t.tr.translations[neededLanguage] === undefined && t.tr.translations["*"] === undefined)
|
||||
.forEach(missing => {
|
||||
themeErrorCount.push("The theme " + theme.id + " should be translation-complete for " + neededLanguage + ", but it lacks a translation for " + missing.context+".\n\tThe full translation is "+missing.tr.translations)
|
||||
themeErrorCount.push("The theme " + theme.id + " should be translation-complete for " + neededLanguage + ", but it lacks a translation for " + missing.context+".\n\tThe english translation is "+missing.tr.textFor('en'))
|
||||
})
|
||||
}
|
||||
|
||||
|
|