diff --git a/Logic/Actors/SelectedFeatureHandler.ts b/Logic/Actors/SelectedFeatureHandler.ts
index 7d431c7af..44a2940d3 100644
--- a/Logic/Actors/SelectedFeatureHandler.ts
+++ b/Logic/Actors/SelectedFeatureHandler.ts
@@ -114,6 +114,7 @@ export default class SelectedFeatureHandler {
// Hash has been cleared - we clear the selected element
state.selectedElement.setData(undefined);
} else {
+
// we search the element to select
const feature = state.allElements.ContainingFeatures.get(h)
if (feature === undefined) {
diff --git a/Logic/BBox.ts b/Logic/BBox.ts
index 0205b1533..78634897b 100644
--- a/Logic/BBox.ts
+++ b/Logic/BBox.ts
@@ -1,5 +1,6 @@
import * as turf from "@turf/turf";
import {TileRange, Tiles} from "../Models/TileRange";
+import {GeoOperations} from "./GeoOperations";
export class BBox {
@@ -22,7 +23,7 @@ export class BBox {
this.minLon = Math.min(this.minLon, coordinate[0]);
this.minLat = Math.min(this.minLat, coordinate[1]);
}
-
+
this.maxLon = Math.min(this.maxLon, 180)
this.maxLat = Math.min(this.maxLat, 90)
this.minLon = Math.max(this.minLon, -180)
@@ -117,12 +118,12 @@ export class BBox {
}
pad(factor: number, maxIncrease = 2): BBox {
-
+
const latDiff = Math.min(maxIncrease / 2, Math.abs(this.maxLat - this.minLat) * factor)
- const lonDiff =Math.min(maxIncrease / 2, Math.abs(this.maxLon - this.minLon) * factor)
+ const lonDiff = Math.min(maxIncrease / 2, Math.abs(this.maxLon - this.minLon) * factor)
return new BBox([[
this.minLon - lonDiff,
- this.minLat - latDiff
+ this.minLat - latDiff
], [this.maxLon + lonDiff,
this.maxLat + latDiff]])
}
@@ -161,4 +162,16 @@ export class BBox {
const boundslr = Tiles.tile_bounds_lon_lat(lr.z, lr.x, lr.y)
return new BBox([].concat(boundsul, boundslr))
}
+
+ toMercator(): { minLat: number, maxLat: number, minLon: number, maxLon: number } {
+ const [minLon, minLat] = GeoOperations.ConvertWgs84To900913([this.minLon, this.minLat])
+ const [maxLon, maxLat] = GeoOperations.ConvertWgs84To900913([this.maxLon, this.maxLat])
+
+ return {
+ minLon, maxLon,
+ minLat, maxLat
+ }
+
+
+ }
}
\ No newline at end of file
diff --git a/Logic/DetermineLayout.ts b/Logic/DetermineLayout.ts
index 9af285952..563b8c1a7 100644
--- a/Logic/DetermineLayout.ts
+++ b/Logic/DetermineLayout.ts
@@ -18,7 +18,6 @@ export default class DetermineLayout {
*/
public static async GetLayout(): Promise<[LayoutConfig, string]> {
-
const loadCustomThemeParam = QueryParameters.GetQueryParameter("userlayout", "false", "If not 'false', a custom (non-official) theme is loaded. This custom layout can be done in multiple ways: \n\n- The hash of the URL contains a base64-encoded .json-file containing the theme definition\n- The hash of the URL contains a lz-compressed .json-file, as generated by the custom theme generator\n- The parameter itself is an URL, in which case that URL will be downloaded. It should point to a .json of a theme")
const layoutFromBase64 = decodeURIComponent(loadCustomThemeParam.data);
@@ -73,17 +72,13 @@ export default class DetermineLayout {
try {
- const data = await Utils.downloadJson(link)
+ const parsed = await Utils.downloadJson(link)
+ console.log("Got ", parsed)
try {
- let parsed = data;
- if (typeof parsed == "string") {
- parsed = JSON.parse(parsed);
- }
- // Overwrite the id to the url
parsed.id = link;
- return new LayoutConfig(parsed, false).patchImages(link, data);
+ return new LayoutConfig(parsed, false).patchImages(link, JSON.stringify(parsed));
} catch (e) {
-
+ console.error(e)
DetermineLayout.ShowErrorOnCustomTheme(
`${link} is invalid:`,
new FixedUiElement(e)
@@ -92,6 +87,7 @@ export default class DetermineLayout {
}
} catch (e) {
+ console.error(e)
DetermineLayout.ShowErrorOnCustomTheme(
`${link} is invalid - probably not found or invalid JSON:`,
new FixedUiElement(e)
@@ -107,7 +103,7 @@ export default class DetermineLayout {
try {
// layoutFromBase64 contains the name of the theme. This is partly to do tracking with goat counter
const dedicatedHashFromLocalStorage = LocalStorageSource.Get(
- "user-layout-" + userLayoutParam.data.replace(" ", "_")
+ "user-layout-" + userLayoutParam.data?.replace(" ", "_")
);
if (dedicatedHashFromLocalStorage.data?.length < 10) {
dedicatedHashFromLocalStorage.setData(undefined);
@@ -134,6 +130,7 @@ export default class DetermineLayout {
try {
json = JSON.parse(Utils.UnMinify(LZString.decompressFromBase64(hash)))
} catch (e) {
+ console.error(e)
DetermineLayout.ShowErrorOnCustomTheme("Could not decode the hash", new FixedUiElement("Not a valid (LZ-compressed) JSON"))
return null;
}
@@ -143,6 +140,7 @@ export default class DetermineLayout {
userLayoutParam.setData(layoutToUse.id);
return [layoutToUse, btoa(Utils.MinifyJSON(JSON.stringify(json)))];
} catch (e) {
+ console.error(e)
if (hash === undefined || hash.length < 10) {
DetermineLayout.ShowErrorOnCustomTheme("Could not load a theme from the hash", new FixedUiElement("Hash does not contain data"))
}
diff --git a/Logic/ExtraFunction.ts b/Logic/ExtraFunction.ts
index 8f0265bb1..4fc1840c1 100644
--- a/Logic/ExtraFunction.ts
+++ b/Logic/ExtraFunction.ts
@@ -222,7 +222,6 @@ export class ExtraFunction {
const maxFeatures = options?.maxFeatures ?? 1
const maxDistance = options?.maxDistance ?? 500
const uniqueTag: string | undefined = options?.uniqueTag
- console.log("Requested closestN")
if (typeof features === "string") {
const name = features
const bbox = GeoOperations.bbox(GeoOperations.buffer(GeoOperations.bbox(feature), maxDistance))
@@ -238,7 +237,7 @@ export class ExtraFunction {
let closestFeatures: { feat: any, distance: number }[] = [];
for (const featureList of features) {
for (const otherFeature of featureList) {
- if (otherFeature === feature || otherFeature.id === feature.id) {
+ if (otherFeature === feature || otherFeature.properties.id === feature.properties.id) {
continue; // We ignore self
}
const distance = GeoOperations.distanceBetween(
@@ -249,6 +248,11 @@ export class ExtraFunction {
console.error("Could not calculate the distance between", feature, "and", otherFeature)
throw "Undefined distance!"
}
+
+ if(distance === 0){
+ console.trace("Got a suspiciously zero distance between", otherFeature, "and self-feature",feature)
+ }
+
if (distance > maxDistance) {
continue
}
diff --git a/Logic/FeatureSource/FeaturePipeline.ts b/Logic/FeatureSource/FeaturePipeline.ts
index 174c18bca..a017f6818 100644
--- a/Logic/FeatureSource/FeaturePipeline.ts
+++ b/Logic/FeatureSource/FeaturePipeline.ts
@@ -98,7 +98,7 @@ export default class FeaturePipeline {
this.osmSourceZoomLevel = state.osmApiTileSize.data;
const useOsmApi = state.locationControl.map(l => l.zoom > (state.overpassMaxZoom.data ?? 12))
this.relationTracker = new RelationsTracker()
-
+
state.changes.allChanges.addCallbackAndRun(allChanges => {
allChanges.filter(ch => ch.id < 0 && ch.changes !== undefined)
.map(ch => ch.changes)
@@ -203,7 +203,9 @@ export default class FeaturePipeline {
neededTiles: neededTilesFromOsm,
handleTile: tile => {
new RegisteringAllFromFeatureSourceActor(tile)
- new SaveTileToLocalStorageActor(tile, tile.tileIndex)
+ if (tile.layer.layerDef.maxAgeOfCache > 0) {
+ new SaveTileToLocalStorageActor(tile, tile.tileIndex)
+ }
perLayerHierarchy.get(tile.layer.layerDef.id).registerTile(tile)
tile.features.addCallbackAndRunD(_ => self.newDataLoadedSignal.setData(tile))
@@ -211,7 +213,9 @@ export default class FeaturePipeline {
state: state,
markTileVisited: (tileId) =>
state.filteredLayers.data.forEach(flayer => {
- SaveTileToLocalStorageActor.MarkVisited(flayer.layerDef.id, tileId, new Date())
+ if (flayer.layerDef.maxAgeOfCache > 0) {
+ SaveTileToLocalStorageActor.MarkVisited(flayer.layerDef.id, tileId, new Date())
+ }
self.freshnesses.get(flayer.layerDef.id).addTileLoad(tileId, new Date())
})
})
@@ -260,7 +264,7 @@ export default class FeaturePipeline {
// Whenever fresh data comes in, we need to update the metatagging
- self.newDataLoadedSignal.stabilized(1000).addCallback(_ => {
+ self.newDataLoadedSignal.stabilized(250).addCallback(src => {
self.updateAllMetaTagging()
})
@@ -385,7 +389,7 @@ export default class FeaturePipeline {
window.setTimeout(
() => {
const layerDef = src.layer.layerDef;
- MetaTagging.addMetatags(
+ const somethingChanged = MetaTagging.addMetatags(
src.features.data,
{
memberships: this.relationTracker,
@@ -406,9 +410,10 @@ export default class FeaturePipeline {
private updateAllMetaTagging() {
const self = this;
+ console.debug("Updating the meta tagging of all tiles as new data got loaded")
this.perLayerHierarchy.forEach(hierarchy => {
- hierarchy.loadedTiles.forEach(src => {
- self.applyMetaTags(src)
+ hierarchy.loadedTiles.forEach(tile => {
+ self.applyMetaTags(tile)
})
})
diff --git a/Logic/FeatureSource/Sources/FilteringFeatureSource.ts b/Logic/FeatureSource/Sources/FilteringFeatureSource.ts
index 0c2c9d92a..ec097acca 100644
--- a/Logic/FeatureSource/Sources/FilteringFeatureSource.ts
+++ b/Logic/FeatureSource/Sources/FilteringFeatureSource.ts
@@ -1,5 +1,4 @@
import {UIEventSource} from "../../UIEventSource";
-import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
import FilteredLayer from "../../../Models/FilteredLayer";
import {FeatureSourceForLayer, Tiled} from "../FeatureSource";
import Hash from "../../Web/Hash";
@@ -12,6 +11,8 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti
public readonly layer: FilteredLayer;
public readonly tileIndex: number
public readonly bbox: BBox
+ private readonly upstream: FeatureSourceForLayer;
+ private readonly state: { locationControl: UIEventSource<{ zoom: number }>; selectedElement: UIEventSource };
constructor(
state: {
@@ -21,70 +22,63 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti
tileIndex,
upstream: FeatureSourceForLayer
) {
- const self = this;
this.name = "FilteringFeatureSource(" + upstream.name + ")"
this.tileIndex = tileIndex
this.bbox = BBox.fromTileIndex(tileIndex)
+ this.upstream = upstream
+ this.state = state
this.layer = upstream.layer;
const layer = upstream.layer;
-
- function update() {
-
- const features: { feature: any; freshness: Date }[] = upstream.features.data;
- const newFeatures = features.filter((f) => {
- if (
- state.selectedElement.data?.id === f.feature.id ||
- f.feature.id === Hash.hash.data) {
- // This is the selected object - it gets a free pass even if zoom is not sufficient or it is filtered away
- return true;
- }
-
- const isShown = layer.layerDef.isShown;
- const tags = f.feature.properties;
- if (isShown.IsKnown(tags)) {
- const result = layer.layerDef.isShown.GetRenderValue(
- f.feature.properties
- ).txt;
- if (result !== "yes") {
- return false;
- }
- }
-
- const tagsFilter = layer.appliedFilters.data;
- for (const filter of tagsFilter ?? []) {
- const neededTags = filter.filter.options[filter.selected].osmTags
- if (!neededTags.matchesProperties(f.feature.properties)) {
- // Hidden by the filter on the layer itself - we want to hide it no matter wat
- return false;
- }
- }
-
-
- return true;
- });
-
- self.features.setData(newFeatures);
- }
-
+
upstream.features.addCallback(() => {
- update();
+ this. update();
});
layer.appliedFilters.addCallback(_ => {
- update()
+ this.update()
})
- update();
+ this.update();
+ }
+ public update() {
+
+ const layer = this.upstream.layer;
+ const features: { feature: any; freshness: Date }[] = this.upstream.features.data;
+ const newFeatures = features.filter((f) => {
+ if (
+ this.state.selectedElement.data?.id === f.feature.id ||
+ f.feature.id === Hash.hash.data) {
+ // This is the selected object - it gets a free pass even if zoom is not sufficient or it is filtered away
+ return true;
+ }
+
+ const isShown = layer.layerDef.isShown;
+ const tags = f.feature.properties;
+ if (isShown.IsKnown(tags)) {
+ const result = layer.layerDef.isShown.GetRenderValue(
+ f.feature.properties
+ ).txt;
+ if (result !== "yes") {
+ return false;
+ }
+ }
+
+ const tagsFilter = layer.appliedFilters.data;
+ for (const filter of tagsFilter ?? []) {
+ const neededTags = filter.filter.options[filter.selected].osmTags
+ if (!neededTags.matchesProperties(f.feature.properties)) {
+ // Hidden by the filter on the layer itself - we want to hide it no matter wat
+ return false;
+ }
+ }
+
+
+ return true;
+ });
+
+ this.features.setData(newFeatures);
}
- private static showLayer(
- layer: {
- isDisplayed: UIEventSource;
- layerDef: LayerConfig;
- }) {
- return layer.isDisplayed.data;
-
- }
}
diff --git a/Logic/FeatureSource/Sources/GeoJsonSource.ts b/Logic/FeatureSource/Sources/GeoJsonSource.ts
index be93dedc7..68f8fab82 100644
--- a/Logic/FeatureSource/Sources/GeoJsonSource.ts
+++ b/Logic/FeatureSource/Sources/GeoJsonSource.ts
@@ -7,6 +7,7 @@ import {Utils} from "../../../Utils";
import {FeatureSourceForLayer, Tiled} from "../FeatureSource";
import {Tiles} from "../../../Models/TileRange";
import {BBox} from "../../BBox";
+import {GeoOperations} from "../../GeoOperations";
export default class GeoJsonSource implements FeatureSourceForLayer, Tiled {
@@ -14,7 +15,6 @@ export default class GeoJsonSource implements FeatureSourceForLayer, Tiled {
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>;
public readonly name;
public readonly isOsmCache: boolean
- private onFail: ((errorMsg: any, url: string) => void) = undefined;
private readonly seenids: Set = new Set()
public readonly layer: FilteredLayer;
@@ -44,10 +44,20 @@ export default class GeoJsonSource implements FeatureSourceForLayer, Tiled {
let url = flayer.layerDef.source.geojsonSource.replace("{layer}", flayer.layerDef.id);
if (zxy !== undefined) {
const [z, x, y] = zxy;
+ let tile_bbox = BBox.fromTile(z, x, y)
+ let bounds : { minLat: number, maxLat: number, minLon: number, maxLon: number } = tile_bbox
+ if(this.layer.layerDef.source.mercatorCrs){
+ bounds = tile_bbox.toMercator()
+ }
url = url
.replace('{z}', "" + z)
.replace('{x}', "" + x)
.replace('{y}', "" + y)
+ .replace('{y_min}',""+bounds.minLat)
+ .replace('{y_max}',""+bounds.maxLat)
+ .replace('{x_min}',""+bounds.minLon)
+ .replace('{x_max}',""+bounds.maxLon)
+
this.tileIndex = Tiles.tile_index(z, x, y)
this.bbox = BBox.fromTile(z, x, y)
} else {
@@ -71,6 +81,10 @@ export default class GeoJsonSource implements FeatureSourceForLayer, Tiled {
if(json.features === undefined || json.features === null){
return;
}
+
+ if(self.layer.layerDef.source.mercatorCrs){
+ json = GeoOperations.GeoJsonToWGS84(json)
+ }
const time = new Date();
const newFeatures: { feature: any, freshness: Date } [] = []
diff --git a/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts b/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts
index 0a1c67f41..92138b995 100644
--- a/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts
+++ b/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts
@@ -31,7 +31,6 @@ export class NewGeometryFromChangesFeatureSource implements FeatureSource {
// Already handled
!seenChanges.has(ch)))
.addCallbackAndRunD(changes => {
-
if (changes.length === 0) {
return;
}
diff --git a/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts b/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts
index 5032f53ea..21aeec1c1 100644
--- a/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts
+++ b/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts
@@ -20,24 +20,28 @@ export default class DynamicGeoJsonTileSource extends DynamicTileSource {
if (source.geojsonSource === undefined) {
throw "Invalid layer: geojsonSource expected"
}
-
- const whitelistUrl = source.geojsonSource
- .replace("{z}", ""+source.geojsonZoomLevel)
- .replace("{x}_{y}.geojson", "overview.json")
- .replace("{layer}",layer.layerDef.id)
-
+
let whitelist = undefined
- Utils.downloadJson(whitelistUrl).then(
- json => {
- const data = new Map>();
- for (const x in json) {
- data.set(Number(x), new Set(json[x]))
+ if (source.geojsonSource.indexOf("{x}_{y}.geojson") > 0) {
+
+ const whitelistUrl = source.geojsonSource
+ .replace("{z}", "" + source.geojsonZoomLevel)
+ .replace("{x}_{y}.geojson", "overview.json")
+ .replace("{layer}", layer.layerDef.id)
+
+ Utils.downloadJson(whitelistUrl).then(
+ json => {
+ const data = new Map>();
+ for (const x in json) {
+ data.set(Number(x), new Set(json[x]))
+ }
+ console.log("The whitelist is", data, "based on ", json, "from", whitelistUrl)
+ whitelist = data
}
- whitelist = data
- }
- ).catch(err => {
- console.warn("No whitelist found for ", layer.layerDef.id, err)
- })
+ ).catch(err => {
+ console.warn("No whitelist found for ", layer.layerDef.id, err)
+ })
+ }
const seenIds = new Set();
const blackList = new UIEventSource(seenIds)
@@ -45,14 +49,14 @@ export default class DynamicGeoJsonTileSource extends DynamicTileSource {
layer,
source.geojsonZoomLevel,
(zxy) => {
- if(whitelist !== undefined){
+ if (whitelist !== undefined) {
const isWhiteListed = whitelist.get(zxy[1])?.has(zxy[2])
- if(!isWhiteListed){
+ if (!isWhiteListed) {
console.log("Not downloading tile", ...zxy, "as it is not on the whitelist")
return undefined;
}
}
-
+
const src = new GeoJsonSource(
layer,
zxy,
diff --git a/Logic/GeoOperations.ts b/Logic/GeoOperations.ts
index 87716003e..09a288117 100644
--- a/Logic/GeoOperations.ts
+++ b/Logic/GeoOperations.ts
@@ -226,7 +226,7 @@ export class GeoOperations {
/**
* Generates the closest point on a way from a given point
- *
+ *
* The properties object will contain three values:
// - `index`: closest point was found on nth line part,
// - `dist`: distance between pt and the closest point (in kilometer),
@@ -283,6 +283,34 @@ export class GeoOperations {
return headerValuesOrdered.map(v => JSON.stringify(v)).join(",") + "\n" + lines.join("\n")
}
+
+ private static readonly _earthRadius = 6378137;
+ private static readonly _originShift = 2 * Math.PI * GeoOperations._earthRadius / 2;
+
+ //Converts given lat/lon in WGS84 Datum to XY in Spherical Mercator EPSG:900913
+ public static ConvertWgs84To900913(lonLat: [number, number]): [number, number] {
+ const lon = lonLat[0];
+ const lat = lonLat[1];
+ const x = lon * GeoOperations._originShift / 180;
+ let y = Math.log(Math.tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180);
+ y = y * GeoOperations._originShift / 180;
+ return [x, y];
+ }
+
+//Converts XY point from (Spherical) Web Mercator EPSG:3785 (unofficially EPSG:900913) to lat/lon in WGS84 Datum
+ public static Convert900913ToWgs84(lonLat: [number, number]): [number, number] {
+ const lon = lonLat[0]
+ const lat = lonLat[1]
+ const x = 180 * lon / GeoOperations._originShift;
+ let y = 180 * lat / GeoOperations._originShift;
+ y = 180 / Math.PI * (2 * Math.atan(Math.exp(y * Math.PI / 180)) - Math.PI / 2);
+ return [x, y];
+ }
+
+ public static GeoJsonToWGS84(geojson){
+ return turf.toWgs84(geojson)
+ }
+
/**
* Calculates the intersection between two features.
* Returns the length if intersecting a linestring and a (multi)polygon (in meters), returns a surface area (in m²) if intersecting two (multi)polygons
diff --git a/Logic/MetaTagging.ts b/Logic/MetaTagging.ts
index 83b85f34a..a6e3cc44b 100644
--- a/Logic/MetaTagging.ts
+++ b/Logic/MetaTagging.ts
@@ -18,6 +18,8 @@ export default class MetaTagging {
/**
* This method (re)calculates all metatags and calculated tags on every given object.
* The given features should be part of the given layer
+ *
+ * Returns true if at least one feature has changed properties
*/
public static addMetatags(features: { feature: any; freshness: Date }[],
params: ExtraFuncParams,
@@ -25,7 +27,7 @@ export default class MetaTagging {
options?: {
includeDates?: true | boolean,
includeNonDates?: true | boolean
- }) {
+ }): boolean {
if (features === undefined || features.length === 0) {
return;
@@ -48,6 +50,7 @@ export default class MetaTagging {
// The calculated functions - per layer - which add the new keys
const layerFuncs = this.createRetaggingFunc(layer)
+ let atLeastOneFeatureChanged = false;
for (let i = 0; i < features.length; i++) {
const ff = features[i];
@@ -95,8 +98,10 @@ export default class MetaTagging {
if (somethingChanged) {
State.state?.allElements?.getEventSourceById(feature.properties.id)?.ping()
+ atLeastOneFeatureChanged = true
}
}
+ return atLeastOneFeatureChanged
}
diff --git a/Models/ThemeConfig/Json/LayerConfigJson.ts b/Models/ThemeConfig/Json/LayerConfigJson.ts
index 52b524dd0..1d0e7f8fd 100644
--- a/Models/ThemeConfig/Json/LayerConfigJson.ts
+++ b/Models/ThemeConfig/Json/LayerConfigJson.ts
@@ -55,6 +55,8 @@ export interface LayerConfigJson {
* source: {geoJson: "https://my.source.net/some-tile-geojson-{layer}-{z}-{x}-{y}.geojson", geoJsonZoomLevel: 14}
* to use a tiled geojson source. The web server must offer multiple geojsons. {z}, {x} and {y} are substituted by the location; {layer} is substituted with the id of the loaded layer
*
+ * Some API's use a BBOX instead of a tile, this can be used by specifying {y_min}, {y_max}, {x_min} and {x_max}
+ * Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this
*
* Note that both geojson-options might set a flag 'isOsmCache' indicating that the data originally comes from OSM too
*
@@ -63,7 +65,7 @@ export interface LayerConfigJson {
* While still supported, this is considered deprecated
*/
source: ({ osmTags: AndOrTagConfigJson | string, overpassScript?: string } |
- { osmTags: AndOrTagConfigJson | string, geoJson: string, geoJsonZoomLevel?: number, isOsmCache?: boolean }) & ({
+ { osmTags: AndOrTagConfigJson | string, geoJson: string, geoJsonZoomLevel?: number, isOsmCache?: boolean, mercatorCrs?: boolean }) & ({
/**
* The maximum amount of seconds that a tile is allowed to linger in the cache
*/
diff --git a/Models/ThemeConfig/Json/TagRenderingConfigJson.ts b/Models/ThemeConfig/Json/TagRenderingConfigJson.ts
index 456c693b5..ee39cfb92 100644
--- a/Models/ThemeConfig/Json/TagRenderingConfigJson.ts
+++ b/Models/ThemeConfig/Json/TagRenderingConfigJson.ts
@@ -89,6 +89,7 @@ export interface TagRenderingConfigJson {
* Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes
*/
mappings?: {
+
/**
* If this condition is met, then the text under `then` will be shown.
* If no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.
@@ -173,5 +174,12 @@ export interface TagRenderingConfigJson {
* If this is important to your usecase, consider using multiple radiobutton-fields without `multiAnswer`
*/
ifnot?: AndOrTagConfigJson | string
+
+ /**
+ * If chosen as answer, these tags will be applied as well onto the object.
+ * Not compatible with multiAnswer
+ */
+ addExtraTags: string[]
+
}[]
}
\ No newline at end of file
diff --git a/Models/ThemeConfig/LayerConfig.ts b/Models/ThemeConfig/LayerConfig.ts
index e705e440a..b09ffcb60 100644
--- a/Models/ThemeConfig/LayerConfig.ts
+++ b/Models/ThemeConfig/LayerConfig.ts
@@ -96,6 +96,7 @@ export default class LayerConfig extends WithContextLoader {
geojsonSourceLevel: json.source["geoJsonZoomLevel"],
overpassScript: json.source["overpassScript"],
isOsmCache: json.source["isOsmCache"],
+ mercatorCrs: json.source["mercatorCrs"]
},
json.id
);
diff --git a/Models/ThemeConfig/LayoutConfig.ts b/Models/ThemeConfig/LayoutConfig.ts
index 1d93ca27f..bb492e047 100644
--- a/Models/ThemeConfig/LayoutConfig.ts
+++ b/Models/ThemeConfig/LayoutConfig.ts
@@ -270,7 +270,6 @@ export default class LayoutConfig {
}
rewriting.forEach((value, key) => {
console.log("Rewriting", key, "==>", value)
-
originalJson = originalJson.replace(new RegExp(key, "g"), value)
})
return new LayoutConfig(JSON.parse(originalJson), false, "Layout rewriting")
diff --git a/Models/ThemeConfig/SourceConfig.ts b/Models/ThemeConfig/SourceConfig.ts
index 1d223bd2b..b378d0acd 100644
--- a/Models/ThemeConfig/SourceConfig.ts
+++ b/Models/ThemeConfig/SourceConfig.ts
@@ -1,4 +1,5 @@
import {TagsFilter} from "../../Logic/Tags/TagsFilter";
+import {RegexTag} from "../../Logic/Tags/RegexTag";
export default class SourceConfig {
@@ -7,8 +8,10 @@ export default class SourceConfig {
public readonly geojsonSource?: string;
public readonly geojsonZoomLevel?: number;
public readonly isOsmCacheLayer: boolean;
+ public readonly mercatorCrs: boolean;
constructor(params: {
+ mercatorCrs?: boolean;
osmTags?: TagsFilter,
overpassScript?: string,
geojsonSource?: string,
@@ -33,10 +36,15 @@ export default class SourceConfig {
console.error(params)
throw `Source said it is a OSM-cached layer, but didn't define the actual source of the cache (in context ${context})`
}
- this.osmTags = params.osmTags;
+ if(params.geojsonSource !== undefined && params.geojsonSourceLevel !== undefined){
+ if(! ["x","y","x_min","x_max","y_min","Y_max"].some(toSearch => params.geojsonSource.indexOf(toSearch) > 0)){
+ throw `Source defines a geojson-zoomLevel, but does not specify {x} nor {y} (or equivalent), this is probably a bug (in context ${context})`
+ }}
+ this.osmTags = params.osmTags ?? new RegexTag("id",/.*/);
this.overpassScript = params.overpassScript;
this.geojsonSource = params.geojsonSource;
this.geojsonZoomLevel = params.geojsonSourceLevel;
this.isOsmCacheLayer = params.isOsmCache ?? false;
+ this.mercatorCrs = params.mercatorCrs ?? false;
}
}
\ No newline at end of file
diff --git a/Models/ThemeConfig/TagRenderingConfig.ts b/Models/ThemeConfig/TagRenderingConfig.ts
index 5f223e6d6..7ef2c5fec 100644
--- a/Models/ThemeConfig/TagRenderingConfig.ts
+++ b/Models/ThemeConfig/TagRenderingConfig.ts
@@ -6,6 +6,7 @@ import {TagUtils} from "../../Logic/Tags/TagUtils";
import {And} from "../../Logic/Tags/And";
import ValidatedTextField from "../../UI/Input/ValidatedTextField";
import {Utils} from "../../Utils";
+import {Tag} from "../../Logic/Tags/Tag";
/***
* The parsed version of TagRenderingConfigJSON
@@ -37,6 +38,7 @@ export default class TagRenderingConfig {
readonly ifnot?: TagsFilter,
readonly then: Translation
readonly hideInAnswer: boolean | TagsFilter
+ readonly addExtraTags: Tag[]
}[]
constructor(json: string | TagRenderingConfigJson, context?: string) {
@@ -118,21 +120,24 @@ export default class TagRenderingConfig {
this.mappings = json.mappings.map((mapping, i) => {
-
+ const ctx = `${context}.mapping[${i}]`
if (mapping.then === undefined) {
- throw `${context}.mapping[${i}]: Invalid mapping: if without body`
+ throw `${ctx}: Invalid mapping: if without body`
}
if (mapping.ifnot !== undefined && !this.multiAnswer) {
- throw `${context}.mapping[${i}]: Invalid mapping: ifnot defined, but the tagrendering is not a multianswer`
+ throw `${ctx}: Invalid mapping: ifnot defined, but the tagrendering is not a multianswer`
}
if (mapping.if === undefined) {
- throw `${context}.mapping[${i}]: Invalid mapping: "if" is not defined, but the tagrendering is not a multianswer`
+ throw `${ctx}: Invalid mapping: "if" is not defined, but the tagrendering is not a multianswer`
}
if (typeof mapping.if !== "string" && mapping.if["length"] !== undefined) {
- throw `${context}.mapping[${i}]: Invalid mapping: "if" is defined as an array. Use {"and": } or {"or": } instead`
+ throw `${ctx}: Invalid mapping: "if" is defined as an array. Use {"and": } or {"or": } instead`
+ }
+
+ if(mapping.addExtraTags !== undefined && this.multiAnswer){
+ throw `${ctx}: Invalid mapping: got a multi-Answer with addExtraTags; this is not allowed`
}
-
let hideInAnswer: boolean | TagsFilter = false;
if (typeof mapping.hideInAnswer === "boolean") {
@@ -140,12 +145,12 @@ export default class TagRenderingConfig {
} else if (mapping.hideInAnswer !== undefined) {
hideInAnswer = TagUtils.Tag(mapping.hideInAnswer, `${context}.mapping[${i}].hideInAnswer`);
}
- const mappingContext = `${context}.mapping[${i}]`
const mp = {
- if: TagUtils.Tag(mapping.if, `${mappingContext}.if`),
- ifnot: (mapping.ifnot !== undefined ? TagUtils.Tag(mapping.ifnot, `${mappingContext}.ifnot`) : undefined),
- then: Translations.T(mapping.then, `${mappingContext}.then`),
- hideInAnswer: hideInAnswer
+ if: TagUtils.Tag(mapping.if, `${ctx}.if`),
+ ifnot: (mapping.ifnot !== undefined ? TagUtils.Tag(mapping.ifnot, `${ctx}.ifnot`) : undefined),
+ then: Translations.T(mapping.then, `${ctx}.then`),
+ hideInAnswer: hideInAnswer,
+ addExtraTags: (mapping.addExtraTags??[]).map((str, j) => TagUtils.SimpleTag(str, `${ctx}.addExtraTags[${j}]`))
};
if (this.question) {
if (hideInAnswer !== true && mp.if !== undefined && !mp.if.isUsableAsAnswer()) {
diff --git a/UI/Base/ScrollableFullScreen.ts b/UI/Base/ScrollableFullScreen.ts
index b9f134565..248f9a884 100644
--- a/UI/Base/ScrollableFullScreen.ts
+++ b/UI/Base/ScrollableFullScreen.ts
@@ -19,6 +19,7 @@ import Img from "./Img";
export default class ScrollableFullScreen extends UIElement {
private static readonly empty = new FixedUiElement("");
private static _currentlyOpen: ScrollableFullScreen;
+ private hashToShow: string;
public isShown: UIEventSource;
private _component: BaseUIElement;
private _fullscreencomponent: BaseUIElement;
@@ -28,6 +29,7 @@ export default class ScrollableFullScreen extends UIElement {
isShown: UIEventSource = new UIEventSource(false)
) {
super();
+ this.hashToShow = hashToShow;
this.isShown = isShown;
if (hashToShow === undefined) {
@@ -45,24 +47,25 @@ export default class ScrollableFullScreen extends UIElement {
self.Activate();
Hash.hash.setData(hashToShow)
} else {
- ScrollableFullScreen.clear();
+ self.clear();
}
})
Hash.hash.addCallback(hash => {
- if (hash === hashToShow) {
- return
+ if (!isShown.data) {
+ return;
+ }
+ if (hash === undefined || hash === "") {
+ isShown.setData(false)
}
- isShown.setData(false)
})
}
- private static clear() {
+ private clear() {
ScrollableFullScreen.empty.AttachTo("fullscreen")
const fs = document.getElementById("fullscreen");
ScrollableFullScreen._currentlyOpen?.isShown?.setData(false);
fs.classList.add("hidden")
- Hash.hash.setData(undefined);
}
InnerRender(): BaseUIElement {
diff --git a/UI/Base/TabbedComponent.ts b/UI/Base/TabbedComponent.ts
index f911c0b41..e151df6c5 100644
--- a/UI/Base/TabbedComponent.ts
+++ b/UI/Base/TabbedComponent.ts
@@ -21,6 +21,9 @@ export class TabbedComponent extends Combine {
let element = elements[i];
const header = Translations.W(element.header).onClick(() => openedTabSrc.setData(i))
openedTabSrc.addCallbackAndRun(selected => {
+ if(selected >= elements.length){
+ selected = 0
+ }
if (selected === i) {
header.SetClass("tab-active")
header.RemoveClass("tab-non-active")
diff --git a/UI/DefaultGUI.ts b/UI/DefaultGUI.ts
index e259b4f2e..f6c551663 100644
--- a/UI/DefaultGUI.ts
+++ b/UI/DefaultGUI.ts
@@ -114,10 +114,8 @@ export default class DefaultGUI {
Utils.LoadCustomCss(state.layoutToUse.customCss);
}
-
this.SetupUIElements();
this.SetupMap()
-
}
diff --git a/UI/Popup/FeatureInfoBox.ts b/UI/Popup/FeatureInfoBox.ts
index cebc1fe9f..cfffd0ffe 100644
--- a/UI/Popup/FeatureInfoBox.ts
+++ b/UI/Popup/FeatureInfoBox.ts
@@ -111,7 +111,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen {
layerConfig.allowMove
);
})
- )
+ ).SetClass("text-base")
);
}
@@ -122,14 +122,14 @@ export default class FeatureInfoBox extends ScrollableFullScreen {
id,
layerConfig.deletion
))
- ))
+ ).SetClass("text-base"))
}
if (layerConfig.allowSplit) {
editElements.push(
new VariableUiElement(tags.map(tags => tags.id).map(id =>
new SplitRoadWizard(id))
- ))
+ ).SetClass("text-base"))
}
diff --git a/UI/Popup/QuestionBox.ts b/UI/Popup/QuestionBox.ts
index 1192ff8ee..5dfa062ea 100644
--- a/UI/Popup/QuestionBox.ts
+++ b/UI/Popup/QuestionBox.ts
@@ -16,6 +16,7 @@ import Lazy from "../Base/Lazy";
export default class QuestionBox extends VariableUiElement {
constructor(tagsSource: UIEventSource, tagRenderings: TagRenderingConfig[], units: Unit[]) {
+
const skippedQuestions: UIEventSource = new UIEventSource([])
tagRenderings = tagRenderings
@@ -33,7 +34,7 @@ export default class QuestionBox extends VariableUiElement {
{
units: units,
afterSave: () => {
- // We save
+ // We save and indicate progress by pinging and recalculating
skippedQuestions.ping();
},
cancelButton: Translations.t.general.skip.Clone()
@@ -45,7 +46,7 @@ export default class QuestionBox extends VariableUiElement {
}
)));
- const skippedQuestionsButton = Translations.t.general.skippedQuestions.Clone()
+ const skippedQuestionsButton = Translations.t.general.skippedQuestions
.onClick(() => {
skippedQuestions.setData([]);
})
diff --git a/UI/Popup/TagRenderingQuestion.ts b/UI/Popup/TagRenderingQuestion.ts
index 17adc87ce..9c0c530ab 100644
--- a/UI/Popup/TagRenderingQuestion.ts
+++ b/UI/Popup/TagRenderingQuestion.ts
@@ -48,7 +48,7 @@ export default class TagRenderingQuestion extends Combine {
const applicableMappingsSrc =
UIEventSource.ListStabilized(tags.map(tags => {
- const applicableMappings: { if: TagsFilter, then: any, ifnot?: TagsFilter }[] = []
+ const applicableMappings: { if: TagsFilter, then: any, ifnot?: TagsFilter, addExtraTags: Tag[] }[] = []
for (const mapping of configuration.mappings ?? []) {
if (mapping.hideInAnswer === true) {
continue
@@ -107,9 +107,9 @@ export default class TagRenderingQuestion extends Combine {
const saveButton = new Combine([
options.saveButtonConstr(inputElement.GetValue()),
- new Toggle(Translations.t.general.testing, undefined, State.state.featureSwitchIsTesting).SetClass("alert")
+ new Toggle(Translations.t.general.testing.SetClass("alert"), undefined, State.state.featureSwitchIsTesting)
])
-
+
let bottomTags: BaseUIElement;
if (options.bottomText !== undefined) {
bottomTags = options.bottomText(inputElement.GetValue())
@@ -146,7 +146,7 @@ export default class TagRenderingQuestion extends Combine {
private static GenerateInputElement(configuration: TagRenderingConfig,
- applicableMappings: { if: TagsFilter, then: any, ifnot?: TagsFilter }[],
+ applicableMappings: { if: TagsFilter, then: any, ifnot?: TagsFilter, addExtraTags: Tag[] }[],
applicableUnit: Unit,
tagsSource: UIEventSource)
: InputElement {
@@ -340,12 +340,16 @@ export default class TagRenderingQuestion extends Combine {
mapping: {
if: TagsFilter,
then: Translation,
+ addExtraTags: Tag[]
}, ifNot?: TagsFilter[]): InputElement {
let tagging: TagsFilter = mapping.if;
if (ifNot !== undefined) {
tagging = new And([mapping.if, ...ifNot])
}
+ if (mapping.addExtraTags) {
+ tagging = new And([tagging, ...mapping.addExtraTags])
+ }
return new FixedInputElement(
new SubstitutedTranslation(mapping.then, tagsSource),
diff --git a/UI/ShowDataLayer/ShowDataLayer.ts b/UI/ShowDataLayer/ShowDataLayer.ts
index 589d2f726..e2b5f6440 100644
--- a/UI/ShowDataLayer/ShowDataLayer.ts
+++ b/UI/ShowDataLayer/ShowDataLayer.ts
@@ -281,7 +281,6 @@ export default class ShowDataLayer {
infobox.isShown.addCallback(isShown => {
if (!isShown) {
- this._selectedElement?.setData(undefined);
leafletLayer.closePopup()
}
});
@@ -293,7 +292,7 @@ export default class ShowDataLayer {
}
});
-
+
// Add the feature to the index to open the popup when needed
this.leafletLayersPerId.set(feature.properties.id + feature.geometry.type, {
diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts
index 6d0784b7d..1563cf3f5 100644
--- a/UI/SpecialVisualizations.ts
+++ b/UI/SpecialVisualizations.ts
@@ -508,7 +508,7 @@ There are also some technicalities in your theme to keep in mind:
return new Combine([new FixedUiElement("The import button is disabled for unofficial themes to prevent accidents.").SetClass("alert"),
new FixedUiElement("To test, add 'test=true' to the URL. The changeset will be printed in the console. Please open a PR to officialize this theme to actually enable the import button.")])
}
- const tgsSpec = args[0].split(",").map(spec => {
+ const tgsSpec = args[0].split(";").map(spec => {
const kv = spec.split("=").map(s => s.trim());
if (kv.length != 2) {
throw "Invalid key spec: multiple '=' found in " + spec
diff --git a/assets/layers/charging_station/charging_station.json b/assets/layers/charging_station/charging_station.json
index 28e97ecd1..7427e6b4e 100644
--- a/assets/layers/charging_station/charging_station.json
+++ b/assets/layers/charging_station/charging_station.json
@@ -32,7 +32,8 @@
"#": "Allowed vehicle types",
"question": {
"en": "Which vehicles are allowed to charge here?",
- "nl": "Welke voertuigen kunnen hier opgeladen worden?"
+ "nl": "Welke voertuigen kunnen hier opgeladen worden?",
+ "de": "Welche Fahrzeuge dürfen hier geladen werden?"
},
"multiAnswer": true,
"mappings": [
@@ -40,8 +41,9 @@
"if": "bicycle=yes",
"ifnot": "bicycle=no",
"then": {
- "en": "Bcycles can be charged here",
- "nl": "Fietsen kunnen hier opgeladen worden"
+ "en": "bicycles can be charged here",
+ "nl": "Fietsen kunnen hier opgeladen worden",
+ "de": "Fahrräder können hier geladen werden"
}
},
{
@@ -49,7 +51,8 @@
"ifnot": "motorcar=no",
"then": {
"en": "Cars can be charged here",
- "nl": "Elektrische auto's kunnen hier opgeladen worden"
+ "nl": "Elektrische auto's kunnen hier opgeladen worden",
+ "de": "Autos können hier geladen werden"
}
},
{
@@ -57,7 +60,8 @@
"ifnot": "scooter=no",
"then": {
"en": "Scooters can be charged here",
- "nl": "Electrische scooters (snorfiets of bromfiets) kunnen hier opgeladen worden"
+ "nl": "Electrische scooters (snorfiets of bromfiets) kunnen hier opgeladen worden",
+ "de": " Roller können hier geladen werden"
}
},
{
@@ -65,7 +69,8 @@
"ifnot": "hgv=no",
"then": {
"en": "Heavy good vehicles (such as trucks) can be charged here",
- "nl": "Vrachtwagens kunnen hier opgeladen worden"
+ "nl": "Vrachtwagens kunnen hier opgeladen worden",
+ "de": "Lastkraftwagen (LKW) können hier geladen werden"
}
},
{
@@ -73,7 +78,8 @@
"ifnot": "bus=no",
"then": {
"en": "Buses can be charged here",
- "nl": "Bussen kunnen hier opgeladen worden"
+ "nl": "Bussen kunnen hier opgeladen worden",
+ "de": "Busse können hier geladen werden"
}
}
]
@@ -82,11 +88,13 @@
"id": "access",
"question": {
"en": "Who is allowed to use this charging station?",
- "nl": "Wie mag er dit oplaadpunt gebruiken?"
+ "nl": "Wie mag er dit oplaadpunt gebruiken?",
+ "de": "Wer darf diese Ladestation benutzen?"
},
"render": {
"en": "Access is {access}",
- "nl": "Toegang voor {access}"
+ "nl": "Toegang voor {access}",
+ "de": "Zugang ist {access}"
},
"freeform": {
"key": "access",
@@ -119,14 +127,14 @@
"if": "access=customers",
"then": {
"en": "Only customers of the place this station belongs to can use this charging station
E.g. a charging station operated by hotel which is only usable by their guests",
- "nl": "Enkel klanten van de bijhorende plaats mogen dit oplaadpunt gebruiken
Bv. op de parking van een hotel en enkel toegankelijk voor klanten van dit hotel"
+ "nl": "Enkel klanten van de bijhorende plaats mogen dit oplaadpunt gebruiken
Bijvoorbeeld een oplaadpunt op de parking van een restaurant dat enkel door klanten van het restaurant gebruikt mag worden"
}
},
{
"if": "access=private",
"then": {
"en": "Not accessible to the general public (e.g. only accessible to the owners, employees, ...)",
- "nl": "Niet toegankelijk voor het publiek
Bv. enkel toegankelijk voor de eigenaar, medewerkers ,... "
+ "nl": "Niet toegankelijk voor het publiek Enkel toegankelijk voor de eigenaar, medewerkers ,... "
}
}
]
@@ -135,11 +143,13 @@
"id": "capacity",
"render": {
"en": "{capacity} vehicles can be charged here at the same time",
- "nl": "{capacity} voertuigen kunnen hier op hetzelfde moment opgeladen worden"
+ "nl": "{capacity} voertuigen kunnen hier op hetzelfde moment opgeladen worden",
+ "de": "{capacity} Fahrzeuge können hier gleichzeitig geladen werden"
},
"question": {
"en": "How much vehicles can be charged here at the same time?",
- "nl": "Hoeveel voertuigen kunnen hier opgeladen worden?"
+ "nl": "Hoeveel voertuigen kunnen hier opgeladen worden?",
+ "de": "Wie viele Fahrzeuge können hier gleichzeitig geladen werden?"
},
"freeform": {
"key": "capacity",
@@ -149,8 +159,9 @@
{
"id": "Available_charging_stations (generated)",
"question": {
- "en": "Which charging connections are available here?",
- "nl": "Welke aansluitingen zijn hier beschikbaar?"
+ "en": "Which charging stations are available here?",
+ "nl": "Welke aansluitingen zijn hier beschikbaar?",
+ "de": "Welche Ladestationen gibt es hier?"
},
"multiAnswer": true,
"mappings": [
@@ -251,7 +262,8 @@
},
"then": {
"en": " Chademo ",
- "nl": " Chademo "
+ "nl": " Chademo ",
+ "de": " Chademo "
},
"hideInAnswer": true
},
@@ -260,7 +272,8 @@
"ifnot": "socket:type1_cable=",
"then": {
"en": " Type 1 with cable (J1772) ",
- "nl": " Type 1 met kabel (J1772) "
+ "nl": " Type 1 met kabel (J1772) ",
+ "de": " Typ 1 mit Kabel (J1772) "
},
"hideInAnswer": {
"or": [
@@ -298,7 +311,8 @@
},
"then": {
"en": " Type 1 with cable (J1772) ",
- "nl": " Type 1 met kabel (J1772) "
+ "nl": " Type 1 met kabel (J1772) ",
+ "de": " Typ 1 mit Kabel (J1772) "
},
"hideInAnswer": true
},
@@ -307,7 +321,8 @@
"ifnot": "socket:type1=",
"then": {
"en": " Type 1 without cable (J1772) ",
- "nl": " Type 1 zonder kabel (J1772) "
+ "nl": " Type 1 zonder kabel (J1772) ",
+ "de": " Typ 1 ohne Kabel (J1772) "
},
"hideInAnswer": {
"or": [
@@ -345,7 +360,8 @@
},
"then": {
"en": " Type 1 without cable (J1772) ",
- "nl": " Type 1 zonder kabel (J1772) "
+ "nl": " Type 1 zonder kabel (J1772) ",
+ "de": " Typ 1 ohne Kabel (J1772) "
},
"hideInAnswer": true
},
@@ -354,7 +370,8 @@
"ifnot": "socket:type1_combo=",
"then": {
"en": " Type 1 CCS (aka Type 1 Combo) ",
- "nl": " Type 1 CCS (ook gekend als Type 1 Combo) "
+ "nl": " Type 1 CCS (ook gekend als Type 1 Combo) ",
+ "de": " Typ 1 CCS (auch bekannt als Typ 1 Combo) "
},
"hideInAnswer": {
"or": [
@@ -392,7 +409,8 @@
},
"then": {
"en": " Type 1 CCS (aka Type 1 Combo) ",
- "nl": " Type 1 CCS (ook gekend als Type 1 Combo) "
+ "nl": " Type 1 CCS (ook gekend als Type 1 Combo) ",
+ "de": " Typ 1 CCS (auch bekannt als Typ 1 Combo) "
},
"hideInAnswer": true
},
@@ -401,7 +419,8 @@
"ifnot": "socket:tesla_supercharger=",
"then": {
"en": " Tesla Supercharger ",
- "nl": " Tesla Supercharger "
+ "nl": " Tesla Supercharger ",
+ "de": " Tesla Supercharger "
},
"hideInAnswer": {
"or": [
@@ -439,7 +458,8 @@
},
"then": {
"en": " Tesla Supercharger ",
- "nl": " Tesla Supercharger "
+ "nl": " Tesla Supercharger ",
+ "de": " Tesla Supercharger "
},
"hideInAnswer": true
},
@@ -448,7 +468,8 @@
"ifnot": "socket:type2=",
"then": {
"en": " Type 2 (mennekes) ",
- "nl": " Type 2 (mennekes) "
+ "nl": " Type 2 (mennekes) ",
+ "de": " Typ 2 (Mennekes) "
},
"hideInAnswer": {
"or": [
@@ -486,7 +507,8 @@
},
"then": {
"en": " Type 2 (mennekes) ",
- "nl": " Type 2 (mennekes) "
+ "nl": " Type 2 (mennekes) ",
+ "de": " Typ 2 (Mennekes) "
},
"hideInAnswer": true
},
@@ -495,7 +517,8 @@
"ifnot": "socket:type2_combo=",
"then": {
"en": " Type 2 CCS (mennekes) ",
- "nl": " Type 2 CCS (mennekes) "
+ "nl": " Type 2 CCS (mennekes) ",
+ "de": " Typ 2 CCS (Mennekes) "
},
"hideInAnswer": {
"or": [
@@ -533,7 +556,8 @@
},
"then": {
"en": " Type 2 CCS (mennekes) ",
- "nl": " Type 2 CCS (mennekes) "
+ "nl": " Type 2 CCS (mennekes) ",
+ "de": " Typ 2 CCS (Mennekes) "
},
"hideInAnswer": true
},
@@ -542,7 +566,8 @@
"ifnot": "socket:type2_cable=",
"then": {
"en": " Type 2 with cable (mennekes) ",
- "nl": " Type 2 met kabel (J1772) "
+ "nl": " Type 2 met kabel (J1772) ",
+ "de": " Typ 2 mit Kabel (Mennekes) "
},
"hideInAnswer": {
"or": [
@@ -580,7 +605,8 @@
},
"then": {
"en": " Type 2 with cable (mennekes) ",
- "nl": " Type 2 met kabel (J1772) "
+ "nl": " Type 2 met kabel (J1772) ",
+ "de": " Typ 2 mit Kabel (Mennekes) "
},
"hideInAnswer": true
},
@@ -589,7 +615,8 @@
"ifnot": "socket:tesla_supercharger_ccs=",
"then": {
"en": " Tesla Supercharger CCS (a branded type2_css) ",
- "nl": " Tesla Supercharger CCS (een type2 CCS met Tesla-logo) "
+ "nl": " Tesla Supercharger CCS (een type2 CCS met Tesla-logo) ",
+ "de": " Tesla Supercharger CCS (Typ 2 CSS) "
},
"hideInAnswer": {
"or": [
@@ -627,7 +654,8 @@
},
"then": {
"en": " Tesla Supercharger CCS (a branded type2_css) ",
- "nl": " Tesla Supercharger CCS (een type2 CCS met Tesla-logo) "
+ "nl": " Tesla Supercharger CCS (een type2 CCS met Tesla-logo) ",
+ "de": " Tesla Supercharger CCS (Typ 2 CSS) "
},
"hideInAnswer": true
},
@@ -740,7 +768,8 @@
"ifnot": "socket:USB-A=",
"then": {
"en": " USB to charge phones and small electronics ",
- "nl": " USB om GSMs en kleine electronica op te laden "
+ "nl": " USB om GSMs en kleine electronica op te laden ",
+ "de": " USB zum Laden von Smartphones oder Elektrokleingeräten "
}
},
{
@@ -752,7 +781,8 @@
},
"then": {
"en": " USB to charge phones and small electronics ",
- "nl": " USB om GSMs en kleine electronica op te laden "
+ "nl": " USB om GSMs en kleine electronica op te laden ",
+ "de": " USB zum Laden von Smartphones und Elektrokleingeräten "
},
"hideInAnswer": true
},
@@ -804,7 +834,8 @@
"ifnot": "socket:bosch_5pin=",
"then": {
"en": " Bosch Active Connect with 5 pins and cable ",
- "nl": " Bosch Active Connect met 5 pinnen aan een kabel "
+ "nl": " Bosch Active Connect met 5 pinnen aan een kabel ",
+ "de": " Bosch Active Connect mit 5 Pins und Kabel "
},
"hideInAnswer": {
"or": [
@@ -838,7 +869,8 @@
},
"then": {
"en": " Bosch Active Connect with 5 pins and cable ",
- "nl": " Bosch Active Connect met 5 pinnen aan een kabel "
+ "nl": " Bosch Active Connect met 5 pinnen aan een kabel ",
+ "de": " Bosch Active Connect mit 5 Pins und Kabel "
},
"hideInAnswer": true
}
@@ -1189,14 +1221,21 @@
},
"question": {
"en": "When is this charging station opened?",
- "nl": "Wanneer is dit oplaadpunt beschikbaar??"
+ "nl": "Wanneer is dit oplaadpunt beschikbaar??",
+ "de": "Wann ist diese Ladestation geöffnet?",
+ "it": "Quali sono gli orari di apertura di questa stazione di ricarica?",
+ "ja": "この充電ステーションはいつオープンしますか?",
+ "nb_NO": "Når åpnet denne ladestasjonen?",
+ "ru": "В какое время работает эта зарядная станция?",
+ "zh_Hant": "何時是充電站開放使用的時間?"
},
"mappings": [
{
"if": "opening_hours=24/7",
"then": {
"en": "24/7 opened (including holidays)",
- "nl": "24/7 open - ook tijdens vakanties"
+ "nl": "24/7 open - ook tijdens vakanties",
+ "de": "durchgehend geöffnet (auch an Feiertagen)"
}
}
]
@@ -1305,7 +1344,8 @@
"ifnot": "payment:app=no",
"then": {
"en": "Payment is done using a dedicated app",
- "nl": "Betalen via een app van het netwerk"
+ "nl": "Betalen via een app van het netwerk",
+ "de": "Bezahlung mit einer speziellen App"
}
},
{
@@ -1313,7 +1353,8 @@
"ifnot": "payment:membership_card=no",
"then": {
"en": "Payment is done using a membership card",
- "nl": "Betalen via een lidkaart van het netwerk"
+ "nl": "Betalen via een lidkaart van het netwerk",
+ "de": "Bezahlung mit einer Mitgliedskarte"
}
}
]
@@ -1324,7 +1365,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?",
+ "de": "Welche Authentifizierung ist an der Ladestation möglich?"
},
"multiAnswer": true,
"mappings": [
@@ -1333,7 +1375,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",
+ "de": "Authentifizierung durch eine Mitgliedskarte"
}
},
{
@@ -1341,7 +1384,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",
+ "de": "Authentifizierung durch eine App"
}
},
{
@@ -1349,15 +1393,17 @@
"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",
+ "de": "Authentifizierung per Anruf ist möglich"
}
},
{
"if": "authentication:short_message=yes",
"ifnot": "authentication:short_message=no",
"then": {
- "en": "Authentication via SMS is available",
- "nl": "Aanmelden via SMS is mogelijk"
+ "en": "Authentication via phone call is available",
+ "nl": "Aanmelden via SMS is mogelijk",
+ "de": "Authentifizierung per Anruf ist möglich"
}
},
{
@@ -1365,7 +1411,8 @@
"ifnot": "authentication:nfc=no",
"then": {
"en": "Authentication via NFC is available",
- "nl": "Aanmelden via NFC is mogelijk"
+ "nl": "Aanmelden via NFC is mogelijk",
+ "de": "Authentifizierung über NFC ist möglich"
}
},
{
@@ -1373,7 +1420,8 @@
"ifnot": "authentication:money_card=no",
"then": {
"en": "Authentication via Money Card is available",
- "nl": "Aanmelden met Money Card is mogelijk"
+ "nl": "Aanmelden met Money Card is mogelijk",
+ "de": "Authentifizierung über Geldkarte ist möglich"
}
},
{
@@ -1381,7 +1429,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",
+ "de": "Authentifizierung per Debitkarte ist möglich"
}
},
{
@@ -1389,7 +1438,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",
+ "de": "Das Aufladen ist hier (auch) ohne Authentifizierung möglich"
}
}
],
@@ -1404,11 +1454,13 @@
"id": "Auth phone",
"render": {
"en": "Authenticate by calling or SMS'ing to {authentication:phone_call:number}",
- "nl": "Aanmelden door te bellen of te SMS'en naar {authentication:phone_call:number}"
+ "nl": "Aanmelden door te bellen of te SMS'en naar {authentication:phone_call:number}",
+ "de": "Authentifizierung durch Anruf oder SMS an {authentication:phone_call:number}"
},
"question": {
"en": "What's the phone number for authentication call or SMS?",
- "nl": "Wat is het telefoonnummer dat men moet bellen of SMS'en om zich aan te melden?"
+ "nl": "Wat is het telefoonnummer dat men moet bellen of SMS'en om zich aan te melden?",
+ "de": "Wie lautet die Telefonnummer für den Authentifizierungsanruf oder die SMS?"
},
"freeform": {
"key": "authentication:phone_call:number",
@@ -1425,21 +1477,24 @@
"id": "maxstay",
"question": {
"en": "What is the maximum amount of time one is allowed to stay here?",
- "nl": "Hoelang mag een voertuig hier blijven staan?"
+ "nl": "Hoelang mag een voertuig hier blijven staan?",
+ "de": "Was ist die Höchstdauer des Aufenthalts hier?"
},
"freeform": {
"key": "maxstay"
},
"render": {
"en": "One can stay at most {canonical(maxstay)}",
- "nl": "De maximale parkeertijd hier is {canonical(maxstay)}"
+ "nl": "De maximale parkeertijd hier is {canonical(maxstay)}",
+ "de": "Die maximale Parkzeit beträgt {canonical(maxstay)}"
},
"mappings": [
{
"if": "maxstay=unlimited",
"then": {
"en": "No timelimit on leaving your vehicle here",
- "nl": "Geen maximum parkeertijd"
+ "nl": "Geen maximum parkeertijd",
+ "de": "Keine Höchstparkdauer"
}
}
],
@@ -1456,11 +1511,22 @@
"id": "Network",
"render": {
"en": "Part of the network {network}",
- "nl": "Maakt deel uit van het {network}-netwerk"
+ "nl": "Maakt deel uit van het {network}-netwerk",
+ "de": "Teil des Netzwerks {network}",
+ "it": "{network}",
+ "ja": "{network}",
+ "nb_NO": "{network}",
+ "ru": "{network}",
+ "zh_Hant": "{network}"
},
"question": {
"en": "Is this charging station part of a network?",
- "nl": "Is dit oplaadpunt deel van een groter netwerk?"
+ "nl": "Is dit oplaadpunt deel van een groter netwerk?",
+ "de": "Ist diese Ladestation Teil eines Netzwerks?",
+ "it": "A quale rete appartiene questa stazione di ricarica?",
+ "ja": "この充電ステーションの運営チェーンはどこですか?",
+ "ru": "К какой сети относится эта станция?",
+ "zh_Hant": "充電站所屬的網路是?"
},
"freeform": {
"key": "network"
@@ -1470,14 +1536,16 @@
"if": "no:network=yes",
"then": {
"en": "Not part of a bigger network",
- "nl": "Maakt geen deel uit van een groter netwerk"
+ "nl": "Maakt geen deel uit van een groter netwerk",
+ "de": "Nicht Teil eines größeren Netzwerks"
}
},
{
"if": "network=none",
"then": {
"en": "Not part of a bigger network",
- "nl": "Maakt geen deel uit van een groter netwerk"
+ "nl": "Maakt geen deel uit van een groter netwerk",
+ "de": "Nicht Teil eines größeren Netzwerks"
},
"hideInAnswer": true
},
@@ -1499,11 +1567,13 @@
"id": "Operator",
"question": {
"en": "Who is the operator of this charging station?",
- "nl": "Wie beheert dit oplaadpunt?"
+ "nl": "Wie beheert dit oplaadpunt?",
+ "de": "Wer ist der Betreiber dieser Ladestation?"
},
"render": {
"en": "This charging station is operated by {operator}",
- "nl": "Wordt beheerd door {operator}"
+ "nl": "Wordt beheerd door {operator}",
+ "de": "Diese Ladestation wird betrieben von {operator}"
},
"freeform": {
"key": "operator"
@@ -1517,7 +1587,8 @@
},
"then": {
"en": "Actually, {operator} is the network",
- "nl": "Eigenlijk is {operator} het netwerk waarvan het deel uitmaakt"
+ "nl": "Eigenlijk is {operator} het netwerk waarvan het deel uitmaakt",
+ "de": "Eigentlich ist {operator} das Netzwerk"
},
"addExtraTags": [
"operator="
@@ -1530,11 +1601,13 @@
"id": "phone",
"question": {
"en": "What number can one call if there is a problem with this charging station?",
- "nl": "Wat is het telefoonnummer van de beheerder van dit oplaadpunt?"
+ "nl": "Wat is het telefoonnummer van de beheerder van dit oplaadpunt?",
+ "de": "Welche Nummer kann man anrufen, wenn es ein Problem mit dieser Ladestation gibt?"
},
"render": {
"en": "In case of problems, call {phone}",
- "nl": "Bij problemen, bel naar {phone}"
+ "nl": "Bij problemen, bel naar {phone}",
+ "de": "Bei Problemen, anrufen unter {phone}"
},
"freeform": {
"key": "phone",
@@ -1545,11 +1618,13 @@
"id": "email",
"question": {
"en": "What is the email address of the operator?",
- "nl": "Wat is het email-adres van de operator?"
+ "nl": "Wat is het email-adres van de operator?",
+ "de": "Wie ist die Email-Adresse des Betreibers?"
},
"render": {
"en": "In case of problems, send an email to {email}",
- "nl": "Bij problemen, email naar {email}"
+ "nl": "Bij problemen, email naar {email}",
+ "de": "Bei Problemen senden Sie eine E-Mail an {email}"
},
"freeform": {
"key": "email",
@@ -1559,12 +1634,14 @@
{
"id": "website",
"question": {
- "en": "What is the website where one can find more information about this charging station?",
- "nl": "Wat is de website waar men meer info kan vinden over dit oplaadpunt?"
+ "en": "What is the website of the operator?",
+ "nl": "Wat is de website waar men meer info kan vinden over dit oplaadpunt?",
+ "de": "Wie ist die Webseite des Betreibers?"
},
"render": {
"en": "More info on {website}",
- "nl": "Meer informatie op {website}"
+ "nl": "Meer informatie op {website}",
+ "de": "Weitere Informationen auf {website}"
},
"freeform": {
"key": "website",
@@ -1576,11 +1653,13 @@
"id": "ref",
"question": {
"en": "What is the reference number of this charging station?",
- "nl": "Wat is het referentienummer van dit oplaadstation?"
+ "nl": "Wat is het referentienummer van dit oplaadstation?",
+ "de": "Wie lautet die Kennung dieser Ladestation?"
},
"render": {
"en": "Reference number is {ref}",
- "nl": "Het referentienummer van dit oplaadpunt is {ref}"
+ "nl": "Het referentienummer van dit oplaadpunt is {ref}",
+ "de": "Die Kennziffer ist {ref}"
},
"freeform": {
"key": "ref"
@@ -1592,7 +1671,8 @@
"id": "Operational status",
"question": {
"en": "Is this charging point in use?",
- "nl": "Is dit oplaadpunt operationeel?"
+ "nl": "Is dit oplaadpunt operationeel?",
+ "de": "Ist dieser Ladepunkt in Betrieb?"
},
"mappings": [
{
@@ -1607,7 +1687,8 @@
},
"then": {
"en": "This charging station works",
- "nl": "Dit oplaadpunt werkt"
+ "nl": "Dit oplaadpunt werkt",
+ "de": "Diese Ladestation funktioniert"
}
},
{
@@ -1622,7 +1703,8 @@
},
"then": {
"en": "This charging station is broken",
- "nl": "Dit oplaadpunt is kapot"
+ "nl": "Dit oplaadpunt is kapot",
+ "de": "Diese Ladestation ist kaputt"
}
},
{
@@ -1637,7 +1719,8 @@
},
"then": {
"en": "A charging station is planned here",
- "nl": "Hier zal binnenkort een oplaadpunt gebouwd worden"
+ "nl": "Hier zal binnenkort een oplaadpunt gebouwd worden",
+ "de": "Hier ist eine Ladestation geplant"
}
},
{
@@ -1652,7 +1735,8 @@
},
"then": {
"en": "A charging station is constructed here",
- "nl": "Hier wordt op dit moment een oplaadpunt gebouwd"
+ "nl": "Hier wordt op dit moment een oplaadpunt gebouwd",
+ "de": "Hier wird eine Ladestation gebaut"
}
},
{
@@ -1667,7 +1751,8 @@
},
"then": {
"en": "This charging station has beed permanently disabled and is not in use anymore but is still visible",
- "nl": "Dit oplaadpunt is niet meer in gebruik maar is wel nog aanwezig"
+ "nl": "Dit oplaadpunt is niet meer in gebruik maar is wel nog aanwezig",
+ "de": "Diese Ladestation wurde dauerhaft deaktiviert und wird nicht mehr benutzt, ist aber noch sichtbar"
}
}
]
@@ -1676,21 +1761,24 @@
"id": "Parking:fee",
"question": {
"en": "Does one have to pay a parking fee while charging?",
- "nl": "Moet men parkeergeld betalen tijdens het opladen?"
+ "nl": "Moet men parkeergeld betalen tijdens het opladen?",
+ "de": "Muss man beim Laden eine Parkgebühr bezahlen?"
},
"mappings": [
{
"if": "parking:fee=no",
"then": {
"en": "No additional parking cost while charging",
- "nl": "Geen extra parkeerkost tijdens het opladen"
+ "nl": "Geen extra parkeerkost tijdens het opladen",
+ "de": "Keine zusätzlichen Parkgebühren beim Laden"
}
},
{
"if": "parking:fee=yes",
"then": {
"en": "An additional parking fee should be paid while charging",
- "nl": "Tijdens het opladen moet er parkeergeld betaald worden"
+ "nl": "Tijdens het opladen moet er parkeergeld betaald worden",
+ "de": "Beim Laden ist eine zusätzliche Parkgebühr zu entrichten"
}
}
],
@@ -1705,69 +1793,6 @@
}
}
],
- "mapRendering": [
- {
- "location": [
- "point",
- "centroid"
- ],
- "icon": {
- "render": "pin:#fff;./assets/themes/charging_stations/plug.svg",
- "mappings": [
- {
- "if": "bicycle=yes",
- "then": "pin:#fff;./assets/themes/charging_stations/bicycle.svg"
- },
- {
- "if": {
- "or": [
- "car=yes",
- "motorcar=yes"
- ]
- },
- "then": "pin:#fff;./assets/themes/charging_stations/car.svg"
- }
- ]
- },
- "iconBadges": [
- {
- "if": {
- "or": [
- "disused:amenity=charging_station",
- "operational_status=broken"
- ]
- },
- "then": "cross:#c22;"
- },
- {
- "if": {
- "or": [
- "proposed:amenity=charging_station",
- "planned:amenity=charging_station"
- ]
- },
- "then": "./assets/layers/charging_station/under_construction.svg"
- },
- {
- "if": {
- "and": [
- "bicycle=yes",
- {
- "or": [
- "motorcar=yes",
- "car=yes"
- ]
- }
- ]
- },
- "then": "circle:#fff;./assets/themes/charging_stations/car.svg"
- }
- ],
- "iconSize": {
- "render": "50,50,bottom"
- }
- }
- ],
"presets": [
{
"tags": [
@@ -1777,8 +1802,10 @@
"socket:typee=1"
],
"title": {
- "en": "electrical outlet to charge e-bikes",
- "nl": "laadpunt met gewone stekker(s) (bedoeld om electrische fietsen op te laden)"
+ "en": "Charging station",
+ "nl": "gewone stekker (bedoeld om electrische fietsen op te laden)",
+ "de": "Ladestation",
+ "ru": "Зарядная станция"
},
"preciseInput": {
"preferredBackground": "map"
@@ -1833,20 +1860,23 @@
{
"question": {
"en": "All vehicle types",
- "nl": "Alle voertuigen"
+ "nl": "Alle voertuigen",
+ "de": "Alle Fahrzeugtypen"
}
},
{
"question": {
"en": "Charging station for bicycles",
- "nl": "Oplaadpunten voor fietsen"
+ "nl": "Oplaadpunten voor fietsen",
+ "de": "Ladestation für Fahrräder"
},
"osmTags": "bicycle=yes"
},
{
"question": {
"en": "Charging station for cars",
- "nl": "Oplaadpunten voor auto's"
+ "nl": "Oplaadpunten voor auto's",
+ "de": "Ladestation für Autos"
},
"osmTags": {
"or": [
@@ -1863,7 +1893,8 @@
{
"question": {
"en": "Only working charging stations",
- "nl": "Enkel werkende oplaadpunten"
+ "nl": "Enkel werkende oplaadpunten",
+ "de": "Nur funktionierende Ladestationen"
},
"osmTags": {
"and": [
@@ -1880,7 +1911,8 @@
{
"question": {
"en": "All connectors",
- "nl": "Alle types"
+ "nl": "Alle types",
+ "de": "Alle Anschlüsse"
}
},
{
@@ -1900,7 +1932,8 @@
{
"question": {
"en": "Has a Chademo connector",
- "nl": "Heeft een Chademo "
+ "nl": "Heeft een Chademo ",
+ "de": "Hat einen Chademo Stecker"
},
"osmTags": "socket:chademo~*"
},
@@ -1928,7 +1961,8 @@
{
"question": {
"en": "Has a Tesla Supercharger connector",
- "nl": "Heeft een Tesla Supercharger "
+ "nl": "Heeft een Tesla Supercharger ",
+ "de": "Hat einen Tesla Supercharger Stecker"
},
"osmTags": "socket:tesla_supercharger~*"
},
@@ -2016,11 +2050,15 @@
],
"human": {
"en": " minutes",
- "nl": " minuten"
+ "nl": " minuten",
+ "de": " Minuten",
+ "ru": " минут"
},
"humanSingular": {
"en": " minute",
- "nl": " minuut"
+ "nl": " minuut",
+ "de": " Minute",
+ "ru": " минута"
}
},
{
@@ -2036,11 +2074,15 @@
],
"human": {
"en": " hours",
- "nl": " uren"
+ "nl": " uren",
+ "de": " Stunden",
+ "ru": " часов"
},
"humanSingular": {
"en": " hour",
- "nl": " uur"
+ "nl": " uur",
+ "de": " Stunde",
+ "ru": " час"
}
},
{
@@ -2053,11 +2095,15 @@
],
"human": {
"en": " days",
- "nl": " day"
+ "nl": " day",
+ "de": " Tage",
+ "ru": " дней"
},
"humanSingular": {
"en": " day",
- "nl": " dag"
+ "nl": " dag",
+ "de": " Tag",
+ "ru": " день"
}
}
]
@@ -2093,7 +2139,9 @@
],
"human": {
"en": "Volts",
- "nl": "volt"
+ "nl": "volt",
+ "de": "Volt",
+ "ru": "Вольт"
}
}
],
@@ -2162,7 +2210,9 @@
],
"human": {
"en": "kilowatt",
- "nl": "kilowatt"
+ "nl": "kilowatt",
+ "de": "Kilowatt",
+ "ru": "киловатт"
}
},
{
@@ -2172,7 +2222,9 @@
],
"human": {
"en": "megawatt",
- "nl": "megawatt"
+ "nl": "megawatt",
+ "de": "Megawatt",
+ "ru": "мегаватт"
}
}
],
@@ -2191,5 +2243,68 @@
]
},
"neededChangesets": 10
- }
+ },
+ "mapRendering": [
+ {
+ "location": [
+ "point",
+ "centroid"
+ ],
+ "icon": {
+ "render": "pin:#fff;./assets/themes/charging_stations/plug.svg",
+ "mappings": [
+ {
+ "if": "bicycle=yes",
+ "then": "pin:#fff;./assets/themes/charging_stations/bicycle.svg"
+ },
+ {
+ "if": {
+ "or": [
+ "car=yes",
+ "motorcar=yes"
+ ]
+ },
+ "then": "pin:#fff;./assets/themes/charging_stations/car.svg"
+ }
+ ]
+ },
+ "iconBadges": [
+ {
+ "if": {
+ "or": [
+ "disused:amenity=charging_station",
+ "operational_status=broken"
+ ]
+ },
+ "then": "cross:#c22;"
+ },
+ {
+ "if": {
+ "or": [
+ "proposed:amenity=charging_station",
+ "planned:amenity=charging_station"
+ ]
+ },
+ "then": "./assets/layers/charging_station/under_construction.svg"
+ },
+ {
+ "if": {
+ "and": [
+ "bicycle=yes",
+ {
+ "or": [
+ "motorcar=yes",
+ "car=yes"
+ ]
+ }
+ ]
+ },
+ "then": "circle:#fff;./assets/themes/charging_stations/car.svg"
+ }
+ ],
+ "iconSize": {
+ "render": "50,50,bottom"
+ }
+ }
+ ]
}
\ No newline at end of file
diff --git a/assets/layers/cycleways_and_roads/cycleways_and_roads.json b/assets/layers/cycleways_and_roads/cycleways_and_roads.json
index d0d843b82..11f160c4e 100644
--- a/assets/layers/cycleways_and_roads/cycleways_and_roads.json
+++ b/assets/layers/cycleways_and_roads/cycleways_and_roads.json
@@ -83,7 +83,6 @@
}
]
},
- "description": {},
"tagRenderings": [
{
"question": {
@@ -102,21 +101,24 @@
"if": "cycleway=shared_lane",
"then": {
"en": "There is a shared lane",
- "nl": "Er is een fietssuggestiestrook"
+ "nl": "Er is een fietssuggestiestrook",
+ "de": "Es gibt eine geteilte Fahrspur"
}
},
{
"if": "cycleway=lane",
"then": {
"en": "There is a lane next to the road (separated with paint)",
- "nl": "Er is een fietspad aangrenzend aan de weg (gescheiden met verf)"
+ "nl": "Er is een fietspad aangrenzend aan de weg (gescheiden met verf)",
+ "de": "Es gibt eine Spur neben der Straße (getrennt durch eine Straßenmarkierung)"
}
},
{
"if": "cycleway=track",
"then": {
"en": "There is a track, but no cycleway drawn separately from this road on the map.",
- "nl": "Er is een fietspad (los van de weg), maar geen fietspad afzonderlijk getekend naast deze weg."
+ "nl": "Er is een fietspad (los van de weg), maar geen fietspad afzonderlijk getekend naast deze weg.",
+ "de": "Es gibt einen Weg, aber keinen Radweg, der auf der Karte getrennt von dieser Straße eingezeichnet ist."
}
},
{
@@ -163,7 +165,8 @@
"if": "lit=yes",
"then": {
"en": "This street is lit",
- "nl": "Deze weg is verlicht"
+ "nl": "Deze weg is verlicht",
+ "de": "Diese Straße ist beleuchtet"
}
},
{
@@ -211,7 +214,8 @@
"if": "cyclestreet=yes",
"then": {
"en": "This is a cyclestreet, and a 30km/h zone.",
- "nl": "Dit is een fietsstraat, en dus een 30km/h zone"
+ "nl": "Dit is een fietsstraat, en dus een 30km/h zone",
+ "de": "Dies ist eine Fahrradstraße in einer 30km/h Zone."
},
"addExtraTags": [
"overtaking:motor_vehicle=no",
@@ -245,7 +249,8 @@
{
"render": {
"en": "The maximum speed on this road is {maxspeed} km/h",
- "nl": "De maximumsnelheid op deze weg is {maxspeed} km/u"
+ "nl": "De maximumsnelheid op deze weg is {maxspeed} km/u",
+ "de": "Die Höchstgeschwindigkeit auf dieser Straße beträgt {maxspeed} km/h"
},
"freeform": {
"key": "maxspeed",
@@ -301,7 +306,8 @@
],
"question": {
"en": "What is the maximum speed in this street?",
- "nl": "Wat is de maximumsnelheid in deze straat?"
+ "nl": "Wat is de maximumsnelheid in deze straat?",
+ "de": "Was ist die Höchstgeschwindigkeit auf dieser Straße?"
},
"id": "Maxspeed (for road)"
},
@@ -352,7 +358,8 @@
"if": "cycleway:surface=paving_stones",
"then": {
"en": "This cycleway is made of smooth paving stones",
- "nl": "Dit fietspad is gemaakt van straatstenen"
+ "nl": "Dit fietspad is gemaakt van straatstenen",
+ "de": "Dieser Fahrradweg besteht aus ebenen Pflastersteinen"
}
},
{
@@ -367,7 +374,8 @@
"if": "cycleway:surface=cobblestone",
"then": {
"en": "This cycleway is made of cobblestone (unhewn or sett)",
- "nl": "Dit fietspad is gemaakt van kasseien (natuurlijk of verwerkt)"
+ "nl": "Dit fietspad is gemaakt van kasseien (natuurlijk of verwerkt)",
+ "de": "Dieser Radweg besteht aus Kopfsteinpflaster"
},
"hideInAnswer": true
},
@@ -375,14 +383,16 @@
"if": "cycleway:surface=unhewn_cobblestone",
"then": {
"en": "This cycleway is made of raw, natural cobblestone",
- "nl": "Dit fietspad is gemaakt van ruwe, natuurlijke kasseien"
+ "nl": "Dit fietspad is gemaakt van ruwe, natuurlijke kasseien",
+ "de": "Dieser Fahrradweg besteht aus unregelmäßigem, unbehauenem Kopfsteinpflaster"
}
},
{
"if": "cycleway:surface=sett",
"then": {
"en": "This cycleway is made of flat, square cobblestone",
- "nl": "Dit fietspad is gemaakt van vlakke, rechthoekige kasseien"
+ "nl": "Dit fietspad is gemaakt van vlakke, rechthoekige kasseien",
+ "de": "Dieser Fahrradweg besteht aus regelmäßigem, behauenem Kopfsteinpflaster"
}
},
{
@@ -428,7 +438,8 @@
],
"question": {
"en": "What is the surface of the cycleway made from?",
- "nl": "Waaruit is het oppervlak van het fietspad van gemaakt?"
+ "nl": "Waaruit is het oppervlak van het fietspad van gemaakt?",
+ "de": "Was ist der Belag dieses Radwegs?"
},
"id": "Cycleway:surface"
},
@@ -466,28 +477,32 @@
"if": "cycleway:smoothness=intermediate",
"then": {
"en": "Usable for normal wheels: city bike, wheelchair, scooter",
- "nl": "Geschikt voor normale wielen: stadsfiets, rolstoel, scooter"
+ "nl": "Geschikt voor normale wielen: stadsfiets, rolstoel, scooter",
+ "de": "Geeignet für normale Reifen: Fahrrad, Rollstuhl, Scooter"
}
},
{
"if": "cycleway:smoothness=bad",
"then": {
"en": "Usable for robust wheels: trekking bike, car, rickshaw",
- "nl": "Geschikt voor brede wielen: trekfiets, auto, rickshaw"
+ "nl": "Geschikt voor brede wielen: trekfiets, auto, rickshaw",
+ "de": "Geeignet für breite Reifen: Trekkingfahrrad, Auto, Rikscha"
}
},
{
"if": "cycleway:smoothness=very_bad",
"then": {
"en": "Usable for vehicles with high clearance: light duty off-road vehicle",
- "nl": "Geschikt voor voertuigen met hoge banden: lichte terreinwagen"
+ "nl": "Geschikt voor voertuigen met hoge banden: lichte terreinwagen",
+ "de": "Geeignet für Fahrzeuge mit großer Bodenfreiheit: leichte Geländewagen"
}
},
{
"if": "cycleway:smoothness=horrible",
"then": {
"en": "Usable for off-road vehicles: heavy duty off-road vehicle",
- "nl": "Geschikt voor terreinwagens: zware terreinwagen"
+ "nl": "Geschikt voor terreinwagens: zware terreinwagen",
+ "de": "Geeignet für Geländefahrzeuge: schwerer Geländewagen"
}
},
{
@@ -523,7 +538,8 @@
"if": "surface=unpaved",
"then": {
"en": "This cycleway is unhardened",
- "nl": "Dit fietspad is onverhard"
+ "nl": "Dit fietspad is onverhard",
+ "de": "Dieser Radweg ist nicht befestigt"
},
"hideInAnswer": true
},
@@ -548,7 +564,8 @@
"if": "surface=paving_stones",
"then": {
"en": "This cycleway is made of smooth paving stones",
- "nl": "Dit fietspad is gemaakt van straatstenen"
+ "nl": "Dit fietspad is gemaakt van straatstenen",
+ "de": "Dieser Fahrradweg besteht aus ebenen Pflastersteinen"
}
},
{
@@ -563,7 +580,8 @@
"if": "surface=cobblestone",
"then": {
"en": "This cycleway is made of cobblestone (unhewn or sett)",
- "nl": "Dit fietspad is gemaakt van kasseien (natuurlijk of verwerkt)"
+ "nl": "Dit fietspad is gemaakt van kasseien (natuurlijk of verwerkt)",
+ "de": "Dieser Radweg besteht aus Kopfsteinpflaster"
},
"hideInAnswer": true
},
@@ -571,14 +589,16 @@
"if": "surface=unhewn_cobblestone",
"then": {
"en": "This cycleway is made of raw, natural cobblestone",
- "nl": "Dit fietspad is gemaakt van ruwe, natuurlijke kasseien"
+ "nl": "Dit fietspad is gemaakt van ruwe, natuurlijke kasseien",
+ "de": "Dieser Fahrradweg besteht aus unregelmäßigem, unbehauenem Kopfsteinpflaster"
}
},
{
"if": "surface=sett",
"then": {
"en": "This cycleway is made of flat, square cobblestone",
- "nl": "Dit fietspad is gemaakt van vlakke, rechthoekige kasseien"
+ "nl": "Dit fietspad is gemaakt van vlakke, rechthoekige kasseien",
+ "de": "Dieser Fahrradweg besteht aus regelmäßigem, behauenem Kopfsteinpflaster"
}
},
{
@@ -624,7 +644,8 @@
],
"question": {
"en": "What is the surface of the street made from?",
- "nl": "Waaruit is het oppervlak van de straat gemaakt?"
+ "nl": "Waaruit is het oppervlak van de straat gemaakt?",
+ "de": "Was ist der Belag dieser Straße?"
},
"id": "Surface of the road"
},
@@ -658,25 +679,29 @@
{
"if": "smoothness=intermediate",
"then": {
- "en": "Usable for normal wheels: city bike, wheelchair, scooter"
+ "en": "Usable for normal wheels: city bike, wheelchair, scooter",
+ "de": "Geeignet für normale Reifen: Fahrrad, Rollstuhl, Scooter"
}
},
{
"if": "smoothness=bad",
"then": {
- "en": "Usable for robust wheels: trekking bike, car, rickshaw"
+ "en": "Usable for robust wheels: trekking bike, car, rickshaw",
+ "de": "Geeignet für breite Reifen: Trekkingfahrrad, Auto, Rikscha"
}
},
{
"if": "smoothness=very_bad",
"then": {
- "en": "Usable for vehicles with high clearance: light duty off-road vehicle"
+ "en": "Usable for vehicles with high clearance: light duty off-road vehicle",
+ "de": "Geeignet für Fahrzeuge mit großer Bodenfreiheit: leichte Geländewagen"
}
},
{
"if": "smoothness=horrible",
"then": {
- "en": "Usable for off-road vehicles: heavy duty off-road vehicle"
+ "en": "Usable for off-road vehicles: heavy duty off-road vehicle",
+ "de": "Geeignet für Geländefahrzeuge: schwerer Geländewagen"
}
},
{
@@ -748,7 +773,8 @@
"if": "cycleway:traffic_sign~BE:D7;.*",
"then": {
"en": "Compulsory cycleway (with supplementary sign)
",
- "nl": "Verplicht fietspad (met onderbord)
"
+ "nl": "Verplicht fietspad (met onderbord)
",
+ "de": "Vorgeschriebener Radweg (mit Zusatzschild)
"
},
"hideInAnswer": true
},
@@ -821,7 +847,8 @@
"if": "traffic_sign~BE:D7;.*",
"then": {
"en": "Compulsory cycleway (with supplementary sign)
",
- "nl": "Verplicht fietspad (met onderbord)
"
+ "nl": "Verplicht fietspad (met onderbord)
",
+ "de": "Vorgeschriebener Radweg (mit Zusatzschild)
"
},
"hideInAnswer": true
},
@@ -1055,11 +1082,13 @@
{
"render": {
"en": "The buffer besides this cycleway is {cycleway:buffer} m",
- "nl": "De schrikafstand van dit fietspad is {cycleway:buffer} m"
+ "nl": "De schrikafstand van dit fietspad is {cycleway:buffer} m",
+ "de": "Der Sicherheitsabstand zu diesem Radweg beträgt {cycleway:buffer} m"
},
"question": {
"en": "How wide is the gap between the cycleway and the road?",
- "nl": "Hoe breed is de ruimte tussen het fietspad en de weg?"
+ "nl": "Hoe breed is de ruimte tussen het fietspad en de weg?",
+ "de": "Wie breit ist der Abstand zwischen Radweg und Straße?"
},
"condition": {
"or": [
@@ -1081,7 +1110,8 @@
"id": "cyclelan-segregation",
"question": {
"en": "How is this cycleway separated from the road?",
- "nl": "Hoe is dit fietspad gescheiden van de weg?"
+ "nl": "Hoe is dit fietspad gescheiden van de weg?",
+ "de": "Wie ist der Radweg von der Straße abgegrenzt?"
},
"condition": {
"or": [
@@ -1094,21 +1124,24 @@
"if": "cycleway:separation=dashed_line",
"then": {
"en": "This cycleway is separated by a dashed line",
- "nl": "Dit fietspad is gescheiden van de weg met een onderbroken streep"
+ "nl": "Dit fietspad is gescheiden van de weg met een onderbroken streep",
+ "de": "Der Radweg ist abgegrenzt durch eine gestrichelte Linie"
}
},
{
"if": "cycleway:separation=solid_line",
"then": {
"en": "This cycleway is separated by a solid line",
- "nl": "Dit fietspad is gescheiden van de weg met een doorgetrokken streep"
+ "nl": "Dit fietspad is gescheiden van de weg met een doorgetrokken streep",
+ "de": "Der Radweg ist abgegrenzt durch eine durchgezogene Linie"
}
},
{
"if": "cycleway:separation=parking_lane",
"then": {
"en": "This cycleway is separated by a parking lane",
- "nl": "Dit fietspad is gescheiden van de weg met parkeervakken"
+ "nl": "Dit fietspad is gescheiden van de weg met parkeervakken",
+ "de": "Der Radweg ist abgegrenzt durch eine Parkspur"
}
},
{
@@ -1125,7 +1158,8 @@
"id": "cycleway-segregation",
"question": {
"en": "How is this cycleway separated from the road?",
- "nl": "Hoe is dit fietspad gescheiden van de weg?"
+ "nl": "Hoe is dit fietspad gescheiden van de weg?",
+ "de": "Wie ist der Radweg von der Straße abgegrenzt?"
},
"condition": {
"or": [
@@ -1138,21 +1172,24 @@
"if": "separation=dashed_line",
"then": {
"en": "This cycleway is separated by a dashed line",
- "nl": "Dit fietspad is gescheiden van de weg met een onderbroken streep"
+ "nl": "Dit fietspad is gescheiden van de weg met een onderbroken streep",
+ "de": "Der Radweg ist abgegrenzt durch eine gestrichelte Linie"
}
},
{
"if": "separation=solid_line",
"then": {
"en": "This cycleway is separated by a solid line",
- "nl": "Dit fietspad is gescheiden van de weg met een doorgetrokken streep"
+ "nl": "Dit fietspad is gescheiden van de weg met een doorgetrokken streep",
+ "de": "Der Radweg ist abgegrenzt durch eine durchgezogene Linie"
}
},
{
"if": "separation=parking_lane",
"then": {
"en": "This cycleway is separated by a parking lane",
- "nl": "Dit fietspad is gescheiden van de weg met parkeervakken"
+ "nl": "Dit fietspad is gescheiden van de weg met parkeervakken",
+ "de": "Der Radweg ist abgegrenzt durch eine Parkspur"
}
},
{
diff --git a/assets/layers/public_bookcase/public_bookcase.json b/assets/layers/public_bookcase/public_bookcase.json
index 65f017ee3..cabdc34f1 100644
--- a/assets/layers/public_bookcase/public_bookcase.json
+++ b/assets/layers/public_bookcase/public_bookcase.json
@@ -1,516 +1,518 @@
{
- "id": "public_bookcase",
- "name": {
- "en": "Bookcases",
- "nl": "Boekenruilkastjes",
- "de": "Bücherschränke",
- "fr": "Microbibliothèque",
- "ru": "Книжные шкафы",
- "it": "Microbiblioteche"
+ "id": "public_bookcase",
+ "name": {
+ "en": "Bookcases",
+ "nl": "Boekenruilkastjes",
+ "de": "Bücherschränke",
+ "fr": "Microbibliothèque",
+ "ru": "Книжные шкафы",
+ "it": "Microbiblioteche"
+ },
+ "description": {
+ "en": "A streetside cabinet with books, accessible to anyone",
+ "nl": "Een straatkastje met boeken voor iedereen",
+ "de": "Ein Bücherschrank am Straßenrand mit Büchern, für jedermann zugänglich",
+ "fr": "Une armoire ou une boite contenant des livres en libre accès",
+ "it": "Una vetrinetta ai bordi della strada contenente libri, aperta al pubblico",
+ "ru": "Уличный шкаф с книгами, доступными для всех"
+ },
+ "source": {
+ "osmTags": "amenity=public_bookcase"
+ },
+ "minzoom": 10,
+ "wayHandling": 2,
+ "title": {
+ "render": {
+ "en": "Bookcase",
+ "nl": "Boekenruilkast",
+ "de": "Bücherschrank",
+ "fr": "Microbibliothèque",
+ "ru": "Книжный шкаф",
+ "it": "Microbiblioteca"
},
- "description": {
- "en": "A streetside cabinet with books, accessible to anyone",
- "nl": "Een straatkastje met boeken voor iedereen",
- "de": "Ein Bücherschrank am Straßenrand mit Büchern, für jedermann zugänglich",
- "fr": "Une armoire ou une boite contenant des livres en libre accès",
- "it": "Una vetrinetta ai bordi della strada contenente libri, aperta al pubblico",
- "ru": "Уличный шкаф с книгами, доступными для всех"
- },
- "source": {
- "osmTags": "amenity=public_bookcase"
- },
- "minzoom": 10,
- "wayHandling": 2,
- "title": {
- "render": {
- "en": "Bookcase",
- "nl": "Boekenruilkast",
- "de": "Bücherschrank",
- "fr": "Microbibliothèque",
- "ru": "Книжный шкаф",
- "it": "Microbiblioteca"
- },
- "mappings": [
- {
- "if": "name~*",
- "then": {
- "en": "Public bookcase {name}",
- "nl": "Boekenruilkast {name}",
- "de": "Öffentlicher Bücherschrank {name}",
- "fr": "Microbibliothèque {name}",
- "ru": "Общественный книжный шкаф {name}",
- "it": "Microbiblioteca pubblica {name}"
- }
- }
- ]
- },
- "icon": {
- "render": "./assets/themes/bookcases/bookcase.svg"
- },
- "label": {
- "mappings": [
- {
- "if": "name~*",
- "then": "{name}
"
- }
- ]
- },
- "color": {
- "render": "#0000ff"
- },
- "width": {
- "render": "8"
- },
- "presets": [
- {
- "title": {
- "en": "Bookcase",
- "nl": "Boekenruilkast",
- "de": "Bücherschrank",
- "fr": "Microbibliothèque",
- "ru": "Книжный шкаф",
- "it": "Microbiblioteca"
- },
- "tags": [
- "amenity=public_bookcase"
- ],
- "preciseInput": {
- "preferredBackground": "photo"
- }
- }
- ],
- "tagRenderings": [
- "images",
- {
- "id": "minimap",
- "render": "{minimap():height: 9rem; border-radius: 2.5rem; overflow:hidden;border:1px solid gray}"
- },
- {
- "render": {
- "en": "The name of this bookcase is {name}",
- "nl": "De naam van dit boekenruilkastje is {name}",
- "de": "Der Name dieses Bücherschrank lautet {name}",
- "fr": "Le nom de cette microbibliothèque est {name}",
- "ru": "Название книжного шкафа — {name}",
- "it": "Questa microbiblioteca si chiama {name}"
- },
- "question": {
- "en": "What is the name of this public bookcase?",
- "nl": "Wat is de naam van dit boekenuilkastje?",
- "de": "Wie heißt dieser öffentliche Bücherschrank?",
- "fr": "Quel est le nom de cette microbibliothèque ?",
- "ru": "Как называется этот общественный книжный шкаф?",
- "it": "Come si chiama questa microbiblioteca pubblica?"
- },
- "freeform": {
- "key": "name"
- },
- "mappings": [
- {
- "if": {
- "and": [
- "noname=yes",
- "name="
- ]
- },
- "then": {
- "en": "This bookcase doesn't have a name",
- "nl": "Dit boekenruilkastje heeft geen naam",
- "de": "Dieser Bücherschrank hat keinen Namen",
- "fr": "Cette microbibliothèque n'a pas de nom",
- "ru": "У этого книжного шкафа нет названия",
- "it": "Questa microbiblioteca non ha un nome proprio"
- }
- }
- ],
- "id": "public_bookcase-name"
- },
- {
- "render": {
- "en": "{capacity} books fit in this bookcase",
- "nl": "Er passen {capacity} boeken",
- "de": "{capacity} Bücher passen in diesen Bücherschrank",
- "fr": "{capacity} livres peuvent entrer dans cette microbibliothèque",
- "it": "Questa microbiblioteca può contenere fino a {capacity} libri",
- "ru": "{capacity} книг помещается в этот книжный шкаф"
- },
- "question": {
- "en": "How many books fit into this public bookcase?",
- "nl": "Hoeveel boeken passen er in dit boekenruilkastje?",
- "de": "Wie viele Bücher passen in diesen öffentlichen Bücherschrank?",
- "fr": "Combien de livres peuvent entrer dans cette microbibliothèque ?",
- "ru": "Сколько книг помещается в этом общественном книжном шкафу?",
- "it": "Quanti libri può contenere questa microbiblioteca?"
- },
- "freeform": {
- "key": "capacity",
- "type": "nat",
- "inline": true
- },
- "id": "public_bookcase-capacity"
- },
- {
- "id": "bookcase-booktypes",
- "question": {
- "en": "What kind of books can be found in this public bookcase?",
- "nl": "Voor welke doelgroep zijn de meeste boeken in dit boekenruilkastje?",
- "de": "Welche Art von Büchern sind in diesem öffentlichen Bücherschrank zu finden?",
- "fr": "Quel type de livres peut-on dans cette microbibliothèque ?",
- "it": "Che tipo di libri si possono trovare in questa microbiblioteca?",
- "ru": "Какие книги можно найти в этом общественном книжном шкафу?"
- },
- "mappings": [
- {
- "if": "books=children",
- "then": {
- "en": "Mostly children books",
- "nl": "Voornamelijk kinderboeken",
- "de": "Vorwiegend Kinderbücher",
- "fr": "Livres pour enfants",
- "ru": "В основном детские книги",
- "it": "Principalmente libri per l'infanzia"
- }
- },
- {
- "if": "books=adults",
- "then": {
- "en": "Mostly books for adults",
- "nl": "Voornamelijk boeken voor volwassenen",
- "de": "Vorwiegend Bücher für Erwachsene",
- "fr": "Livres pour les adultes",
- "ru": "В основном книги для взрослых",
- "it": "Principalmente libri per persone in età adulta"
- }
- },
- {
- "if": "books=children;adults",
- "then": {
- "en": "Both books for kids and adults",
- "nl": "Boeken voor zowel kinderen als volwassenen",
- "de": "Sowohl Bücher für Kinder als auch für Erwachsene",
- "fr": "Livres pour enfants et adultes également",
- "it": "Sia libri per l'infanzia, sia per l'età adulta",
- "ru": "Книги и для детей, и для взрослых"
- }
- }
- ]
- },
- {
- "id": "bookcase-is-indoors",
- "question": {
- "en": "Is this bookcase located outdoors?",
- "nl": "Staat dit boekenruilkastje binnen of buiten?",
- "de": "Befindet sich dieser Bücherschrank im Freien?",
- "fr": "Cette microbiliothèque est-elle en extérieur ?",
- "it": "Questa microbiblioteca si trova all'aperto?"
- },
- "mappings": [
- {
- "then": {
- "en": "This bookcase is located indoors",
- "nl": "Dit boekenruilkastje staat binnen",
- "de": "Dieser Bücherschrank befindet sich im Innenbereich",
- "fr": "Cette microbibliothèque est en intérieur",
- "it": "Questa microbiblioteca si trova al chiuso"
- },
- "if": "indoor=yes"
- },
- {
- "then": {
- "en": "This bookcase is located outdoors",
- "nl": "Dit boekenruilkastje staat buiten",
- "de": "Dieser Bücherschrank befindet sich im Freien",
- "fr": "Cette microbibliothèque est en extérieur",
- "it": "Questa microbiblioteca si trova all'aperto"
- },
- "if": "indoor=no"
- },
- {
- "then": {
- "en": "This bookcase is located outdoors",
- "nl": "Dit boekenruilkastje staat buiten",
- "de": "Dieser Bücherschrank befindet sich im Freien",
- "fr": "Cette microbibliothèque est en extérieur",
- "it": "Questa microbiblioteca si trova all'aperto"
- },
- "if": "indoor=",
- "hideInAnswer": true
- }
- ]
- },
- {
- "id": "bookcase-is-accessible",
- "question": {
- "en": "Is this public bookcase freely accessible?",
- "nl": "Is dit boekenruilkastje publiek toegankelijk?",
- "de": "Ist dieser öffentliche Bücherschrank frei zugänglich?",
- "fr": "Cette microbibliothèque est-elle librement accèssible ?",
- "it": "Questa microbiblioteca è ad accesso libero?",
- "ru": "Имеется ли свободный доступ к этому общественному книжному шкафу?"
- },
- "condition": "indoor=yes",
- "mappings": [
- {
- "then": {
- "en": "Publicly accessible",
- "nl": "Publiek toegankelijk",
- "de": "Öffentlich zugänglich",
- "fr": "Accèssible au public",
- "it": "È ad accesso libero",
- "ru": "Свободный доступ"
- },
- "if": "access=yes"
- },
- {
- "then": {
- "en": "Only accessible to customers",
- "nl": "Enkel toegankelijk voor klanten",
- "de": "Nur für Kunden zugänglich",
- "fr": "Accèssible aux clients",
- "it": "L'accesso è riservato ai clienti"
- },
- "if": "access=customers"
- }
- ]
- },
- {
- "question": {
- "en": "Who maintains this public bookcase?",
- "nl": "Wie is verantwoordelijk voor dit boekenruilkastje?",
- "de": "Wer unterhält diesen öffentlichen Bücherschrank?",
- "fr": "Qui entretien cette microbibliothèque ?",
- "it": "Chi mantiene questa microbiblioteca?"
- },
- "render": {
- "en": "Operated by {operator}",
- "nl": "Onderhouden door {operator}",
- "de": "Betrieben von {operator}",
- "fr": "Entretenue par {operator}",
- "it": "È gestita da {operator}"
- },
- "freeform": {
- "type": "string",
- "key": "operator"
- },
- "id": "public_bookcase-operator"
- },
- {
- "question": {
- "en": "Is this public bookcase part of a bigger network?",
- "nl": "Is dit boekenruilkastje deel van een netwerk?",
- "de": "Ist dieser öffentliche Bücherschrank Teil eines größeren Netzwerks?",
- "fr": "Cette microbibliothèque fait-elle partie d'un réseau/groupe ?",
- "it": "Questa microbiblioteca fa parte di una rete?"
- },
- "render": {
- "en": "This public bookcase is part of {brand}",
- "nl": "Dit boekenruilkastje is deel van het netwerk {brand}",
- "de": "Dieser Bücherschrank ist Teil von {brand}",
- "fr": "Cette microbibliothèque fait partie du groupe {brand}",
- "it": "Questa microbiblioteca fa parte di {brand}"
- },
- "condition": "ref=",
- "freeform": {
- "key": "brand"
- },
- "mappings": [
- {
- "then": {
- "en": "Part of the network 'Little Free Library'",
- "nl": "Deel van het netwerk 'Little Free Library'",
- "de": "Teil des Netzwerks 'Little Free Library'",
- "fr": "Fait partie du réseau Little Free Library",
- "it": "Fa parte della rete 'Little Free Library'"
- },
- "if": {
- "and": [
- "brand=Little Free Library",
- "nobrand="
- ]
- }
- },
- {
- "if": {
- "and": [
- "nobrand=yes",
- "brand="
- ]
- },
- "then": {
- "en": "This public bookcase is not part of a bigger network",
- "nl": "Dit boekenruilkastje maakt geen deel uit van een netwerk",
- "de": "Dieser öffentliche Bücherschrank ist nicht Teil eines größeren Netzwerks",
- "fr": "Cette microbibliothèque ne fait pas partie d'un réseau/groupe",
- "it": "Questa microbiblioteca non fa parte di una rete"
- }
- }
- ],
- "id": "public_bookcase-brand"
- },
- {
- "render": {
- "en": "The reference number of this public bookcase within {brand} is {ref}",
- "nl": "Het referentienummer binnen {brand} is {ref}",
- "de": "Die Referenznummer dieses öffentlichen Bücherschranks innerhalb {brand} lautet {ref}",
- "fr": "Cette microbibliothèque du réseau {brand} possède le numéro {ref}",
- "it": "Il numero identificativo di questa microbiblioteca nella rete {brand} è {ref}"
- },
- "question": {
- "en": "What is the reference number of this public bookcase?",
- "nl": "Wat is het referentienummer van dit boekenruilkastje?",
- "de": "Wie lautet die Referenznummer dieses öffentlichen Bücherschranks?",
- "fr": "Quelle est le numéro de référence de cette microbibliothèque ?",
- "it": "Qual è il numero identificativo di questa microbiblioteca?"
- },
- "condition": "brand~*",
- "freeform": {
- "key": "ref"
- },
- "mappings": [
- {
- "then": {
- "en": "This bookcase is not part of a bigger network",
- "nl": "Dit boekenruilkastje maakt geen deel uit van een netwerk",
- "de": "Dieser Bücherschrank ist nicht Teil eines größeren Netzwerks",
- "fr": "Cette microbibliothèque ne fait pas partie d'un réseau/groupe",
- "it": "Questa microbiblioteca non fa parte di una rete"
- },
- "if": {
- "and": [
- "nobrand=yes",
- "brand=",
- "ref="
- ]
- }
- }
- ],
- "id": "public_bookcase-ref"
- },
- {
- "question": {
- "en": "When was this public bookcase installed?",
- "nl": "Op welke dag werd dit boekenruilkastje geinstalleerd?",
- "de": "Wann wurde dieser öffentliche Bücherschrank installiert?",
- "fr": "Quand a été installée cette microbibliothèque ?",
- "it": "Quando è stata inaugurata questa microbiblioteca?",
- "ru": "Когда был установлен этот общественный книжный шкаф?"
- },
- "render": {
- "en": "Installed on {start_date}",
- "nl": "Geplaatst op {start_date}",
- "de": "Installiert am {start_date}",
- "fr": "Installée le {start_date}",
- "it": "È stata inaugurata il {start_date}",
- "ru": "Установлен {start_date}"
- },
- "freeform": {
- "key": "start_date",
- "type": "date"
- },
- "id": "public_bookcase-start_date"
- },
- {
- "render": {
- "en": "More info on the website",
- "nl": "Meer info op de website",
- "de": "Weitere Informationen auf der Webseite",
- "fr": "Plus d'infos sur le site web",
- "ru": "Более подробная информация на сайте",
- "it": "Maggiori informazioni sul sito web"
- },
- "question": {
- "en": "Is there a website with more information about this public bookcase?",
- "nl": "Is er een website over dit boekenruilkastje?",
- "de": "Gibt es eine Website mit weiteren Informationen über diesen öffentlichen Bücherschrank?",
- "fr": "Y a-t-il un site web avec plus d'informations sur cette microbibliothèque ?",
- "it": "C'è un sito web con maggiori informazioni su questa microbiblioteca?",
- "ru": "Есть ли веб-сайт с более подробной информацией об этом общественном книжном шкафе?"
- },
- "freeform": {
- "key": "website",
- "type": "url"
- },
- "id": "public_bookcase-website"
- }
- ],
- "deletion": {
- "softDeletionTags": {
- "and": [
- "disused:amenity=public_bookcase",
- "amenity="
- ]
- },
- "neededChangesets": 5
- },
- "filter": [
- {
- "id": "kid-books",
- "options": [
- {
- "question": "Kinderboeken aanwezig?",
- "osmTags": "books~.*children.*"
- }
- ]
- },
- {
- "id": "adult-books",
- "options": [
- {
- "question": "Boeken voor volwassenen aanwezig?",
- "osmTags": "books~.*adults.*"
- }
- ]
- },
- {
- "id": "inside",
- "options": [
- {
- "question": {
- "nl": "Binnen of buiten",
- "en": "Indoor or outdoor"
- }
- },
- {
- "question": "Binnen?",
- "osmTags": "indoor=yes"
- },
- {
- "question": "Buiten?",
- "osmTags": {
- "or": [
- "indoor=no",
- "indoor="
- ]
- }
- }
- ]
- }
- ],
- "mapRendering": [
- {
- "icon": {
- "render": "./assets/themes/bookcases/bookcase.svg"
- },
- "label": {
- "mappings": [
- {
- "if": "name~*",
- "then": "{name}
"
- }
- ]
- },
- "location": [
- "point",
- "centroid"
- ]
- },
- {
- "color": {
- "render": "#0000ff"
- },
- "width": {
- "render": "8"
- }
+ "mappings": [
+ {
+ "if": "name~*",
+ "then": {
+ "en": "Public bookcase {name}",
+ "nl": "Boekenruilkast {name}",
+ "de": "Öffentlicher Bücherschrank {name}",
+ "fr": "Microbibliothèque {name}",
+ "ru": "Общественный книжный шкаф {name}",
+ "it": "Microbiblioteca pubblica {name}"
}
+ }
]
+ },
+ "icon": {
+ "render": "./assets/themes/bookcases/bookcase.svg"
+ },
+ "label": {
+ "mappings": [
+ {
+ "if": "name~*",
+ "then": "{name}
"
+ }
+ ]
+ },
+ "color": {
+ "render": "#0000ff"
+ },
+ "width": {
+ "render": "8"
+ },
+ "presets": [
+ {
+ "title": {
+ "en": "Bookcase",
+ "nl": "Boekenruilkast",
+ "de": "Bücherschrank",
+ "fr": "Microbibliothèque",
+ "ru": "Книжный шкаф",
+ "it": "Microbiblioteca"
+ },
+ "tags": [
+ "amenity=public_bookcase"
+ ],
+ "preciseInput": {
+ "preferredBackground": "photo"
+ }
+ }
+ ],
+ "tagRenderings": [
+ "images",
+ {
+ "id": "minimap",
+ "render": "{minimap():height: 9rem; border-radius: 2.5rem; overflow:hidden;border:1px solid gray}"
+ },
+ {
+ "render": {
+ "en": "The name of this bookcase is {name}",
+ "nl": "De naam van dit boekenruilkastje is {name}",
+ "de": "Der Name dieses Bücherschrank lautet {name}",
+ "fr": "Le nom de cette microbibliothèque est {name}",
+ "ru": "Название книжного шкафа — {name}",
+ "it": "Questa microbiblioteca si chiama {name}"
+ },
+ "question": {
+ "en": "What is the name of this public bookcase?",
+ "nl": "Wat is de naam van dit boekenuilkastje?",
+ "de": "Wie heißt dieser öffentliche Bücherschrank?",
+ "fr": "Quel est le nom de cette microbibliothèque ?",
+ "ru": "Как называется этот общественный книжный шкаф?",
+ "it": "Come si chiama questa microbiblioteca pubblica?"
+ },
+ "freeform": {
+ "key": "name"
+ },
+ "mappings": [
+ {
+ "if": {
+ "and": [
+ "noname=yes",
+ "name="
+ ]
+ },
+ "then": {
+ "en": "This bookcase doesn't have a name",
+ "nl": "Dit boekenruilkastje heeft geen naam",
+ "de": "Dieser Bücherschrank hat keinen Namen",
+ "fr": "Cette microbibliothèque n'a pas de nom",
+ "ru": "У этого книжного шкафа нет названия",
+ "it": "Questa microbiblioteca non ha un nome proprio"
+ }
+ }
+ ],
+ "id": "public_bookcase-name"
+ },
+ {
+ "render": {
+ "en": "{capacity} books fit in this bookcase",
+ "nl": "Er passen {capacity} boeken",
+ "de": "{capacity} Bücher passen in diesen Bücherschrank",
+ "fr": "{capacity} livres peuvent entrer dans cette microbibliothèque",
+ "it": "Questa microbiblioteca può contenere fino a {capacity} libri",
+ "ru": "{capacity} книг помещается в этот книжный шкаф"
+ },
+ "question": {
+ "en": "How many books fit into this public bookcase?",
+ "nl": "Hoeveel boeken passen er in dit boekenruilkastje?",
+ "de": "Wie viele Bücher passen in diesen öffentlichen Bücherschrank?",
+ "fr": "Combien de livres peuvent entrer dans cette microbibliothèque ?",
+ "ru": "Сколько книг помещается в этом общественном книжном шкафу?",
+ "it": "Quanti libri può contenere questa microbiblioteca?"
+ },
+ "freeform": {
+ "key": "capacity",
+ "type": "nat",
+ "inline": true
+ },
+ "id": "public_bookcase-capacity"
+ },
+ {
+ "id": "bookcase-booktypes",
+ "question": {
+ "en": "What kind of books can be found in this public bookcase?",
+ "nl": "Voor welke doelgroep zijn de meeste boeken in dit boekenruilkastje?",
+ "de": "Welche Art von Büchern sind in diesem öffentlichen Bücherschrank zu finden?",
+ "fr": "Quel type de livres peut-on dans cette microbibliothèque ?",
+ "it": "Che tipo di libri si possono trovare in questa microbiblioteca?",
+ "ru": "Какие книги можно найти в этом общественном книжном шкафу?"
+ },
+ "mappings": [
+ {
+ "if": "books=children",
+ "then": {
+ "en": "Mostly children books",
+ "nl": "Voornamelijk kinderboeken",
+ "de": "Vorwiegend Kinderbücher",
+ "fr": "Livres pour enfants",
+ "ru": "В основном детские книги",
+ "it": "Principalmente libri per l'infanzia"
+ }
+ },
+ {
+ "if": "books=adults",
+ "then": {
+ "en": "Mostly books for adults",
+ "nl": "Voornamelijk boeken voor volwassenen",
+ "de": "Vorwiegend Bücher für Erwachsene",
+ "fr": "Livres pour les adultes",
+ "ru": "В основном книги для взрослых",
+ "it": "Principalmente libri per persone in età adulta"
+ }
+ },
+ {
+ "if": "books=children;adults",
+ "then": {
+ "en": "Both books for kids and adults",
+ "nl": "Boeken voor zowel kinderen als volwassenen",
+ "de": "Sowohl Bücher für Kinder als auch für Erwachsene",
+ "fr": "Livres pour enfants et adultes également",
+ "it": "Sia libri per l'infanzia, sia per l'età adulta",
+ "ru": "Книги и для детей, и для взрослых"
+ }
+ }
+ ]
+ },
+ {
+ "id": "bookcase-is-indoors",
+ "question": {
+ "en": "Is this bookcase located outdoors?",
+ "nl": "Staat dit boekenruilkastje binnen of buiten?",
+ "de": "Befindet sich dieser Bücherschrank im Freien?",
+ "fr": "Cette microbiliothèque est-elle en extérieur ?",
+ "it": "Questa microbiblioteca si trova all'aperto?"
+ },
+ "mappings": [
+ {
+ "then": {
+ "en": "This bookcase is located indoors",
+ "nl": "Dit boekenruilkastje staat binnen",
+ "de": "Dieser Bücherschrank befindet sich im Innenbereich",
+ "fr": "Cette microbibliothèque est en intérieur",
+ "it": "Questa microbiblioteca si trova al chiuso"
+ },
+ "if": "indoor=yes"
+ },
+ {
+ "then": {
+ "en": "This bookcase is located outdoors",
+ "nl": "Dit boekenruilkastje staat buiten",
+ "de": "Dieser Bücherschrank befindet sich im Freien",
+ "fr": "Cette microbibliothèque est en extérieur",
+ "it": "Questa microbiblioteca si trova all'aperto"
+ },
+ "if": "indoor=no"
+ },
+ {
+ "then": {
+ "en": "This bookcase is located outdoors",
+ "nl": "Dit boekenruilkastje staat buiten",
+ "de": "Dieser Bücherschrank befindet sich im Freien",
+ "fr": "Cette microbibliothèque est en extérieur",
+ "it": "Questa microbiblioteca si trova all'aperto"
+ },
+ "if": "indoor=",
+ "hideInAnswer": true
+ }
+ ]
+ },
+ {
+ "id": "bookcase-is-accessible",
+ "question": {
+ "en": "Is this public bookcase freely accessible?",
+ "nl": "Is dit boekenruilkastje publiek toegankelijk?",
+ "de": "Ist dieser öffentliche Bücherschrank frei zugänglich?",
+ "fr": "Cette microbibliothèque est-elle librement accèssible ?",
+ "it": "Questa microbiblioteca è ad accesso libero?",
+ "ru": "Имеется ли свободный доступ к этому общественному книжному шкафу?"
+ },
+ "condition": "indoor=yes",
+ "mappings": [
+ {
+ "then": {
+ "en": "Publicly accessible",
+ "nl": "Publiek toegankelijk",
+ "de": "Öffentlich zugänglich",
+ "fr": "Accèssible au public",
+ "it": "È ad accesso libero",
+ "ru": "Свободный доступ"
+ },
+ "if": "access=yes"
+ },
+ {
+ "then": {
+ "en": "Only accessible to customers",
+ "nl": "Enkel toegankelijk voor klanten",
+ "de": "Nur für Kunden zugänglich",
+ "fr": "Accèssible aux clients",
+ "it": "L'accesso è riservato ai clienti"
+ },
+ "if": "access=customers"
+ }
+ ]
+ },
+ {
+ "question": {
+ "en": "Who maintains this public bookcase?",
+ "nl": "Wie is verantwoordelijk voor dit boekenruilkastje?",
+ "de": "Wer unterhält diesen öffentlichen Bücherschrank?",
+ "fr": "Qui entretien cette microbibliothèque ?",
+ "it": "Chi mantiene questa microbiblioteca?"
+ },
+ "render": {
+ "en": "Operated by {operator}",
+ "nl": "Onderhouden door {operator}",
+ "de": "Betrieben von {operator}",
+ "fr": "Entretenue par {operator}",
+ "it": "È gestita da {operator}"
+ },
+ "freeform": {
+ "type": "string",
+ "key": "operator"
+ },
+ "id": "public_bookcase-operator"
+ },
+ {
+ "question": {
+ "en": "Is this public bookcase part of a bigger network?",
+ "nl": "Is dit boekenruilkastje deel van een netwerk?",
+ "de": "Ist dieser öffentliche Bücherschrank Teil eines größeren Netzwerks?",
+ "fr": "Cette microbibliothèque fait-elle partie d'un réseau/groupe ?",
+ "it": "Questa microbiblioteca fa parte di una rete?"
+ },
+ "render": {
+ "en": "This public bookcase is part of {brand}",
+ "nl": "Dit boekenruilkastje is deel van het netwerk {brand}",
+ "de": "Dieser Bücherschrank ist Teil von {brand}",
+ "fr": "Cette microbibliothèque fait partie du groupe {brand}",
+ "it": "Questa microbiblioteca fa parte di {brand}"
+ },
+ "condition": "ref=",
+ "freeform": {
+ "key": "brand"
+ },
+ "mappings": [
+ {
+ "then": {
+ "en": "Part of the network 'Little Free Library'",
+ "nl": "Deel van het netwerk 'Little Free Library'",
+ "de": "Teil des Netzwerks 'Little Free Library'",
+ "fr": "Fait partie du réseau Little Free Library",
+ "it": "Fa parte della rete 'Little Free Library'"
+ },
+ "if": {
+ "and": [
+ "brand=Little Free Library",
+ "nobrand="
+ ]
+ }
+ },
+ {
+ "if": {
+ "and": [
+ "nobrand=yes",
+ "brand="
+ ]
+ },
+ "then": {
+ "en": "This public bookcase is not part of a bigger network",
+ "nl": "Dit boekenruilkastje maakt geen deel uit van een netwerk",
+ "de": "Dieser öffentliche Bücherschrank ist nicht Teil eines größeren Netzwerks",
+ "fr": "Cette microbibliothèque ne fait pas partie d'un réseau/groupe",
+ "it": "Questa microbiblioteca non fa parte di una rete"
+ }
+ }
+ ],
+ "id": "public_bookcase-brand"
+ },
+ {
+ "render": {
+ "en": "The reference number of this public bookcase within {brand} is {ref}",
+ "nl": "Het referentienummer binnen {brand} is {ref}",
+ "de": "Die Referenznummer dieses öffentlichen Bücherschranks innerhalb {brand} lautet {ref}",
+ "fr": "Cette microbibliothèque du réseau {brand} possède le numéro {ref}",
+ "it": "Il numero identificativo di questa microbiblioteca nella rete {brand} è {ref}"
+ },
+ "question": {
+ "en": "What is the reference number of this public bookcase?",
+ "nl": "Wat is het referentienummer van dit boekenruilkastje?",
+ "de": "Wie lautet die Referenznummer dieses öffentlichen Bücherschranks?",
+ "fr": "Quelle est le numéro de référence de cette microbibliothèque ?",
+ "it": "Qual è il numero identificativo di questa microbiblioteca?"
+ },
+ "condition": "brand~*",
+ "freeform": {
+ "key": "ref"
+ },
+ "mappings": [
+ {
+ "then": {
+ "en": "This bookcase is not part of a bigger network",
+ "nl": "Dit boekenruilkastje maakt geen deel uit van een netwerk",
+ "de": "Dieser Bücherschrank ist nicht Teil eines größeren Netzwerks",
+ "fr": "Cette microbibliothèque ne fait pas partie d'un réseau/groupe",
+ "it": "Questa microbiblioteca non fa parte di una rete"
+ },
+ "if": {
+ "and": [
+ "nobrand=yes",
+ "brand=",
+ "ref="
+ ]
+ }
+ }
+ ],
+ "id": "public_bookcase-ref"
+ },
+ {
+ "question": {
+ "en": "When was this public bookcase installed?",
+ "nl": "Op welke dag werd dit boekenruilkastje geinstalleerd?",
+ "de": "Wann wurde dieser öffentliche Bücherschrank installiert?",
+ "fr": "Quand a été installée cette microbibliothèque ?",
+ "it": "Quando è stata inaugurata questa microbiblioteca?",
+ "ru": "Когда был установлен этот общественный книжный шкаф?"
+ },
+ "render": {
+ "en": "Installed on {start_date}",
+ "nl": "Geplaatst op {start_date}",
+ "de": "Installiert am {start_date}",
+ "fr": "Installée le {start_date}",
+ "it": "È stata inaugurata il {start_date}",
+ "ru": "Установлен {start_date}"
+ },
+ "freeform": {
+ "key": "start_date",
+ "type": "date"
+ },
+ "id": "public_bookcase-start_date"
+ },
+ {
+ "render": {
+ "en": "More info on the website",
+ "nl": "Meer info op de website",
+ "de": "Weitere Informationen auf der Webseite",
+ "fr": "Plus d'infos sur le site web",
+ "ru": "Более подробная информация на сайте",
+ "it": "Maggiori informazioni sul sito web"
+ },
+ "question": {
+ "en": "Is there a website with more information about this public bookcase?",
+ "nl": "Is er een website over dit boekenruilkastje?",
+ "de": "Gibt es eine Website mit weiteren Informationen über diesen öffentlichen Bücherschrank?",
+ "fr": "Y a-t-il un site web avec plus d'informations sur cette microbibliothèque ?",
+ "it": "C'è un sito web con maggiori informazioni su questa microbiblioteca?",
+ "ru": "Есть ли веб-сайт с более подробной информацией об этом общественном книжном шкафе?"
+ },
+ "freeform": {
+ "key": "website",
+ "type": "url"
+ },
+ "id": "public_bookcase-website"
+ }
+ ],
+ "deletion": {
+ "softDeletionTags": {
+ "and": [
+ "disused:amenity=public_bookcase",
+ "amenity="
+ ]
+ },
+ "neededChangesets": 5
+ },
+ "filter": [
+ {
+ "id": "kid-books",
+ "options": [
+ {
+ "question": "Kinderboeken aanwezig?",
+ "osmTags": "books~.*children.*"
+ }
+ ]
+ },
+ {
+ "id": "adult-books",
+ "options": [
+ {
+ "question": "Boeken voor volwassenen aanwezig?",
+ "osmTags": "books~.*adults.*"
+ }
+ ]
+ },
+ {
+ "id": "inside",
+ "options": [
+ {
+ "question": {
+ "nl": "Binnen of buiten",
+ "en": "Indoor or outdoor",
+ "de": "Innen oder Außen"
+ }
+ },
+ {
+ "question": "Binnen?",
+ "osmTags": "indoor=yes"
+ },
+ {
+ "question": "Buiten?",
+ "osmTags": {
+ "or": [
+ "indoor=no",
+ "indoor="
+ ]
+ }
+ }
+ ]
+ }
+ ],
+ "mapRendering": [
+ {
+ "icon": {
+ "render": "./assets/themes/bookcases/bookcase.svg"
+ },
+ "label": {
+ "mappings": [
+ {
+ "if": "name~*",
+ "then": "{name}
"
+ }
+ ]
+ },
+ "location": [
+ "point",
+ "centroid"
+ ]
+ },
+ {
+ "color": {
+ "render": "#0000ff"
+ },
+ "width": {
+ "render": "8"
+ }
+ }
+ ],
+ "allowMove": true
}
\ No newline at end of file
diff --git a/assets/themes/grb.json b/assets/themes/grb.json
deleted file mode 100644
index fe0a3ed2f..000000000
--- a/assets/themes/grb.json
+++ /dev/null
@@ -1,231 +0,0 @@
-{
- "id": "grb",
- "title": {
- "nl": "GRB Fixup"
- },
- "shortDescription": {
- "nl": "Grb Fixup"
- },
- "description": {
- "nl": "GRB Fixup"
- },
- "language": [
- "nl"
- ],
- "maintainer": "",
- "icon": "./assets/svg/bug.svg",
- "version": "0",
- "startLat": 51.2132,
- "startLon": 3.231,
- "startZoom": 14,
- "widenFactor": 2,
- "socialImage": "",
- "layers": [
- {
- "id": "grb-fixmes",
- "name": {
- "nl": "Fixmes op gebouwen"
- },
- "minzoom": 12,
- "source": {
- "osmTags": {
- "and": [
- "fixme~*",
- "building~*"
- ]
- }
- },
- "calculatedTags": [
- "_grbNumber=(feat.properties.fixme?.match(/GRB thinks that this has number ([^;]+)/ ) ?? ['','none']) [1]"
- ],
- "title": {
- "render": {
- "nl": "{addr:street} {addr:housenumber}"
- },
- "mappings": [
- {
- "if": {
- "and": [
- "fixme~*"
- ]
- },
- "then": {
- "nl": "{fixme}"
- }
- }
- ]
- },
- "description": {
- "nl": "Dit gebouw heeft een foutmelding"
- },
- "tagRenderings": [
- {
- "id": "grb-housenumber",
- "render": {
- "nl": "Het huisnummer is {addr:housenumber}"
- },
- "question": {
- "nl": "Wat is het huisnummer?"
- },
- "freeform": {
- "key": "addr:housenumber"
- },
- "mappings": [
- {
- "if": {
- "and": [
- "not:addr:housenumber=yes",
- "addr:housenumber="
- ]
- },
- "then": {
- "nl": "Geen huisnummer"
- }
- },
- {
- "if": {
- "and": [
- "addr:housenumber:={_grbNumber}",
- "fixme="
- ]
- },
- "then": "Het huisnummer is {_grbNumber}, wat overeenkomt met het GRB",
- "hideInAnswer": {
- "or": [
- "_grbNumber=",
- "_grbNumber=none",
- "_grbNumber=no number"
- ]
- }
- },
- {
- "if": {
- "and": [
- "addr:housenumber=",
- "not:addr:housenumber=yes",
- "fixme="
- ]
- },
- "then": "Dit gebouw heeft geen nummer, net zoals in het GRB",
- "hideInAnswer": "_grbNumber!=no number"
- }
- ]
- },
- {
- "id": "grb-unit",
- "question": "Wat is de wooneenheid-aanduiding?",
- "render": {
- "nl": "De wooneenheid-aanduiding is {addr:unit} "
- },
- "freeform": {
- "key": "addr:unit"
- },
- "mappings": [
- {
- "if": "addr:unit=",
- "then": "Geen wooneenheid-nummer"
- }
- ]
- },
- {
- "id": "grb-street",
- "render": {
- "nl": "De straat is {addr:street}"
- },
- "freeform": {
- "key": "addr:street"
- },
- "question": {
- "nl": "Wat is de straat?"
- }
- },
- {
- "id": "grb-fixme",
- "render": {
- "nl": "De fixme is {fixme}"
- },
- "question": {
- "nl": "Wat zegt de fixme?"
- },
- "freeform": {
- "key": "fixme"
- },
- "mappings": [
- {
- "if": {
- "and": [
- "fixme="
- ]
- },
- "then": {
- "nl": "Geen fixme"
- }
- }
- ]
- },
- {
- "id": "grb-min-level",
- "render": {
- "nl": "Dit gebouw begint maar op de {building:min_level} verdieping"
- },
- "question": {
- "nl": "Hoeveel verdiepingen ontbreken?"
- },
- "freeform": {
- "key": "building:min_level",
- "type": "pnat"
- }
- }
- ],
- "label": {
- "mappings": [
- {
- "if": "addr:housenumber~*",
- "then": "{addr:housenumber}
"
- }
- ]
- },
- "width": {
- "render": "2"
- },
- "iconSize": {
- "render": "40,40,center"
- },
- "dashes": "2 2",
- "color": {
- "render": "#00f"
- },
- "wayHandling": 2,
- "presets": [],
- "mapRendering": [
- {
- "label": {
- "mappings": [
- {
- "if": "addr:housenumber~*",
- "then": "{addr:housenumber}
"
- }
- ]
- },
- "iconSize": {
- "render": "40,40,center"
- },
- "location": [
- "point",
- "centroid"
- ]
- },
- {
- "color": {
- "render": "#00f"
- },
- "width": {
- "render": "2"
- }
- }
- ]
- }
- ],
- "hideFromOverview": true,
- "defaultBackgroundId": "AGIVFlandersGRB"
-}
\ No newline at end of file
diff --git a/assets/themes/grb_import/README.md b/assets/themes/grb_import/README.md
new file mode 100644
index 000000000..2ed7bfbbf
--- /dev/null
+++ b/assets/themes/grb_import/README.md
@@ -0,0 +1,20 @@
+ GRB Import helper
+===================
+
+
+Preparing the CRAB dataset
+--------------------------
+
+````
+# The original data is downloaded from https://download.vlaanderen.be/Producten/Detail?id=447&title=CRAB_Adressenlijst# (the GML-file here )
+wget https://downloadagiv.blob.core.windows.net/crab-adressenlijst/GML/CRAB_Adressenlijst_GML.zip
+
+# Extract the zip file
+unzip CRAB_Adressenlijst_GML.zip
+
+# convert the pesky GML file into geojson
+ogr2ogr -progress -t_srs WGS84 -f \"GeoJson\" CRAB.geojson CrabAdr.gml
+
+# When done, this big file is sliced into tiles with the slicer script
+node --max_old_space_size=8000 $(which ts-node) ~/git/MapComplete/scripts/slice.ts CRAB.geojson 18 ~/git/pietervdvn.github.io/CRAB_2021_10_26
+````
\ No newline at end of file
diff --git a/assets/themes/grb_import/grb.json b/assets/themes/grb_import/grb.json
new file mode 100644
index 000000000..3a97f1c9a
--- /dev/null
+++ b/assets/themes/grb_import/grb.json
@@ -0,0 +1,494 @@
+{
+ "id": "grb",
+ "title": {
+ "nl": "GRB Fixup"
+ },
+ "shortDescription": {
+ "nl": "Grb Fixup"
+ },
+ "description": {
+ "nl": "GRB Fixup"
+ },
+ "language": [
+ "nl"
+ ],
+ "maintainer": "",
+ "icon": "./assets/svg/bug.svg",
+ "version": "0",
+ "startLat": 51.2132,
+ "startLon": 3.231,
+ "startZoom": 14,
+ "widenFactor": 2,
+ "socialImage": "",
+ "layers": [
+ {
+ "id": "OSM-buildings",
+ "name": "All OSM-buildings",
+ "source": {
+ "osmTags": "building~*",
+ "maxCacheAge": 0
+ },
+ "minzoom": 18,
+ "width": {
+ "render": "2"
+ },
+ "color": {
+ "render": "#00c",
+ "mappings": [
+ {
+ "if": "building=house",
+ "then": "#a00"
+ },
+ {
+ "if": "building=shed",
+ "then": "#563e02"
+ },
+ {
+ "if": {
+ "or": [
+ "building=garage",
+ "building=garages"
+ ]
+ },
+ "then": "#f9bfbb"
+ },
+ {
+ "if": "building=yes",
+ "then": "#0774f2"
+ }
+ ]
+ },
+ "title": "OSM-gebouw",
+ "tagRenderings": [
+ "all_tags"
+ ]
+ },
+ {
+ "id": "All OSM objects",
+ "name": "All OSM Objects",
+ "source": {
+ "osmTags": {
+ "and": [
+ "id~*",
+ "landuse=",
+ "place=",
+ "disused:power=",
+ "power=",
+ "type!=boundary",
+ "boundary=",
+ {
+ "or": [
+ "level=",
+ "level=0"
+ ]
+ },
+ {
+ "or": [
+ "layer=0",
+ "layer="
+ ]
+ }
+ ]
+ },
+ "maxCacheAge": 0
+ },
+ "minzoom": 18,
+ "color": {
+ "render": "#00c"
+ },
+ "width": {
+ "render": "1"
+ },
+ "title": {
+ "render": {
+ "*": "OSM-Object"
+ }
+ },
+ "tagRenderings": [
+ "all_tags"
+ ]
+ },
+ {
+ "id": "osm-fixmes",
+ "name": {
+ "nl": "Fixmes op gebouwen"
+ },
+ "minzoom": 21,
+ "source": {
+ "maxCacheAge": 0,
+ "osmTags": {
+ "and": [
+ "fixme~*",
+ "building~*"
+ ]
+ }
+ },
+ "calculatedTags": [
+ "_grbNumber=(feat.properties.fixme?.match(/GRB thinks that this has number ([^;]+)/ ) ?? ['','none']) [1]"
+ ],
+ "title": {
+ "render": {
+ "nl": "{addr:street} {addr:housenumber}"
+ },
+ "mappings": [
+ {
+ "if": {
+ "and": [
+ "fixme~*"
+ ]
+ },
+ "then": {
+ "nl": "{fixme}"
+ }
+ }
+ ]
+ },
+ "description": {
+ "nl": "Dit gebouw heeft een foutmelding"
+ },
+ "tagRenderings": [
+ {
+ "id": "grb-housenumber",
+ "render": {
+ "nl": "Het huisnummer is {addr:housenumber}"
+ },
+ "question": {
+ "nl": "Wat is het huisnummer?"
+ },
+ "freeform": {
+ "key": "addr:housenumber"
+ },
+ "mappings": [
+ {
+ "if": {
+ "and": [
+ "not:addr:housenumber=yes",
+ "addr:housenumber="
+ ]
+ },
+ "then": {
+ "nl": "Geen huisnummer"
+ }
+ },
+ {
+ "if": {
+ "and": [
+ "addr:housenumber:={_grbNumber}",
+ "fixme="
+ ]
+ },
+ "then": "Het huisnummer is {_grbNumber}, wat overeenkomt met het GRB",
+ "hideInAnswer": {
+ "or": [
+ "_grbNumber=",
+ "_grbNumber=none",
+ "_grbNumber=no number"
+ ]
+ }
+ },
+ {
+ "if": {
+ "and": [
+ "addr:housenumber=",
+ "not:addr:housenumber=yes",
+ "fixme="
+ ]
+ },
+ "then": "Dit gebouw heeft geen nummer, net zoals in het GRB",
+ "hideInAnswer": "_grbNumber!=no number"
+ }
+ ]
+ },
+ {
+ "id": "grb-unit",
+ "question": "Wat is de wooneenheid-aanduiding?",
+ "render": {
+ "nl": "De wooneenheid-aanduiding is {addr:unit} "
+ },
+ "freeform": {
+ "key": "addr:unit"
+ },
+ "mappings": [
+ {
+ "if": "addr:unit=",
+ "then": "Geen wooneenheid-nummer"
+ }
+ ]
+ },
+ {
+ "id": "grb-street",
+ "render": {
+ "nl": "De straat is {addr:street}"
+ },
+ "freeform": {
+ "key": "addr:street"
+ },
+ "question": {
+ "nl": "Wat is de straat?"
+ }
+ },
+ {
+ "id": "grb-fixme",
+ "render": {
+ "nl": "De fixme is {fixme}"
+ },
+ "question": {
+ "nl": "Wat zegt de fixme?"
+ },
+ "freeform": {
+ "key": "fixme"
+ },
+ "mappings": [
+ {
+ "if": {
+ "and": [
+ "fixme="
+ ]
+ },
+ "then": {
+ "nl": "Geen fixme"
+ }
+ }
+ ]
+ },
+ {
+ "id": "grb-min-level",
+ "render": {
+ "nl": "Dit gebouw begint maar op de {building:min_level} verdieping"
+ },
+ "question": {
+ "nl": "Hoeveel verdiepingen ontbreken?"
+ },
+ "freeform": {
+ "key": "building:min_level",
+ "type": "pnat"
+ }
+ }
+ ],
+ "label": {
+ "mappings": [
+ {
+ "if": "addr:housenumber~*",
+ "then": "{addr:housenumber}
"
+ }
+ ]
+ },
+ "width": {
+ "render": "2"
+ },
+ "iconSize": {
+ "render": "40,40,center"
+ },
+ "dashes": "2 2",
+ "color": {
+ "render": "#00f"
+ },
+ "wayHandling": 2,
+ "presets": []
+ },
+ {
+ "id": "crab-addresses 2021-10-26",
+ "source": {
+ "osmTags": "HUISNR~*",
+ "geoJson": "https://raw.githubusercontent.com/pietervdvn/pietervdvn.github.io/master/CRAB_2021_10_26/tile_{z}_{x}_{y}.geojson",
+ "#geoJson": "https://pietervdvn.github.io/CRAB_2021_10_26/tile_{z}_{x}_{y}.geojson",
+ "geoJsonZoomLevel": 18,
+ "maxCacheAge": 0
+ },
+ "minzoom": 19,
+ "name": "CRAB-addressen",
+ "title": "CRAB-adres",
+ "icon": "circle:#bb3322",
+ "iconSize": "15,15,center",
+ "tagRenderings": [
+ "all_tags",
+ {
+ "id": "import-button",
+ "render": "{import_button(addr:street=$STRAATNM; addr:housenumber=$HUISNR)}"
+ }
+ ]
+ },
+ {
+ "id": "grb-fixmes",
+ "name": {
+ "nl": "Fixmes op gebouwen"
+ },
+ "minzoom": 12,
+ "source": {
+ "maxCacheAge": 0,
+ "osmTags": {
+ "and": [
+ "fixme~*",
+ "building~*"
+ ]
+ }
+ },
+ "calculatedTags": [
+ "_grbNumber=(feat.properties.fixme?.match(/GRB thinks that this has number ([^;]+)/ ) ?? ['','none']) [1]"
+ ],
+ "title": {
+ "render": {
+ "nl": "{addr:street} {addr:housenumber}"
+ },
+ "mappings": [
+ {
+ "if": {
+ "and": [
+ "fixme~*"
+ ]
+ },
+ "then": {
+ "nl": "{fixme}"
+ }
+ }
+ ]
+ },
+ "description": {
+ "nl": "Dit gebouw heeft een foutmelding"
+ },
+ "tagRenderings": [
+ {
+ "id": "grb-housenumber",
+ "render": {
+ "nl": "Het huisnummer is {addr:housenumber}"
+ },
+ "question": {
+ "nl": "Wat is het huisnummer?"
+ },
+ "freeform": {
+ "key": "addr:housenumber"
+ },
+ "mappings": [
+ {
+ "if": {
+ "and": [
+ "not:addr:housenumber=yes",
+ "addr:housenumber="
+ ]
+ },
+ "then": {
+ "nl": "Geen huisnummer"
+ }
+ },
+ {
+ "if": {
+ "and": [
+ "addr:housenumber:={_grbNumber}",
+ "fixme="
+ ]
+ },
+ "then": "Het huisnummer is {_grbNumber}, wat overeenkomt met het GRB",
+ "hideInAnswer": {
+ "or": [
+ "_grbNumber=",
+ "_grbNumber=none",
+ "_grbNumber=no number"
+ ]
+ }
+ },
+ {
+ "if": {
+ "and": [
+ "addr:housenumber=",
+ "not:addr:housenumber=yes",
+ "fixme="
+ ]
+ },
+ "then": "Dit gebouw heeft geen nummer, net zoals in het GRB",
+ "hideInAnswer": "_grbNumber!=no number"
+ }
+ ]
+ },
+ {
+ "id": "grb-unit",
+ "question": "Wat is de wooneenheid-aanduiding?",
+ "render": {
+ "nl": "De wooneenheid-aanduiding is {addr:unit} "
+ },
+ "freeform": {
+ "key": "addr:unit"
+ },
+ "mappings": [
+ {
+ "if": "addr:unit=",
+ "then": "Geen wooneenheid-nummer"
+ }
+ ]
+ },
+ {
+ "id": "grb-street",
+ "render": {
+ "nl": "De straat is {addr:street}"
+ },
+ "freeform": {
+ "key": "addr:street"
+ },
+ "question": {
+ "nl": "Wat is de straat?"
+ }
+ },
+ {
+ "id": "grb-fixme",
+ "render": {
+ "nl": "De fixme is {fixme}"
+ },
+ "question": {
+ "nl": "Wat zegt de fixme?"
+ },
+ "freeform": {
+ "key": "fixme"
+ },
+ "mappings": [
+ {
+ "if": {
+ "and": [
+ "fixme="
+ ]
+ },
+ "then": {
+ "nl": "Geen fixme"
+ }
+ }
+ ]
+ },
+ {
+ "id": "grb-min-level",
+ "render": {
+ "nl": "Dit gebouw begint maar op de {building:min_level} verdieping"
+ },
+ "question": {
+ "nl": "Hoeveel verdiepingen ontbreken?"
+ },
+ "freeform": {
+ "key": "building:min_level",
+ "type": "pnat"
+ }
+ }
+ ],
+ "label": {
+ "mappings": [
+ {
+ "if": "addr:housenumber~*",
+ "then": "{addr:housenumber}
"
+ }
+ ]
+ },
+ "width": {
+ "render": "2"
+ },
+ "iconSize": {
+ "render": "40,40,center"
+ },
+ "dashes": "2 2",
+ "color": {
+ "render": "#00f"
+ },
+ "wayHandling": 2,
+ "presets": []
+ }
+ ],
+ "hideFromOverview": true,
+ "defaultBackgroundId": "AGIVFlandersGRB",
+ "overpassMaxZoom": 18,
+ "osmApiTileSize": 17
+}
\ No newline at end of file
diff --git a/assets/themes/uk_addresses/license_info.json b/assets/themes/uk_addresses/license_info.json
index 7d805cee4..4bcce8003 100644
--- a/assets/themes/uk_addresses/license_info.json
+++ b/assets/themes/uk_addresses/license_info.json
@@ -1,4 +1,34 @@
[
+ {
+ "path": "Commemorative_plaque_on_Elizabeth_House_-_geograph.org.uk_-_2693028.jpg",
+ "license": "CC-BY-SA 2.0 Unported",
+ "authors": [
+ "Basher Eyre"
+ ],
+ "sources": [
+ "https://commons.wikimedia.org/wiki/File:Commemorative_plaque_on_Elizabeth_House_-_geograph.org.uk_-_2693028.jpg"
+ ]
+ },
+ {
+ "path": "Plaque,_Raphoe_House_-_geograph.org.uk_-_1925685.jpg",
+ "license": "CC-BY-SA 2.0",
+ "authors": [
+ "Kenneth Allen"
+ ],
+ "sources": [
+ "https://commons.wikimedia.org/wiki/File:Plaque,_Raphoe_House_-_geograph.org.uk_-_1925685.jpg"
+ ]
+ },
+ {
+ "path": "Plaque,_Séamus_Roddy_House_-_geograph.org.uk_-_2000318.jpg",
+ "license": "CC-BY-SA 2.0 Unported",
+ "authors": [
+ "Kenneth Allen"
+ ],
+ "sources": [
+ "https://commons.wikimedia.org/wiki/File:Plaque,_S%C3%A9amus_Roddy_House_-_geograph.org.uk_-_2000318.jpg"
+ ]
+ },
{
"path": "housenumber_add.svg",
"license": "CC0",
diff --git a/assets/themes/uk_addresses/uk_addresses.json b/assets/themes/uk_addresses/uk_addresses.json
index 5fae7d09c..03c1de997 100644
--- a/assets/themes/uk_addresses/uk_addresses.json
+++ b/assets/themes/uk_addresses/uk_addresses.json
@@ -40,7 +40,8 @@
"maxZoom": 20,
"defaultState": false,
"name": {
- "en": "Property boundaries by osmuk.org"
+ "en": "Property boundaries by osmuk.org",
+ "de": "Grenzverläufe gemäß osmuk.org"
}
}
],
@@ -112,29 +113,6 @@
}
]
}
- ],
- "mapRendering": [
- {
- "icon": {
- "render": "./assets/themes/uk_addresses/housenumber_unknown.svg",
- "mappings": [
- {
- "if": "_embedding_object:id~*",
- "then": "./assets/themes/uk_addresses/housenumber_unknown_small.svg"
- },
- {
- "if": "_imported=yes",
- "then": "./assets/themes/uk_addresses/housenumber_unknown_small.svg"
- }
- ]
- },
- "iconSize": {
- "render": "40,40,center"
- },
- "location": [
- "point"
- ]
- }
]
},
{
@@ -290,60 +268,7 @@
"then": "#ff0"
}
]
- },
- "mapRendering": [
- {
- "icon": {
- "render": "./assets/themes/uk_addresses/housenumber_ok.svg",
- "mappings": [
- {
- "if": {
- "or": [
- {
- "and": [
- "addr:housenumber=",
- "nohousenumber!=yes"
- ]
- },
- "addr:street="
- ]
- },
- "then": "./assets/themes/uk_addresses/housenumber_unknown.svg"
- }
- ]
- },
- "iconSize": {
- "render": "40,40,center"
- },
- "location": [
- "point"
- ]
- },
- {
- "color": {
- "render": "#00f",
- "mappings": [
- {
- "if": {
- "or": [
- {
- "and": [
- "addr:housenumber=",
- "nohousenumber!=yes"
- ]
- },
- "addr:street="
- ]
- },
- "then": "#ff0"
- }
- ]
- },
- "width": {
- "render": "8"
- }
- }
- ]
+ }
},
{
"id": "named_streets",
@@ -361,22 +286,9 @@
},
"width": {
"render": "0"
- },
- "mapRendering": [
- {
- "location": [
- "point"
- ]
- },
- {
- "color": {
- "render": "#ccc"
- },
- "width": {
- "render": "0"
- }
- }
- ]
+ }
}
- ]
+ ],
+ "enableShareScreen": false,
+ "enableMoreQuests": false
}
\ No newline at end of file
diff --git a/index.ts b/index.ts
index 92ba9da66..a383eaac2 100644
--- a/index.ts
+++ b/index.ts
@@ -33,8 +33,6 @@ if (location.href.startsWith("http://buurtnatuur.be")) {
class Init {
-
-
public static Init(layoutToUse: LayoutConfig, encoded: string) {
if(layoutToUse === null){
diff --git a/scripts/slice.ts b/scripts/slice.ts
new file mode 100644
index 000000000..342dc22f9
--- /dev/null
+++ b/scripts/slice.ts
@@ -0,0 +1,147 @@
+import * as fs from "fs";
+import TiledFeatureSource from "../Logic/FeatureSource/TiledFeatureSource/TiledFeatureSource";
+import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource";
+import * as readline from "readline";
+import ScriptUtils from "./ScriptUtils";
+
+async function readFeaturesFromLineDelimitedJsonFile(inputFile: string): Promise {
+ const fileStream = fs.createReadStream(inputFile);
+
+ const rl = readline.createInterface({
+ input: fileStream,
+ crlfDelay: Infinity
+ });
+ // Note: we use the crlfDelay option to recognize all instances of CR LF
+ // ('\r\n') in input.txt as a single line break.
+
+ const allFeatures: any[] = []
+ // @ts-ignore
+ for await (const line of rl) {
+ try {
+ allFeatures.push(JSON.parse(line))
+ } catch (e) {
+ console.error("Could not parse", line)
+ break
+ }
+ if (allFeatures.length % 10000 === 0) {
+ ScriptUtils.erasableLog("Loaded ", allFeatures.length, "features up till now")
+ }
+ }
+ return allFeatures
+}
+
+async function readGeojsonLineByLine(inputFile: string): Promise {
+ const fileStream = fs.createReadStream(inputFile);
+
+ const rl = readline.createInterface({
+ input: fileStream,
+ crlfDelay: Infinity
+ });
+ // Note: we use the crlfDelay option to recognize all instances of CR LF
+ // ('\r\n') in input.txt as a single line break.
+
+ const allFeatures: any[] = []
+ let featuresSeen = false
+ // @ts-ignore
+ for await (let line: string of rl) {
+ if (!featuresSeen && line.startsWith("\"features\":")) {
+ featuresSeen = true;
+ continue;
+ }
+ if (!featuresSeen) {
+ continue
+ }
+ if (line.endsWith(",")) {
+ line = line.substring(0, line.length - 1)
+ }
+
+ try {
+ allFeatures.push(JSON.parse(line))
+ } catch (e) {
+ console.error("Could not parse", line)
+ break
+ }
+ if (allFeatures.length % 10000 === 0) {
+ ScriptUtils.erasableLog("Loaded ", allFeatures.length, "features up till now")
+ }
+ }
+ return allFeatures
+}
+
+async function readFeaturesFromGeoJson(inputFile: string): Promise {
+ try {
+ return JSON.parse(fs.readFileSync(inputFile, "UTF-8")).features
+ } catch (e) {
+ // We retry, but with a line-by-line approach
+ return await readGeojsonLineByLine(inputFile)
+ }
+}
+
+async function main(args: string[]) {
+
+ console.log("GeoJSON slicer")
+ if (args.length < 3) {
+ console.log("USAGE: ")
+ return
+ }
+
+ const inputFile = args[0]
+ const zoomlevel = Number(args[1])
+ const outputDirectory = args[2]
+
+ if (!fs.existsSync(outputDirectory)) {
+ fs.mkdirSync(outputDirectory)
+ console.log("Directory created")
+ }
+ console.log("Using directory ", outputDirectory)
+
+
+ let allFeatures: any [];
+ if (inputFile.endsWith(".geojson")) {
+ allFeatures = await readFeaturesFromGeoJson(inputFile)
+ } else {
+ allFeatures = await readFeaturesFromLineDelimitedJsonFile(inputFile)
+ }
+
+
+ console.log("Loaded all", allFeatures.length, "points")
+
+ const keysToRemove = ["STRAATNMID", "GEMEENTE", "POSTCODE"]
+ for (const f of allFeatures) {
+ for (const keyToRm of keysToRemove) {
+ delete f.properties[keyToRm]
+ }
+ delete f.bbox
+ }
+
+ //const knownKeys = Utils.Dedup([].concat(...allFeatures.map(f => Object.keys(f.properties))))
+ //console.log("Kept keys: ", knownKeys)
+
+ TiledFeatureSource.createHierarchy(
+ new StaticFeatureSource(allFeatures, false),
+ {
+ minZoomLevel: zoomlevel,
+ maxZoomLevel: zoomlevel,
+ maxFeatureCount: Number.MAX_VALUE,
+ registerTile: tile => {
+ const path = `${outputDirectory}/tile_${tile.z}_${tile.x}_${tile.y}.geojson`
+ const features = tile.features.data.map(ff => ff.feature)
+ features.forEach(f => {
+ delete f.bbox
+ })
+ fs.writeFileSync(path, JSON.stringify({
+ "type": "FeatureCollection",
+ "features": features
+ }, null, " "))
+ ScriptUtils.erasableLog("Written ", path, "which has ", tile.features.data.length, "features")
+ }
+ }
+ )
+
+}
+
+let args = [...process.argv]
+args.splice(0, 2)
+main(args).then(_ => {
+ console.log("All done!")
+});