diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000..e1002670c3 Binary files /dev/null and b/.DS_Store differ diff --git a/Customizations/JSON/FilterConfig.ts b/Customizations/JSON/FilterConfig.ts new file mode 100644 index 0000000000..4993baf4e3 --- /dev/null +++ b/Customizations/JSON/FilterConfig.ts @@ -0,0 +1,27 @@ +import { TagsFilter } from "../../Logic/Tags/TagsFilter"; +import { Translation } from "../../UI/i18n/Translation"; +import Translations from "../../UI/i18n/Translations"; +import FilterConfigJson from "./FilterConfigJson"; +import { FromJSON } from "./FromJSON"; + +export default class FilterConfig { + readonly options: { + question: Translation; + osmTags: TagsFilter; + }[]; + + constructor(json: FilterConfigJson, context: string) { + this.options = json.options.map((option, i) => { + const question = Translations.T( + option.question, + context + ".options-[" + i + "].question" + ); + const osmTags = FromJSON.Tag( + option.osmTags, + `${context}.options-[${i}].osmTags` + ); + + return { question: question, osmTags: osmTags }; + }); + } +} diff --git a/Customizations/JSON/FilterConfigJson.ts b/Customizations/JSON/FilterConfigJson.ts new file mode 100644 index 0000000000..082fd7fe0a --- /dev/null +++ b/Customizations/JSON/FilterConfigJson.ts @@ -0,0 +1,11 @@ +import { AndOrTagConfigJson } from "./TagConfigJson"; + +export default interface FilterConfigJson { + /** + * The options for a filter + * If there are multiple options these will be a list of radio buttons + * If there is only one option this will be a checkbox + * Filtering is done based on the given osmTags that are compared to the objects in that layer. + */ + options: { question: string | any; osmTags: AndOrTagConfigJson | string }[]; +} diff --git a/Customizations/JSON/LayerConfig.ts b/Customizations/JSON/LayerConfig.ts index 0acf8198f1..994b4c37c5 100644 --- a/Customizations/JSON/LayerConfig.ts +++ b/Customizations/JSON/LayerConfig.ts @@ -1,489 +1,562 @@ import Translations from "../../UI/i18n/Translations"; import TagRenderingConfig from "./TagRenderingConfig"; -import {LayerConfigJson} from "./LayerConfigJson"; -import {FromJSON} from "./FromJSON"; +import { LayerConfigJson } from "./LayerConfigJson"; +import { FromJSON } from "./FromJSON"; import SharedTagRenderings from "../SharedTagRenderings"; -import {TagRenderingConfigJson} from "./TagRenderingConfigJson"; -import {Translation} from "../../UI/i18n/Translation"; +import { TagRenderingConfigJson } from "./TagRenderingConfigJson"; +import { Translation } from "../../UI/i18n/Translation"; import Svg from "../../Svg"; -import {Utils} from "../../Utils"; +import { Utils } from "../../Utils"; import Combine from "../../UI/Base/Combine"; -import {VariableUiElement} from "../../UI/Base/VariableUIElement"; -import {UIEventSource} from "../../Logic/UIEventSource"; -import {FixedUiElement} from "../../UI/Base/FixedUiElement"; +import { VariableUiElement } from "../../UI/Base/VariableUIElement"; +import { UIEventSource } from "../../Logic/UIEventSource"; +import { FixedUiElement } from "../../UI/Base/FixedUiElement"; import SourceConfig from "./SourceConfig"; -import {TagsFilter} from "../../Logic/Tags/TagsFilter"; -import {Tag} from "../../Logic/Tags/Tag"; +import { TagsFilter } from "../../Logic/Tags/TagsFilter"; +import { Tag } from "../../Logic/Tags/Tag"; import BaseUIElement from "../../UI/BaseUIElement"; -import {Unit} from "./Denomination"; +import { Unit } from "./Denomination"; import DeleteConfig from "./DeleteConfig"; +import FilterConfig from "./FilterConfig"; export default class LayerConfig { + static WAYHANDLING_DEFAULT = 0; + static WAYHANDLING_CENTER_ONLY = 1; + static WAYHANDLING_CENTER_AND_WAY = 2; + id: string; + name: Translation; + description: Translation; + source: SourceConfig; + calculatedTags: [string, string][]; + doNotDownload: boolean; + passAllFeatures: boolean; + isShown: TagRenderingConfig; + minzoom: number; + maxzoom: number; + title?: TagRenderingConfig; + titleIcons: TagRenderingConfig[]; + icon: TagRenderingConfig; + iconOverlays: { if: TagsFilter; then: TagRenderingConfig; badge: boolean }[]; + iconSize: TagRenderingConfig; + label: TagRenderingConfig; + rotation: TagRenderingConfig; + color: TagRenderingConfig; + width: TagRenderingConfig; + dashArray: TagRenderingConfig; + wayHandling: number; + public readonly units: Unit[]; + public readonly deletion: DeleteConfig | null; - static WAYHANDLING_DEFAULT = 0; - static WAYHANDLING_CENTER_ONLY = 1; - static WAYHANDLING_CENTER_AND_WAY = 2; + presets: { + title: Translation; + tags: Tag[]; + description?: Translation; + }[]; - id: string; - name: Translation - description: Translation; - source: SourceConfig; - calculatedTags: [string, string][] - doNotDownload: boolean; - passAllFeatures: boolean; - isShown: TagRenderingConfig; - minzoom: number; - maxzoom: number; - title?: TagRenderingConfig; - titleIcons: TagRenderingConfig[]; - icon: TagRenderingConfig; - iconOverlays: { if: TagsFilter, then: TagRenderingConfig, badge: boolean }[] - iconSize: TagRenderingConfig; - label: TagRenderingConfig; - rotation: TagRenderingConfig; - color: TagRenderingConfig; - width: TagRenderingConfig; - dashArray: TagRenderingConfig; - wayHandling: number; - public readonly units: Unit[]; - public readonly deletion: DeleteConfig | null + tagRenderings: TagRenderingConfig[]; + filters: FilterConfig[]; - presets: { - title: Translation, - tags: Tag[], - description?: Translation, - }[]; + constructor( + json: LayerConfigJson, + units?: Unit[], + context?: string, + official: boolean = true + ) { + this.units = units ?? []; + context = context + "." + json.id; + const self = this; + this.id = json.id; + this.name = Translations.T(json.name, context + ".name"); - tagRenderings: TagRenderingConfig []; - - constructor(json: LayerConfigJson, - units?:Unit[], - context?: string, - official: boolean = true,) { - this.units = units ?? []; - context = context + "." + json.id; - const self = this; - this.id = json.id; - this.name = Translations.T(json.name, context + ".name"); - - if(json.description !== undefined){ - if(Object.keys(json.description).length === 0){ - json.description = undefined; - } - } - - this.description =Translations.T(json.description, context + ".description") ; - - let legacy = undefined; - if (json["overpassTags"] !== undefined) { - // @ts-ignore - legacy = FromJSON.Tag(json["overpassTags"], context + ".overpasstags"); - } - if (json.source !== undefined) { - if (legacy !== undefined) { - throw context + "Both the legacy 'layer.overpasstags' and the new 'layer.source'-field are defined" - } - - let osmTags: TagsFilter = legacy; - if (json.source["osmTags"]) { - osmTags = FromJSON.Tag(json.source["osmTags"], context + "source.osmTags"); - } - - if(json.source["geoJsonSource"] !== undefined){ - throw context + "Use 'geoJson' instead of 'geoJsonSource'" - } - - this.source = new SourceConfig({ - osmTags: osmTags, - geojsonSource: json.source["geoJson"], - geojsonSourceLevel: json.source["geoJsonZoomLevel"], - overpassScript: json.source["overpassScript"], - isOsmCache: json.source["isOsmCache"] - }, this.id); - } else { - this.source = new SourceConfig({ - osmTags: legacy - }) - } - - - this.calculatedTags = undefined; - if (json.calculatedTags !== undefined) { - if (!official) { - console.warn(`Unofficial theme ${this.id} with custom javascript! This is a security risk`) - } - this.calculatedTags = []; - for (const kv of json.calculatedTags) { - - const index = kv.indexOf("=") - const key = kv.substring(0, index); - const code = kv.substring(index + 1); - - this.calculatedTags.push([key, code]) - } - } - - this.doNotDownload = json.doNotDownload ?? false; - this.passAllFeatures = json.passAllFeatures ?? false; - this.minzoom = json.minzoom ?? 0; - this.maxzoom = json.maxzoom ?? 1000; - this.wayHandling = json.wayHandling ?? 0; - this.presets = (json.presets ?? []).map((pr, i) => - ({ - title: Translations.T(pr.title, `${context}.presets[${i}].title`), - tags: pr.tags.map(t => FromJSON.SimpleTag(t)), - description: Translations.T(pr.description, `${context}.presets[${i}].description`) - })) - - - /** Given a key, gets the corresponding property from the json (or the default if not found - * - * The found value is interpreted as a tagrendering and fetched/parsed - * */ - function tr(key: string, deflt) { - const v = json[key]; - if (v === undefined || v === null) { - if (deflt === undefined) { - return undefined; - } - return new TagRenderingConfig(deflt, self.source.osmTags, `${context}.${key}.default value`); - } - if (typeof v === "string") { - const shared = SharedTagRenderings.SharedTagRendering.get(v); - if (shared) { - return shared; - } - } - return new TagRenderingConfig(v, self.source.osmTags, `${context}.${key}`); - } - - /** - * Converts a list of tagRenderingCOnfigJSON in to TagRenderingConfig - * A string is interpreted as a name to call - */ - function trs(tagRenderings?: (string | TagRenderingConfigJson)[], readOnly = false) { - if (tagRenderings === undefined) { - return []; - } - - return Utils.NoNull(tagRenderings.map( - (renderingJson, i) => { - if (typeof renderingJson === "string") { - - if (renderingJson === "questions") { - if (readOnly) { - throw `A tagrendering has a question, but asking a question does not make sense here: is it a title icon or a geojson-layer? ${context}. The offending tagrendering is ${JSON.stringify(renderingJson)}` - } - - return new TagRenderingConfig("questions", undefined) - } - - - const shared = SharedTagRenderings.SharedTagRendering.get(renderingJson); - if (shared !== undefined) { - return shared; - } - - const keys = Array.from(SharedTagRenderings.SharedTagRendering.keys()) - - if(Utils.runningFromConsole){ - return undefined; - } - - throw `Predefined tagRendering ${renderingJson} not found in ${context}.\n Try one of ${(keys.join(", "))}\n If you intent to output this text literally, use {\"render\": } instead"}`; - } - return new TagRenderingConfig(renderingJson, self.source.osmTags, `${context}.tagrendering[${i}]`); - })); - } - - this.tagRenderings = trs(json.tagRenderings, false); - - - const titleIcons = []; - const defaultIcons = ["phonelink", "emaillink", "wikipedialink", "osmlink", "sharelink"]; - for (const icon of (json.titleIcons ?? defaultIcons)) { - if (icon === "defaults") { - titleIcons.push(...defaultIcons); - } else { - titleIcons.push(icon); - } - } - - this.titleIcons = trs(titleIcons, true); - - - this.title = tr("title", undefined); - this.icon = tr("icon", ""); - this.iconOverlays = (json.iconOverlays ?? []).map((overlay, i) => { - let tr = new TagRenderingConfig(overlay.then, self.source.osmTags, `iconoverlays.${i}`); - if (typeof overlay.then === "string" && SharedTagRenderings.SharedIcons.get(overlay.then) !== undefined) { - tr = SharedTagRenderings.SharedIcons.get(overlay.then); - } - return { - if: FromJSON.Tag(overlay.if), - then: tr, - badge: overlay.badge ?? false - } - }); - - const iconPath = this.icon.GetRenderValue({id: "node/-1"}).txt; - if (iconPath.startsWith(Utils.assets_path)) { - const iconKey = iconPath.substr(Utils.assets_path.length); - if (Svg.All[iconKey] === undefined) { - throw "Builtin SVG asset not found: " + iconPath - } - } - this.isShown = tr("isShown", "yes"); - this.iconSize = tr("iconSize", "40,40,center"); - this.label = tr("label", "") - this.color = tr("color", "#0000ff"); - this.width = tr("width", "7"); - this.rotation = tr("rotation", "0"); - this.dashArray = tr("dashArray", ""); - - this.deletion = null; - if(json.deletion === true){ - json.deletion = { - } - } - if(json.deletion !== undefined && json.deletion !== false){ - this.deletion = new DeleteConfig(json.deletion, `${context}.deletion`) - } - - - if (json["showIf"] !== undefined) { - throw "Invalid key on layerconfig " + this.id + ": showIf. Did you mean 'isShown' instead?"; - } + if (json.description !== undefined) { + if (Object.keys(json.description).length === 0) { + json.description = undefined; + } } - public CustomCodeSnippets(): string[] { - if (this.calculatedTags === undefined) { - return [] - } + this.description = Translations.T( + json.description, + context + ".description" + ); - return this.calculatedTags.map(code => code[1]); + let legacy = undefined; + if (json["overpassTags"] !== undefined) { + // @ts-ignore + legacy = FromJSON.Tag(json["overpassTags"], context + ".overpasstags"); } + if (json.source !== undefined) { + if (legacy !== undefined) { + throw ( + context + + "Both the legacy 'layer.overpasstags' and the new 'layer.source'-field are defined" + ); + } - public AddRoamingRenderings(addAll: { - tagRenderings: TagRenderingConfig[], - titleIcons: TagRenderingConfig[], - iconOverlays: { "if": TagsFilter, then: TagRenderingConfig, badge: boolean }[] + let osmTags: TagsFilter = legacy; + if (json.source["osmTags"]) { + osmTags = FromJSON.Tag( + json.source["osmTags"], + context + "source.osmTags" + ); + } - }): LayerConfig { + if (json.source["geoJsonSource"] !== undefined) { + throw context + "Use 'geoJson' instead of 'geoJsonSource'"; + } - let insertionPoint = this.tagRenderings.map(tr => tr.IsQuestionBoxElement()).indexOf(true) - if (insertionPoint < 0) { - // No 'questions' defined - we just add them all to the end - insertionPoint = this.tagRenderings.length; - } - this.tagRenderings.splice(insertionPoint, 0, ...addAll.tagRenderings); - - - this.iconOverlays.push(...addAll.iconOverlays); - for (const icon of addAll.titleIcons) { - this.titleIcons.splice(0, 0, icon); - } - return this; - } - - public GetRoamingRenderings(): { - tagRenderings: TagRenderingConfig[], - titleIcons: TagRenderingConfig[], - iconOverlays: { "if": TagsFilter, then: TagRenderingConfig, badge: boolean }[] - - } { - - const tagRenderings = this.tagRenderings.filter(tr => tr.roaming); - const titleIcons = this.titleIcons.filter(tr => tr.roaming); - const iconOverlays = this.iconOverlays.filter(io => io.then.roaming) - - return { - tagRenderings: tagRenderings, - titleIcons: titleIcons, - iconOverlays: iconOverlays - } - - } - - public GenerateLeafletStyle(tags: UIEventSource, clickable: boolean, widthHeight= "100%"): + this.source = new SourceConfig( { - icon: - { - html: BaseUIElement, - iconSize: [number, number], - iconAnchor: [number, number], - popupAnchor: [number, number], - iconUrl: string, - className: string - }, - color: string, - weight: number, - dashArray: number[] - } { + osmTags: osmTags, + geojsonSource: json.source["geoJson"], + geojsonSourceLevel: json.source["geoJsonZoomLevel"], + overpassScript: json.source["overpassScript"], + isOsmCache: json.source["isOsmCache"], + }, + this.id + ); + } else { + this.source = new SourceConfig({ + osmTags: legacy, + }); + } - function num(str, deflt = 40) { - const n = Number(str); - if (isNaN(n)) { - return deflt; - } - return n; + this.calculatedTags = undefined; + if (json.calculatedTags !== undefined) { + if (!official) { + console.warn( + `Unofficial theme ${this.id} with custom javascript! This is a security risk` + ); + } + this.calculatedTags = []; + for (const kv of json.calculatedTags) { + const index = kv.indexOf("="); + const key = kv.substring(0, index); + const code = kv.substring(index + 1); + + this.calculatedTags.push([key, code]); + } + } + + this.doNotDownload = json.doNotDownload ?? false; + this.passAllFeatures = json.passAllFeatures ?? false; + this.minzoom = json.minzoom ?? 0; + this.maxzoom = json.maxzoom ?? 1000; + this.wayHandling = json.wayHandling ?? 0; + this.presets = (json.presets ?? []).map((pr, i) => ({ + title: Translations.T(pr.title, `${context}.presets[${i}].title`), + tags: pr.tags.map((t) => FromJSON.SimpleTag(t)), + description: Translations.T( + pr.description, + `${context}.presets[${i}].description` + ), + })); + + /** Given a key, gets the corresponding property from the json (or the default if not found + * + * The found value is interpreted as a tagrendering and fetched/parsed + * */ + function tr(key: string, deflt) { + const v = json[key]; + if (v === undefined || v === null) { + if (deflt === undefined) { + return undefined; } - - function rendernum(tr: TagRenderingConfig, deflt: number) { - const str = Number(render(tr, "" + deflt)); - const n = Number(str); - if (isNaN(n)) { - return deflt; - } - return n; + return new TagRenderingConfig( + deflt, + self.source.osmTags, + `${context}.${key}.default value` + ); + } + if (typeof v === "string") { + const shared = SharedTagRenderings.SharedTagRendering.get(v); + if (shared) { + return shared; } + } + return new TagRenderingConfig( + v, + self.source.osmTags, + `${context}.${key}` + ); + } - function render(tr: TagRenderingConfig, deflt?: string) { - const str = (tr?.GetRenderValue(tags.data)?.txt ?? deflt); - return Utils.SubstituteKeys(str, tags.data).replace(/{.*}/g, ""); - } + /** + * Converts a list of tagRenderingCOnfigJSON in to TagRenderingConfig + * A string is interpreted as a name to call + */ + function trs( + tagRenderings?: (string | TagRenderingConfigJson)[], + readOnly = false + ) { + if (tagRenderings === undefined) { + return []; + } - const iconSize = render(this.iconSize, "40,40,center").split(","); - const dashArray = render(this.dashArray).split(" ").map(Number); - let color = render(this.color, "#00f"); + return Utils.NoNull( + tagRenderings.map((renderingJson, i) => { + if (typeof renderingJson === "string") { + if (renderingJson === "questions") { + if (readOnly) { + throw `A tagrendering has a question, but asking a question does not make sense here: is it a title icon or a geojson-layer? ${context}. The offending tagrendering is ${JSON.stringify( + renderingJson + )}`; + } - if (color.startsWith("--")) { - color = getComputedStyle(document.body).getPropertyValue("--catch-detail-color") - } - - const weight = rendernum(this.width, 5); - - const iconW = num(iconSize[0]); - let iconH = num(iconSize[1]); - const mode = iconSize[2]?.trim()?.toLowerCase() ?? "center" - - let anchorW = iconW / 2; - let anchorH = iconH / 2; - if (mode === "left") { - anchorW = 0; - } - if (mode === "right") { - anchorW = iconW; - } - - if (mode === "top") { - anchorH = 0; - } - if (mode === "bottom") { - anchorH = iconH; - } - - const iconUrlStatic = render(this.icon); - const self = this; - const mappedHtml = tags.map(tgs => { - function genHtmlFromString(sourcePart: string): BaseUIElement { - - const style = `width:100%;height:100%;transform: rotate( ${rotation} );display:block;position: absolute; top: 0; left: 0`; - let html: BaseUIElement = new FixedUiElement(``); - const match = sourcePart.match(/([a-zA-Z0-9_]*):([^;]*)/) - if (match !== null && Svg.All[match[1] + ".svg"] !== undefined) { - html = new Combine([ - (Svg.All[match[1] + ".svg"] as string) - .replace(/#000000/g, match[2]) - ]).SetStyle(style); - } - return html; + return new TagRenderingConfig("questions", undefined); } - - // What do you mean, 'tgs' is never read? - // It is read implicitly in the 'render' method - const iconUrl = render(self.icon); - const rotation = render(self.rotation, "0deg"); - - let htmlParts: BaseUIElement[] = []; - let sourceParts = Utils.NoNull(iconUrl.split(";").filter(prt => prt != "")); - for (const sourcePart of sourceParts) { - htmlParts.push(genHtmlFromString(sourcePart)) + const shared = + SharedTagRenderings.SharedTagRendering.get(renderingJson); + if (shared !== undefined) { + return shared; } - let badges = []; - for (const iconOverlay of self.iconOverlays) { - if (!iconOverlay.if.matchesProperties(tgs)) { - continue; - } - if (iconOverlay.badge) { - const badgeParts: BaseUIElement[] = []; - const partDefs = iconOverlay.then.GetRenderValue(tgs).txt.split(";").filter(prt => prt != ""); + const keys = Array.from( + SharedTagRenderings.SharedTagRendering.keys() + ); - for (const badgePartStr of partDefs) { - badgeParts.push(genHtmlFromString(badgePartStr)) - } - - const badgeCompound = new Combine(badgeParts) - .SetStyle("display:flex;position:relative;width:100%;height:100%;"); - - badges.push(badgeCompound) - - } else { - htmlParts.push(genHtmlFromString( - iconOverlay.then.GetRenderValue(tgs).txt)); - } + if (Utils.runningFromConsole) { + return undefined; } - if (badges.length > 0) { - const badgesComponent = new Combine(badges) - .SetStyle("display:flex;height:50%;width:100%;position:absolute;top:50%;left:50%;"); - htmlParts.push(badgesComponent) - } - - if (sourceParts.length == 0) { - iconH = 0 - } - try { - - const label = self.label?.GetRenderValue(tgs)?.Subs(tgs) - ?.SetClass("block text-center") - ?.SetStyle("margin-top: " + (iconH + 2) + "px") - if (label !== undefined) { - htmlParts.push(new Combine([label]).SetClass("flex flex-col items-center")) - } - } catch (e) { - console.error(e, tgs) - } - return new Combine(htmlParts); + throw `Predefined tagRendering ${renderingJson} not found in ${context}.\n Try one of ${keys.join( + ", " + )}\n If you intent to output this text literally, use {\"render\": } instead"}`; + } + return new TagRenderingConfig( + renderingJson, + self.source.osmTags, + `${context}.tagrendering[${i}]` + ); }) - - - return { - icon: - { - html: new VariableUiElement(mappedHtml), - iconSize: [iconW, iconH], - iconAnchor: [anchorW, anchorH], - popupAnchor: [0, 3 - anchorH], - iconUrl: iconUrlStatic, - className: clickable ? "leaflet-div-icon" : "leaflet-div-icon unclickable" - }, - color: color, - weight: weight, - dashArray: dashArray - }; + ); } - public ExtractImages(): Set { - const parts: Set[] = [] - parts.push(...this.tagRenderings?.map(tr => tr.ExtractImages(false))) - parts.push(...this.titleIcons?.map(tr => tr.ExtractImages(true))) - parts.push(this.icon?.ExtractImages(true)) - parts.push(...this.iconOverlays?.map(overlay => overlay.then.ExtractImages(true))) - for (const preset of this.presets) { - parts.push(new Set(preset.description?.ExtractImages(false))) - } + this.tagRenderings = trs(json.tagRenderings, false); - const allIcons = new Set(); - for (const part of parts) { - part?.forEach(allIcons.add, allIcons) - } + this.filters = (json.filter ?? []).map((option, i) => { + return new FilterConfig(option, `${context}.filter-[${i}]`) + }); - return allIcons; + const titleIcons = []; + const defaultIcons = [ + "phonelink", + "emaillink", + "wikipedialink", + "osmlink", + "sharelink", + ]; + for (const icon of json.titleIcons ?? defaultIcons) { + if (icon === "defaults") { + titleIcons.push(...defaultIcons); + } else { + titleIcons.push(icon); + } } -} \ No newline at end of file + this.titleIcons = trs(titleIcons, true); + + this.title = tr("title", undefined); + this.icon = tr("icon", ""); + this.iconOverlays = (json.iconOverlays ?? []).map((overlay, i) => { + let tr = new TagRenderingConfig( + overlay.then, + self.source.osmTags, + `iconoverlays.${i}` + ); + if ( + typeof overlay.then === "string" && + SharedTagRenderings.SharedIcons.get(overlay.then) !== undefined + ) { + tr = SharedTagRenderings.SharedIcons.get(overlay.then); + } + return { + if: FromJSON.Tag(overlay.if), + then: tr, + badge: overlay.badge ?? false, + }; + }); + + const iconPath = this.icon.GetRenderValue({ id: "node/-1" }).txt; + if (iconPath.startsWith(Utils.assets_path)) { + const iconKey = iconPath.substr(Utils.assets_path.length); + if (Svg.All[iconKey] === undefined) { + throw "Builtin SVG asset not found: " + iconPath; + } + } + this.isShown = tr("isShown", "yes"); + this.iconSize = tr("iconSize", "40,40,center"); + this.label = tr("label", ""); + this.color = tr("color", "#0000ff"); + this.width = tr("width", "7"); + this.rotation = tr("rotation", "0"); + this.dashArray = tr("dashArray", ""); + + this.deletion = null; + if (json.deletion === true) { + json.deletion = {}; + } + if (json.deletion !== undefined && json.deletion !== false) { + this.deletion = new DeleteConfig(json.deletion, `${context}.deletion`); + } + + if (json["showIf"] !== undefined) { + throw ( + "Invalid key on layerconfig " + + this.id + + ": showIf. Did you mean 'isShown' instead?" + ); + } + } + + public CustomCodeSnippets(): string[] { + if (this.calculatedTags === undefined) { + return []; + } + + return this.calculatedTags.map((code) => code[1]); + } + + public AddRoamingRenderings(addAll: { + tagRenderings: TagRenderingConfig[]; + titleIcons: TagRenderingConfig[]; + iconOverlays: { + if: TagsFilter; + then: TagRenderingConfig; + badge: boolean; + }[]; + }): LayerConfig { + let insertionPoint = this.tagRenderings + .map((tr) => tr.IsQuestionBoxElement()) + .indexOf(true); + if (insertionPoint < 0) { + // No 'questions' defined - we just add them all to the end + insertionPoint = this.tagRenderings.length; + } + this.tagRenderings.splice(insertionPoint, 0, ...addAll.tagRenderings); + + this.iconOverlays.push(...addAll.iconOverlays); + for (const icon of addAll.titleIcons) { + this.titleIcons.splice(0, 0, icon); + } + return this; + } + + public GetRoamingRenderings(): { + tagRenderings: TagRenderingConfig[]; + titleIcons: TagRenderingConfig[]; + iconOverlays: { + if: TagsFilter; + then: TagRenderingConfig; + badge: boolean; + }[]; + } { + const tagRenderings = this.tagRenderings.filter((tr) => tr.roaming); + const titleIcons = this.titleIcons.filter((tr) => tr.roaming); + const iconOverlays = this.iconOverlays.filter((io) => io.then.roaming); + + return { + tagRenderings: tagRenderings, + titleIcons: titleIcons, + iconOverlays: iconOverlays, + }; + } + + public GenerateLeafletStyle( + tags: UIEventSource, + clickable: boolean, + widthHeight = "100%" + ): { + icon: { + html: BaseUIElement; + iconSize: [number, number]; + iconAnchor: [number, number]; + popupAnchor: [number, number]; + iconUrl: string; + className: string; + }; + color: string; + weight: number; + dashArray: number[]; + } { + function num(str, deflt = 40) { + const n = Number(str); + if (isNaN(n)) { + return deflt; + } + return n; + } + + function rendernum(tr: TagRenderingConfig, deflt: number) { + const str = Number(render(tr, "" + deflt)); + const n = Number(str); + if (isNaN(n)) { + return deflt; + } + return n; + } + + function render(tr: TagRenderingConfig, deflt?: string) { + const str = tr?.GetRenderValue(tags.data)?.txt ?? deflt; + return Utils.SubstituteKeys(str, tags.data).replace(/{.*}/g, ""); + } + + const iconSize = render(this.iconSize, "40,40,center").split(","); + const dashArray = render(this.dashArray).split(" ").map(Number); + let color = render(this.color, "#00f"); + + if (color.startsWith("--")) { + color = getComputedStyle(document.body).getPropertyValue( + "--catch-detail-color" + ); + } + + const weight = rendernum(this.width, 5); + + const iconW = num(iconSize[0]); + let iconH = num(iconSize[1]); + const mode = iconSize[2]?.trim()?.toLowerCase() ?? "center"; + + let anchorW = iconW / 2; + let anchorH = iconH / 2; + if (mode === "left") { + anchorW = 0; + } + if (mode === "right") { + anchorW = iconW; + } + + if (mode === "top") { + anchorH = 0; + } + if (mode === "bottom") { + anchorH = iconH; + } + + const iconUrlStatic = render(this.icon); + const self = this; + const mappedHtml = tags.map((tgs) => { + function genHtmlFromString(sourcePart: string): BaseUIElement { + const style = `width:100%;height:100%;transform: rotate( ${rotation} );display:block;position: absolute; top: 0; left: 0`; + let html: BaseUIElement = new FixedUiElement( + `` + ); + const match = sourcePart.match(/([a-zA-Z0-9_]*):([^;]*)/); + if (match !== null && Svg.All[match[1] + ".svg"] !== undefined) { + html = new Combine([ + (Svg.All[match[1] + ".svg"] as string).replace( + /#000000/g, + match[2] + ), + ]).SetStyle(style); + } + return html; + } + + // What do you mean, 'tgs' is never read? + // It is read implicitly in the 'render' method + const iconUrl = render(self.icon); + const rotation = render(self.rotation, "0deg"); + + let htmlParts: BaseUIElement[] = []; + let sourceParts = Utils.NoNull( + iconUrl.split(";").filter((prt) => prt != "") + ); + for (const sourcePart of sourceParts) { + htmlParts.push(genHtmlFromString(sourcePart)); + } + + let badges = []; + for (const iconOverlay of self.iconOverlays) { + if (!iconOverlay.if.matchesProperties(tgs)) { + continue; + } + if (iconOverlay.badge) { + const badgeParts: BaseUIElement[] = []; + const partDefs = iconOverlay.then + .GetRenderValue(tgs) + .txt.split(";") + .filter((prt) => prt != ""); + + for (const badgePartStr of partDefs) { + badgeParts.push(genHtmlFromString(badgePartStr)); + } + + const badgeCompound = new Combine(badgeParts).SetStyle( + "display:flex;position:relative;width:100%;height:100%;" + ); + + badges.push(badgeCompound); + } else { + htmlParts.push( + genHtmlFromString(iconOverlay.then.GetRenderValue(tgs).txt) + ); + } + } + + if (badges.length > 0) { + const badgesComponent = new Combine(badges).SetStyle( + "display:flex;height:50%;width:100%;position:absolute;top:50%;left:50%;" + ); + htmlParts.push(badgesComponent); + } + + if (sourceParts.length == 0) { + iconH = 0; + } + try { + const label = self.label + ?.GetRenderValue(tgs) + ?.Subs(tgs) + ?.SetClass("block text-center") + ?.SetStyle("margin-top: " + (iconH + 2) + "px"); + if (label !== undefined) { + htmlParts.push( + new Combine([label]).SetClass("flex flex-col items-center") + ); + } + } catch (e) { + console.error(e, tgs); + } + return new Combine(htmlParts); + }); + + return { + icon: { + html: new VariableUiElement(mappedHtml), + iconSize: [iconW, iconH], + iconAnchor: [anchorW, anchorH], + popupAnchor: [0, 3 - anchorH], + iconUrl: iconUrlStatic, + className: clickable + ? "leaflet-div-icon" + : "leaflet-div-icon unclickable", + }, + color: color, + weight: weight, + dashArray: dashArray, + }; + } + + public ExtractImages(): Set { + const parts: Set[] = []; + parts.push(...this.tagRenderings?.map((tr) => tr.ExtractImages(false))); + parts.push(...this.titleIcons?.map((tr) => tr.ExtractImages(true))); + parts.push(this.icon?.ExtractImages(true)); + parts.push( + ...this.iconOverlays?.map((overlay) => overlay.then.ExtractImages(true)) + ); + for (const preset of this.presets) { + parts.push(new Set(preset.description?.ExtractImages(false))); + } + + const allIcons = new Set(); + for (const part of parts) { + part?.forEach(allIcons.add, allIcons); + } + + return allIcons; + } +} diff --git a/Customizations/JSON/LayerConfigJson.ts b/Customizations/JSON/LayerConfigJson.ts index d81307fd9f..a3ae740801 100644 --- a/Customizations/JSON/LayerConfigJson.ts +++ b/Customizations/JSON/LayerConfigJson.ts @@ -1,6 +1,7 @@ import {TagRenderingConfigJson} from "./TagRenderingConfigJson"; import {AndOrTagConfigJson} from "./TagConfigJson"; import {DeleteConfigJson} from "./DeleteConfigJson"; +import FilterConfigJson from "./FilterConfigJson"; /** * Configuration for a single layer @@ -233,6 +234,12 @@ export interface LayerConfigJson { */ tagRenderings?: (string | TagRenderingConfigJson) [], + + /** + * All the extra questions for filtering + */ + filter?: (FilterConfigJson) [], + /** * This block defines under what circumstances the delete dialog is shown for objects of this layer. * If set, a dialog is shown to the user to (soft) delete the point. diff --git a/InitUiElements.ts b/InitUiElements.ts index 4ff1031dee..eff22fc2b9 100644 --- a/InitUiElements.ts +++ b/InitUiElements.ts @@ -43,6 +43,7 @@ import LayerConfig from "./Customizations/JSON/LayerConfig"; import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; import { SimpleMapScreenshoter } from "leaflet-simple-map-screenshoter"; import jsPDF from "jspdf"; +import FilterView from "./UI/BigComponents/FilterView"; export class InitUiElements { static InitAll( @@ -207,7 +208,7 @@ export class InitUiElements { Img.AsImageElement(Svg.plus_zoom, "", "width:1.25rem;height:1.25rem") ) ).onClick(() => { - State.state.locationControl.data.zoom++; + State.state.locationControl.data.zoom++; State.state.locationControl.ping(); }); @@ -380,6 +381,7 @@ export class InitUiElements { const layerControlPanel = new LayerControlPanel( State.state.layerControlIsOpened ).SetClass("block p-1 rounded-full"); + const layerControlButton = new Toggle( layerControlPanel, new MapControlButton(Svg.layers_svg()), @@ -392,7 +394,31 @@ export class InitUiElements { State.state.featureSwitchLayers ); - new Combine([copyrightButton, layerControl]).AttachTo("bottom-left"); + const filterView = new FilterView(State.state.FilterIsOpened).SetClass( + "block p-1 rounded-full" + ); + + const filterMapControlButton = new MapControlButton( + new CenterFlexedElement( + Img.AsImageElement(Svg.filter, "", "width:1.25rem;height:1.25rem") + ) + ); + + const filterButton = new Toggle( + filterView, + filterMapControlButton, + State.state.FilterIsOpened + ).ToggleOnClick(); + + const filterControl = new Toggle( + filterButton, + "", + State.state.featureSwitchFilter + ); + + new Combine([copyrightButton, layerControl, filterControl]).AttachTo( + "bottom-left" + ); State.state.locationControl.addCallback(() => { // Close the layer selection when the map is moved diff --git a/State.ts b/State.ts index 8e4322d654..c4169d50ff 100644 --- a/State.ts +++ b/State.ts @@ -1,13 +1,13 @@ -import {Utils} from "./Utils"; -import {ElementStorage} from "./Logic/ElementStorage"; -import {Changes} from "./Logic/Osm/Changes"; -import {OsmConnection} from "./Logic/Osm/OsmConnection"; +import { Utils } from "./Utils"; +import { ElementStorage } from "./Logic/ElementStorage"; +import { Changes } from "./Logic/Osm/Changes"; +import { OsmConnection } from "./Logic/Osm/OsmConnection"; import Locale from "./UI/i18n/Locale"; -import {UIEventSource} from "./Logic/UIEventSource"; -import {LocalStorageSource} from "./Logic/Web/LocalStorageSource"; -import {QueryParameters} from "./Logic/Web/QueryParameters"; +import { UIEventSource } from "./Logic/UIEventSource"; +import { LocalStorageSource } from "./Logic/Web/LocalStorageSource"; +import { QueryParameters } from "./Logic/Web/QueryParameters"; import LayoutConfig from "./Customizations/JSON/LayoutConfig"; -import {MangroveIdentity} from "./Logic/Web/MangroveReviews"; +import { MangroveIdentity } from "./Logic/Web/MangroveReviews"; import InstalledThemes from "./Logic/Actors/InstalledThemes"; import BaseLayer from "./Models/BaseLayer"; import Loc from "./Models/Loc"; @@ -17,7 +17,7 @@ import OverpassFeatureSource from "./Logic/Actors/OverpassFeatureSource"; import LayerConfig from "./Customizations/JSON/LayerConfig"; import TitleHandler from "./Logic/Actors/TitleHandler"; import PendingChangesUploader from "./Logic/Actors/PendingChangesUploader"; -import {Relation} from "./Logic/Osm/ExtractRelations"; +import { Relation } from "./Logic/Osm/ExtractRelations"; import OsmApiFeatureSource from "./Logic/FeatureSource/OsmApiFeatureSource"; /** @@ -25,274 +25,402 @@ import OsmApiFeatureSource from "./Logic/FeatureSource/OsmApiFeatureSource"; */ export default class State { + // The singleton of the global state + public static state: State; - // The singleton of the global state - public static state: State; + public readonly layoutToUse = new UIEventSource(undefined); - - public readonly layoutToUse = new UIEventSource(undefined); - - /** + /** The mapping from id -> UIEventSource */ - public allElements: ElementStorage; - /** + public allElements: ElementStorage; + /** THe change handler */ - public changes: Changes; - /** + public changes: Changes; + /** The leaflet instance of the big basemap */ - public leafletMap = new UIEventSource(undefined); - /** - * Background layer id - */ - public availableBackgroundLayers: UIEventSource; - /** + public leafletMap = new UIEventSource(undefined); + /** + * Background layer id + */ + public availableBackgroundLayers: UIEventSource; + /** The user credentials */ - public osmConnection: OsmConnection; + public osmConnection: OsmConnection; - public mangroveIdentity: MangroveIdentity; + public mangroveIdentity: MangroveIdentity; - public favouriteLayers: UIEventSource; + public favouriteLayers: UIEventSource; - public layerUpdater: OverpassFeatureSource; - - public osmApiFeatureSource : OsmApiFeatureSource ; + public layerUpdater: OverpassFeatureSource; + public osmApiFeatureSource: OsmApiFeatureSource; - public filteredLayers: UIEventSource<{ - readonly isDisplayed: UIEventSource, - readonly layerDef: LayerConfig; - }[]> = new UIEventSource<{ - readonly isDisplayed: UIEventSource, - readonly layerDef: LayerConfig; - }[]>([]) + public filteredLayers: UIEventSource< + { + readonly isDisplayed: UIEventSource; + readonly layerDef: LayerConfig; + }[] + > = new UIEventSource< + { + readonly isDisplayed: UIEventSource; + readonly layerDef: LayerConfig; + }[] + >([]); - - /** + /** The latest element that was selected */ - public readonly selectedElement = new UIEventSource(undefined, "Selected element") + public readonly selectedElement = new UIEventSource( + undefined, + "Selected element" + ); - /** - * Keeps track of relations: which way is part of which other way? - * Set by the overpass-updater; used in the metatagging - */ - public readonly knownRelations = new UIEventSource>(undefined, "Relation memberships") + /** + * Keeps track of relations: which way is part of which other way? + * Set by the overpass-updater; used in the metatagging + */ + public readonly knownRelations = new UIEventSource< + Map + >(undefined, "Relation memberships"); - public readonly featureSwitchUserbadge: UIEventSource; - public readonly featureSwitchSearch: UIEventSource; - public readonly featureSwitchLayers: UIEventSource; - public readonly featureSwitchAddNew: UIEventSource; - public readonly featureSwitchWelcomeMessage: UIEventSource; - public readonly featureSwitchIframe: UIEventSource; - public readonly featureSwitchMoreQuests: UIEventSource; - public readonly featureSwitchShareScreen: UIEventSource; - public readonly featureSwitchGeolocation: UIEventSource; - public readonly featureSwitchIsTesting: UIEventSource; - public readonly featureSwitchIsDebugging: UIEventSource; - public readonly featureSwitchShowAllQuestions: UIEventSource; - public readonly featureSwitchApiURL: UIEventSource; + public readonly featureSwitchUserbadge: UIEventSource; + public readonly featureSwitchSearch: UIEventSource; + public readonly featureSwitchLayers: UIEventSource; + public readonly featureSwitchAddNew: UIEventSource; + public readonly featureSwitchWelcomeMessage: UIEventSource; + public readonly featureSwitchIframe: UIEventSource; + public readonly featureSwitchMoreQuests: UIEventSource; + public readonly featureSwitchShareScreen: UIEventSource; + public readonly featureSwitchGeolocation: UIEventSource; + public readonly featureSwitchIsTesting: UIEventSource; + public readonly featureSwitchIsDebugging: UIEventSource; + public readonly featureSwitchShowAllQuestions: UIEventSource; + public readonly featureSwitchApiURL: UIEventSource; + public readonly featureSwitchFilter: UIEventSource; + /** + * The map location: currently centered lat, lon and zoom + */ + public readonly locationControl = new UIEventSource(undefined); + public backgroundLayer; + public readonly backgroundLayerId: UIEventSource; - /** - * The map location: currently centered lat, lon and zoom - */ - public readonly locationControl = new UIEventSource(undefined); - public backgroundLayer; - public readonly backgroundLayerId: UIEventSource; + /* Last location where a click was registered + */ + public readonly LastClickLocation: UIEventSource<{ + lat: number; + lon: number; + }> = new UIEventSource<{ lat: number; lon: number }>(undefined); - /* Last location where a click was registered - */ - public readonly LastClickLocation: UIEventSource<{ lat: number, lon: number }> = new UIEventSource<{ lat: number, lon: number }>(undefined) + /** + * The location as delivered by the GPS + */ + public currentGPSLocation: UIEventSource<{ + latlng: { lat: number; lng: number }; + accuracy: number; + }> = new UIEventSource<{ + latlng: { lat: number; lng: number }; + accuracy: number; + }>(undefined); + public layoutDefinition: string; + public installedThemes: UIEventSource< + { layout: LayoutConfig; definition: string }[] + >; - /** - * The location as delivered by the GPS - */ - public currentGPSLocation: UIEventSource<{ - latlng: { lat: number, lng: number }, - accuracy: number - }> = new UIEventSource<{ latlng: { lat: number, lng: number }, accuracy: number }>(undefined); - public layoutDefinition: string; - public installedThemes: UIEventSource<{ layout: LayoutConfig; definition: string }[]>; - - public layerControlIsOpened: UIEventSource = - QueryParameters.GetQueryParameter("layer-control-toggle", "false", "Whether or not the layer control is shown") - .map((str) => str !== "false", [], b => "" + b) - - public welcomeMessageOpenedTab = QueryParameters.GetQueryParameter("tab", "0", `The tab that is shown in the welcome-message. 0 = the explanation of the theme,1 = OSM-credits, 2 = sharescreen, 3 = more themes, 4 = about mapcomplete (user must be logged in and have >${Constants.userJourney.mapCompleteHelpUnlock} changesets)`).map( - str => isNaN(Number(str)) ? 0 : Number(str), [], n => "" + n + public layerControlIsOpened: UIEventSource = + QueryParameters.GetQueryParameter( + "layer-control-toggle", + "false", + "Whether or not the layer control is shown" + ).map( + (str) => str !== "false", + [], + (b) => "" + b ); - - constructor(layoutToUse: LayoutConfig) { - const self = this; - this.layoutToUse.setData(layoutToUse); + public FilterIsOpened: UIEventSource = + QueryParameters.GetQueryParameter( + "filter-toggle", + "false", + "Whether or not the filter is shown" + ).map( + (str) => str !== "false", + [], + (b) => "" + b + ); - // -- Location control initialization - { - const zoom = State.asFloat( - QueryParameters.GetQueryParameter("z", "" + (layoutToUse?.startZoom ?? 1), "The initial/current zoom level") - .syncWith(LocalStorageSource.Get("zoom"))); - const lat = State.asFloat(QueryParameters.GetQueryParameter("lat", "" + (layoutToUse?.startLat ?? 0), "The initial/current latitude") - .syncWith(LocalStorageSource.Get("lat"))); - const lon = State.asFloat(QueryParameters.GetQueryParameter("lon", "" + (layoutToUse?.startLon ?? 0), "The initial/current longitude of the app") - .syncWith(LocalStorageSource.Get("lon"))); + public welcomeMessageOpenedTab = QueryParameters.GetQueryParameter( + "tab", + "0", + `The tab that is shown in the welcome-message. 0 = the explanation of the theme,1 = OSM-credits, 2 = sharescreen, 3 = more themes, 4 = about mapcomplete (user must be logged in and have >${Constants.userJourney.mapCompleteHelpUnlock} changesets)` + ).map( + (str) => (isNaN(Number(str)) ? 0 : Number(str)), + [], + (n) => "" + n + ); + constructor(layoutToUse: LayoutConfig) { + const self = this; - this.locationControl = new UIEventSource({ - zoom: Utils.asFloat(zoom.data), - lat: Utils.asFloat(lat.data), - lon: Utils.asFloat(lon.data), - }).addCallback((latlonz) => { - zoom.setData(latlonz.zoom); - lat.setData(latlonz.lat); - lon.setData(latlonz.lon); - }); + this.layoutToUse.setData(layoutToUse); - this.layoutToUse.addCallback(layoutToUse => { - const lcd = self.locationControl.data; - lcd.zoom = lcd.zoom ?? layoutToUse?.startZoom; - lcd.lat = lcd.lat ?? layoutToUse?.startLat; - lcd.lon = lcd.lon ?? layoutToUse?.startLon; - self.locationControl.ping(); - }); + // -- Location control initialization + { + const zoom = State.asFloat( + QueryParameters.GetQueryParameter( + "z", + "" + (layoutToUse?.startZoom ?? 1), + "The initial/current zoom level" + ).syncWith(LocalStorageSource.Get("zoom")) + ); + const lat = State.asFloat( + QueryParameters.GetQueryParameter( + "lat", + "" + (layoutToUse?.startLat ?? 0), + "The initial/current latitude" + ).syncWith(LocalStorageSource.Get("lat")) + ); + const lon = State.asFloat( + QueryParameters.GetQueryParameter( + "lon", + "" + (layoutToUse?.startLon ?? 0), + "The initial/current longitude of the app" + ).syncWith(LocalStorageSource.Get("lon")) + ); - } - - // Helper function to initialize feature switches - function featSw(key: string, deflt: (layout: LayoutConfig) => boolean, documentation: string): UIEventSource { - const queryParameterSource = QueryParameters.GetQueryParameter(key, undefined, documentation); - // I'm so sorry about someone trying to decipher this - - // It takes the current layout, extracts the default value for this query parameter. A query parameter event source is then retrieved and flattened - return UIEventSource.flatten( - self.layoutToUse.map((layout) => { - const defaultValue = deflt(layout); - const queryParam = QueryParameters.GetQueryParameter(key, "" + defaultValue, documentation) - return queryParam.map((str) => str === undefined ? defaultValue : (str !== "false")); - }), [queryParameterSource]); - } - - // Feature switch initialization - not as a function as the UIEventSources are readonly - { - - this.featureSwitchUserbadge = featSw("fs-userbadge", (layoutToUse) => layoutToUse?.enableUserBadge ?? true, - "Disables/Enables the user information pill (userbadge) at the top left. Disabling this disables logging in and thus disables editing all together, effectively putting MapComplete into read-only mode."); - this.featureSwitchSearch = featSw("fs-search", (layoutToUse) => layoutToUse?.enableSearch ?? true, - "Disables/Enables the search bar"); - this.featureSwitchLayers = featSw("fs-layers", (layoutToUse) => layoutToUse?.enableLayers ?? true, - "Disables/Enables the layer control"); - this.featureSwitchAddNew = featSw("fs-add-new", (layoutToUse) => layoutToUse?.enableAddNewPoints ?? true, - "Disables/Enables the 'add new feature'-popup. (A theme without presets might not have it in the first place)"); - this.featureSwitchWelcomeMessage = featSw("fs-welcome-message", () => true, - "Disables/enables the help menu or welcome message"); - this.featureSwitchIframe = featSw("fs-iframe", () => false, - "Disables/Enables the iframe-popup"); - this.featureSwitchMoreQuests = featSw("fs-more-quests", (layoutToUse) => layoutToUse?.enableMoreQuests ?? true, - "Disables/Enables the 'More Quests'-tab in the welcome message"); - this.featureSwitchShareScreen = featSw("fs-share-screen", (layoutToUse) => layoutToUse?.enableShareScreen ?? true, - "Disables/Enables the 'Share-screen'-tab in the welcome message"); - this.featureSwitchGeolocation = featSw("fs-geolocation", (layoutToUse) => layoutToUse?.enableGeolocation ?? true, - "Disables/Enables the geolocation button"); - this.featureSwitchShowAllQuestions = featSw("fs-all-questions", (layoutToUse) => layoutToUse?.enableShowAllQuestions ?? false, - "Always show all questions"); - - this.featureSwitchIsTesting = QueryParameters.GetQueryParameter("test", "false", - "If true, 'dryrun' mode is activated. The app will behave as normal, except that changes to OSM will be printed onto the console instead of actually uploaded to osm.org") - .map(str => str === "true", [], b => "" + b); - - this.featureSwitchIsDebugging = QueryParameters.GetQueryParameter("debug","false", - "If true, shows some extra debugging help such as all the available tags on every object") - .map(str => str === "true", [], b => "" + b) - - this.featureSwitchApiURL = QueryParameters.GetQueryParameter("backend","osm", - "The OSM backend to use - can be used to redirect mapcomplete to the testing backend when using 'osm-test'") - - } - { - // Some other feature switches - const customCssQP = QueryParameters.GetQueryParameter("custom-css", "", "If specified, the custom css from the given link will be loaded additionaly"); - if (customCssQP.data !== undefined && customCssQP.data !== "") { - Utils.LoadCustomCss(customCssQP.data); - } - - - this.backgroundLayerId = QueryParameters.GetQueryParameter("background", - layoutToUse?.defaultBackgroundId ?? "osm", - "The id of the background layer to start with") - - } - - - if(Utils.runningFromConsole){ - return; - } - - this.osmConnection = new OsmConnection( - this.featureSwitchIsTesting.data, - QueryParameters.GetQueryParameter("oauth_token", undefined, - "Used to complete the login"), - layoutToUse?.id, - true, - // @ts-ignore - this.featureSwitchApiURL.data - ); - - - this.allElements = new ElementStorage(); - this.changes = new Changes(); - this.osmApiFeatureSource = new OsmApiFeatureSource() - - new PendingChangesUploader(this.changes, this.selectedElement); - - this.mangroveIdentity = new MangroveIdentity( - this.osmConnection.GetLongPreference("identity", "mangrove") - ); - - - this.installedThemes = new InstalledThemes(this.osmConnection).installedThemes; - - // Important: the favourite layers are initialized _after_ the installed themes, as these might contain an installedTheme - this.favouriteLayers = LocalStorageSource.Get("favouriteLayers") - .syncWith(this.osmConnection.GetLongPreference("favouriteLayers")) - .map( - str => Utils.Dedup(str?.split(";")) ?? [], - [], layers => Utils.Dedup(layers)?.join(";") - ); - - Locale.language.syncWith(this.osmConnection.GetPreference("language")); - - - Locale.language.addCallback((currentLanguage) => { - const layoutToUse = self.layoutToUse.data; - if (layoutToUse === undefined) { - return; - } - if (this.layoutToUse.data.language.indexOf(currentLanguage) < 0) { - console.log("Resetting language to", layoutToUse.language[0], "as", currentLanguage, " is unsupported") - // The current language is not supported -> switch to a supported one - Locale.language.setData(layoutToUse.language[0]); - } - }).ping() - - new TitleHandler(this.layoutToUse, this.selectedElement, this.allElements); + this.locationControl = new UIEventSource({ + zoom: Utils.asFloat(zoom.data), + lat: Utils.asFloat(lat.data), + lon: Utils.asFloat(lon.data), + }).addCallback((latlonz) => { + zoom.setData(latlonz.zoom); + lat.setData(latlonz.lat); + lon.setData(latlonz.lon); + }); + this.layoutToUse.addCallback((layoutToUse) => { + const lcd = self.locationControl.data; + lcd.zoom = lcd.zoom ?? layoutToUse?.startZoom; + lcd.lat = lcd.lat ?? layoutToUse?.startLat; + lcd.lon = lcd.lon ?? layoutToUse?.startLon; + self.locationControl.ping(); + }); } - private static asFloat(source: UIEventSource): UIEventSource { - return source.map(str => { - let parsed = parseFloat(str); - return isNaN(parsed) ? undefined : parsed; - }, [], fl => { - if (fl === undefined || isNaN(fl)) { - return undefined; - } - return ("" + fl).substr(0, 8); - }) + // Helper function to initialize feature switches + function featSw( + key: string, + deflt: (layout: LayoutConfig) => boolean, + documentation: string + ): UIEventSource { + const queryParameterSource = QueryParameters.GetQueryParameter( + key, + undefined, + documentation + ); + // I'm so sorry about someone trying to decipher this + + // It takes the current layout, extracts the default value for this query parameter. A query parameter event source is then retrieved and flattened + return UIEventSource.flatten( + self.layoutToUse.map((layout) => { + const defaultValue = deflt(layout); + const queryParam = QueryParameters.GetQueryParameter( + key, + "" + defaultValue, + documentation + ); + return queryParam.map((str) => + str === undefined ? defaultValue : str !== "false" + ); + }), + [queryParameterSource] + ); } + // Feature switch initialization - not as a function as the UIEventSources are readonly + { + this.featureSwitchUserbadge = featSw( + "fs-userbadge", + (layoutToUse) => layoutToUse?.enableUserBadge ?? true, + "Disables/Enables the user information pill (userbadge) at the top left. Disabling this disables logging in and thus disables editing all together, effectively putting MapComplete into read-only mode." + ); + this.featureSwitchSearch = featSw( + "fs-search", + (layoutToUse) => layoutToUse?.enableSearch ?? true, + "Disables/Enables the search bar" + ); + this.featureSwitchLayers = featSw( + "fs-layers", + (layoutToUse) => layoutToUse?.enableLayers ?? true, + "Disables/Enables the layer control" + ); + this.featureSwitchFilter = featSw( + "fs-filter", + (layoutToUse) => layoutToUse?.enableLayers ?? true, + "Disables/Enables the filter" + ); + this.featureSwitchAddNew = featSw( + "fs-add-new", + (layoutToUse) => layoutToUse?.enableAddNewPoints ?? true, + "Disables/Enables the 'add new feature'-popup. (A theme without presets might not have it in the first place)" + ); + this.featureSwitchWelcomeMessage = featSw( + "fs-welcome-message", + () => true, + "Disables/enables the help menu or welcome message" + ); + this.featureSwitchIframe = featSw( + "fs-iframe", + () => false, + "Disables/Enables the iframe-popup" + ); + this.featureSwitchMoreQuests = featSw( + "fs-more-quests", + (layoutToUse) => layoutToUse?.enableMoreQuests ?? true, + "Disables/Enables the 'More Quests'-tab in the welcome message" + ); + this.featureSwitchShareScreen = featSw( + "fs-share-screen", + (layoutToUse) => layoutToUse?.enableShareScreen ?? true, + "Disables/Enables the 'Share-screen'-tab in the welcome message" + ); + this.featureSwitchGeolocation = featSw( + "fs-geolocation", + (layoutToUse) => layoutToUse?.enableGeolocation ?? true, + "Disables/Enables the geolocation button" + ); + this.featureSwitchShowAllQuestions = featSw( + "fs-all-questions", + (layoutToUse) => layoutToUse?.enableShowAllQuestions ?? false, + "Always show all questions" + ); + this.featureSwitchIsTesting = QueryParameters.GetQueryParameter( + "test", + "false", + "If true, 'dryrun' mode is activated. The app will behave as normal, except that changes to OSM will be printed onto the console instead of actually uploaded to osm.org" + ).map( + (str) => str === "true", + [], + (b) => "" + b + ); + + this.featureSwitchIsDebugging = QueryParameters.GetQueryParameter( + "debug", + "false", + "If true, shows some extra debugging help such as all the available tags on every object" + ).map( + (str) => str === "true", + [], + (b) => "" + b + ); + + this.featureSwitchApiURL = QueryParameters.GetQueryParameter( + "backend", + "osm", + "The OSM backend to use - can be used to redirect mapcomplete to the testing backend when using 'osm-test'" + ); + } + { + // Some other feature switches + const customCssQP = QueryParameters.GetQueryParameter( + "custom-css", + "", + "If specified, the custom css from the given link will be loaded additionaly" + ); + if (customCssQP.data !== undefined && customCssQP.data !== "") { + Utils.LoadCustomCss(customCssQP.data); + } + + this.backgroundLayerId = QueryParameters.GetQueryParameter( + "background", + layoutToUse?.defaultBackgroundId ?? "osm", + "The id of the background layer to start with" + ); + } + + if (Utils.runningFromConsole) { + return; + } + + this.osmConnection = new OsmConnection( + this.featureSwitchIsTesting.data, + QueryParameters.GetQueryParameter( + "oauth_token", + undefined, + "Used to complete the login" + ), + layoutToUse?.id, + true, + // @ts-ignore + this.featureSwitchApiURL.data + ); + + this.allElements = new ElementStorage(); + this.changes = new Changes(); + this.osmApiFeatureSource = new OsmApiFeatureSource(); + + new PendingChangesUploader(this.changes, this.selectedElement); + + this.mangroveIdentity = new MangroveIdentity( + this.osmConnection.GetLongPreference("identity", "mangrove") + ); + + this.installedThemes = new InstalledThemes( + this.osmConnection + ).installedThemes; + + // Important: the favourite layers are initialized _after_ the installed themes, as these might contain an installedTheme + this.favouriteLayers = LocalStorageSource.Get("favouriteLayers") + .syncWith(this.osmConnection.GetLongPreference("favouriteLayers")) + .map( + (str) => Utils.Dedup(str?.split(";")) ?? [], + [], + (layers) => Utils.Dedup(layers)?.join(";") + ); + + Locale.language.syncWith(this.osmConnection.GetPreference("language")); + + Locale.language + .addCallback((currentLanguage) => { + const layoutToUse = self.layoutToUse.data; + if (layoutToUse === undefined) { + return; + } + if (this.layoutToUse.data.language.indexOf(currentLanguage) < 0) { + console.log( + "Resetting language to", + layoutToUse.language[0], + "as", + currentLanguage, + " is unsupported" + ); + // The current language is not supported -> switch to a supported one + Locale.language.setData(layoutToUse.language[0]); + } + }) + .ping(); + + new TitleHandler(this.layoutToUse, this.selectedElement, this.allElements); + } + + private static asFloat(source: UIEventSource): UIEventSource { + return source.map( + (str) => { + let parsed = parseFloat(str); + return isNaN(parsed) ? undefined : parsed; + }, + [], + (fl) => { + if (fl === undefined || isNaN(fl)) { + return undefined; + } + return ("" + fl).substr(0, 8); + } + ); + } } diff --git a/Svg.ts b/Svg.ts index 0ebc3f924e..ab5ef6569a 100644 --- a/Svg.ts +++ b/Svg.ts @@ -29,6 +29,11 @@ export default class Svg { public static arrow_left_smooth_svg() { return new Img(Svg.arrow_left_smooth, true);} public static arrow_left_smooth_ui() { return new FixedUiElement(Svg.arrow_left_smooth_img);} + public static arrow_left_thin = " " + public static arrow_left_thin_img = Img.AsImageElement(Svg.arrow_left_thin) + public static arrow_left_thin_svg() { return new Img(Svg.arrow_left_thin, true);} + public static arrow_left_thin_ui() { return new FixedUiElement(Svg.arrow_left_thin_img);} + public static arrow_right_smooth = " image/svg+xml " public static arrow_right_smooth_img = Img.AsImageElement(Svg.arrow_right_smooth) public static arrow_right_smooth_svg() { return new Img(Svg.arrow_right_smooth, true);} @@ -49,6 +54,16 @@ export default class Svg { public static camera_plus_svg() { return new Img(Svg.camera_plus, true);} public static camera_plus_ui() { return new FixedUiElement(Svg.camera_plus_img);} + public static checkbox_empty = " " + public static checkbox_empty_img = Img.AsImageElement(Svg.checkbox_empty) + public static checkbox_empty_svg() { return new Img(Svg.checkbox_empty, true);} + public static checkbox_empty_ui() { return new FixedUiElement(Svg.checkbox_empty_img);} + + public static checkbox_filled = " " + public static checkbox_filled_img = Img.AsImageElement(Svg.checkbox_filled) + public static checkbox_filled_svg() { return new Img(Svg.checkbox_filled, true);} + public static checkbox_filled_ui() { return new FixedUiElement(Svg.checkbox_filled_img);} + public static checkmark = "" public static checkmark_img = Img.AsImageElement(Svg.checkmark) public static checkmark_svg() { return new Img(Svg.checkmark, true);} @@ -134,6 +149,11 @@ export default class Svg { public static envelope_svg() { return new Img(Svg.envelope, true);} public static envelope_ui() { return new FixedUiElement(Svg.envelope_img);} + public static filter = " " + public static filter_img = Img.AsImageElement(Svg.filter) + public static filter_svg() { return new Img(Svg.filter, true);} + public static filter_ui() { return new FixedUiElement(Svg.filter_img);} + public static floppy = " " public static floppy_img = Img.AsImageElement(Svg.floppy) public static floppy_svg() { return new Img(Svg.floppy, true);} @@ -349,4 +369,4 @@ export default class Svg { public static wikipedia_svg() { return new Img(Svg.wikipedia, true);} public static wikipedia_ui() { return new FixedUiElement(Svg.wikipedia_img);} -public static All = {"SocialImageForeground.svg": Svg.SocialImageForeground,"add.svg": Svg.add,"addSmall.svg": Svg.addSmall,"ampersand.svg": Svg.ampersand,"arrow-left-smooth.svg": Svg.arrow_left_smooth,"arrow-right-smooth.svg": Svg.arrow_right_smooth,"back.svg": Svg.back,"bug.svg": Svg.bug,"camera-plus.svg": Svg.camera_plus,"checkmark.svg": Svg.checkmark,"circle.svg": Svg.circle,"clock.svg": Svg.clock,"close.svg": Svg.close,"compass.svg": Svg.compass,"cross_bottom_right.svg": Svg.cross_bottom_right,"crosshair-blue-center.svg": Svg.crosshair_blue_center,"crosshair-blue.svg": Svg.crosshair_blue,"crosshair.svg": Svg.crosshair,"delete_icon.svg": Svg.delete_icon,"direction.svg": Svg.direction,"direction_gradient.svg": Svg.direction_gradient,"direction_masked.svg": Svg.direction_masked,"direction_outline.svg": Svg.direction_outline,"direction_stroke.svg": Svg.direction_stroke,"down.svg": Svg.down,"envelope.svg": Svg.envelope,"floppy.svg": Svg.floppy,"gear.svg": Svg.gear,"help.svg": Svg.help,"home.svg": Svg.home,"home_white_bg.svg": Svg.home_white_bg,"josm_logo.svg": Svg.josm_logo,"layers.svg": Svg.layers,"layersAdd.svg": Svg.layersAdd,"location.svg": Svg.location,"logo.svg": Svg.logo,"logout.svg": Svg.logout,"mapcomplete_logo.svg": Svg.mapcomplete_logo,"mapillary.svg": Svg.mapillary,"mapillary_black.svg": Svg.mapillary_black,"min-zoom.svg": Svg.min_zoom,"min.svg": Svg.min,"no_checkmark.svg": Svg.no_checkmark,"or.svg": Svg.or,"osm-copyright.svg": Svg.osm_copyright,"osm-logo-us.svg": Svg.osm_logo_us,"osm-logo.svg": Svg.osm_logo,"pencil.svg": Svg.pencil,"phone.svg": Svg.phone,"pin.svg": Svg.pin,"plus-zoom.svg": Svg.plus_zoom,"plus.svg": Svg.plus,"pop-out.svg": Svg.pop_out,"reload.svg": Svg.reload,"ring.svg": Svg.ring,"search.svg": Svg.search,"send_email.svg": Svg.send_email,"share.svg": Svg.share,"square.svg": Svg.square,"star.svg": Svg.star,"star_half.svg": Svg.star_half,"star_outline.svg": Svg.star_outline,"star_outline_half.svg": Svg.star_outline_half,"statistics.svg": Svg.statistics,"translate.svg": Svg.translate,"up.svg": Svg.up,"wikidata.svg": Svg.wikidata,"wikimedia-commons-white.svg": Svg.wikimedia_commons_white,"wikipedia.svg": Svg.wikipedia};} +public static All = {"SocialImageForeground.svg": Svg.SocialImageForeground,"add.svg": Svg.add,"addSmall.svg": Svg.addSmall,"ampersand.svg": Svg.ampersand,"arrow-left-smooth.svg": Svg.arrow_left_smooth,"arrow-left-thin.svg": Svg.arrow_left_thin,"arrow-right-smooth.svg": Svg.arrow_right_smooth,"back.svg": Svg.back,"bug.svg": Svg.bug,"camera-plus.svg": Svg.camera_plus,"checkbox-empty.svg": Svg.checkbox_empty,"checkbox-filled.svg": Svg.checkbox_filled,"checkmark.svg": Svg.checkmark,"circle.svg": Svg.circle,"clock.svg": Svg.clock,"close.svg": Svg.close,"compass.svg": Svg.compass,"cross_bottom_right.svg": Svg.cross_bottom_right,"crosshair-blue-center.svg": Svg.crosshair_blue_center,"crosshair-blue.svg": Svg.crosshair_blue,"crosshair.svg": Svg.crosshair,"delete_icon.svg": Svg.delete_icon,"direction.svg": Svg.direction,"direction_gradient.svg": Svg.direction_gradient,"direction_masked.svg": Svg.direction_masked,"direction_outline.svg": Svg.direction_outline,"direction_stroke.svg": Svg.direction_stroke,"down.svg": Svg.down,"envelope.svg": Svg.envelope,"filter.svg": Svg.filter,"floppy.svg": Svg.floppy,"gear.svg": Svg.gear,"help.svg": Svg.help,"home.svg": Svg.home,"home_white_bg.svg": Svg.home_white_bg,"josm_logo.svg": Svg.josm_logo,"layers.svg": Svg.layers,"layersAdd.svg": Svg.layersAdd,"location.svg": Svg.location,"logo.svg": Svg.logo,"logout.svg": Svg.logout,"mapcomplete_logo.svg": Svg.mapcomplete_logo,"mapillary.svg": Svg.mapillary,"mapillary_black.svg": Svg.mapillary_black,"min-zoom.svg": Svg.min_zoom,"min.svg": Svg.min,"no_checkmark.svg": Svg.no_checkmark,"or.svg": Svg.or,"osm-copyright.svg": Svg.osm_copyright,"osm-logo-us.svg": Svg.osm_logo_us,"osm-logo.svg": Svg.osm_logo,"pencil.svg": Svg.pencil,"phone.svg": Svg.phone,"pin.svg": Svg.pin,"plus-zoom.svg": Svg.plus_zoom,"plus.svg": Svg.plus,"pop-out.svg": Svg.pop_out,"reload.svg": Svg.reload,"ring.svg": Svg.ring,"search.svg": Svg.search,"send_email.svg": Svg.send_email,"share.svg": Svg.share,"square.svg": Svg.square,"star.svg": Svg.star,"star_half.svg": Svg.star_half,"star_outline.svg": Svg.star_outline,"star_outline_half.svg": Svg.star_outline_half,"statistics.svg": Svg.statistics,"translate.svg": Svg.translate,"up.svg": Svg.up,"wikidata.svg": Svg.wikidata,"wikimedia-commons-white.svg": Svg.wikimedia_commons_white,"wikipedia.svg": Svg.wikipedia};} diff --git a/UI/BigComponents/FilterView.ts b/UI/BigComponents/FilterView.ts new file mode 100644 index 0000000000..9b5b79386c --- /dev/null +++ b/UI/BigComponents/FilterView.ts @@ -0,0 +1,89 @@ +import { FixedUiElement } from "./../Base/FixedUiElement"; +import { LayerConfigJson } from "./../../Customizations/JSON/LayerConfigJson"; +import { UIEventSource } from "../../Logic/UIEventSource"; +import { VariableUiElement } from "../Base/VariableUIElement"; +import State from "../../State"; +import Toggle from "../Input/Toggle"; +import Combine from "../Base/Combine"; +import Translations from "../i18n/Translations"; +import LayerConfig from "../../Customizations/JSON/LayerConfig"; +import BaseUIElement from "../BaseUIElement"; +import { Translation } from "../i18n/Translation"; +import ScrollableFullScreen from "../Base/ScrollableFullScreen"; +import Svg from "../../Svg"; + +/** + * Shows the filter + */ +export default class FilterView extends ScrollableFullScreen { + constructor(isShown: UIEventSource) { + super(FilterView.GenTitle, FilterView.Generatecontent, "filter", isShown); + } + + private static GenTitle(): BaseUIElement { + return new FixedUiElement(`Filter`).SetClass( + "text-2xl break-words font-bold p-2" + ); + } + + private static Generatecontent(): BaseUIElement { + let filterPanel: BaseUIElement = new FixedUiElement(""); + + if (State.state.filteredLayers.data.length > 1) { + let activeLayers = State.state.filteredLayers; + + if (activeLayers === undefined) { + throw "ActiveLayers should be defined..."; + } + + const checkboxes: BaseUIElement[] = []; + + for (const layer of activeLayers.data) { + const iconStyle = "width:1.5rem;height:1.5rem;margin-left:1.25rem"; + + const icon = new Combine([Svg.checkbox_filled]).SetStyle(iconStyle); + const iconUnselected = new Combine([Svg.checkbox_empty]).SetStyle( + iconStyle + ); + + if (layer.layerDef.name === undefined) { + continue; + } + + const style = "display:flex;align-items:center;color:#007759"; + + const name: Translation = Translations.WT(layer.layerDef.name)?.Clone(); + + const styledNameChecked = name + .Clone() + .SetStyle("font-size:large;padding-left:1.25rem"); + + const styledNameUnChecked = name + .Clone() + .SetStyle("font-size:large;padding-left:1.25rem"); + + const layerChecked = new Combine([icon, styledNameChecked]).SetStyle( + style + ); + + const layerNotChecked = new Combine([ + iconUnselected, + styledNameUnChecked, + ]).SetStyle(style); + + checkboxes.push( + new Toggle(layerChecked, layerNotChecked, layer.isDisplayed) + .ToggleOnClick() + .SetStyle("margin:0.3em;") + ); + } + + let combinedCheckboxes = new Combine(checkboxes); + combinedCheckboxes.SetStyle("display:flex;flex-direction:column;"); + + filterPanel = new Combine([combinedCheckboxes]); + + return filterPanel; + } + } +} diff --git a/assets/.DS_Store b/assets/.DS_Store new file mode 100644 index 0000000000..046c7db674 Binary files /dev/null and b/assets/.DS_Store differ diff --git a/assets/layers/bench/bench.json b/assets/layers/bench/bench.json index fca4ca579e..f55874b0ff 100644 --- a/assets/layers/bench/bench.json +++ b/assets/layers/bench/bench.json @@ -520,11 +520,7 @@ ], "hideUnderlayingFeaturesMinPercentage": 0, "icon": { - "render": "./assets/themes/benches/bench_poi.svg", - "mappings": [] - }, - "width": { - "render": "8" + "render": "circle:#FE6F32;./assets/layers/bench/bench.svg" }, "iconSize": { "render": "35,35,center" diff --git a/assets/layers/bench/bench.svg b/assets/layers/bench/bench.svg new file mode 100644 index 0000000000..dc4fc07882 --- /dev/null +++ b/assets/layers/bench/bench.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/layers/bench/license_info.json b/assets/layers/bench/license_info.json new file mode 100644 index 0000000000..2e404ab20d --- /dev/null +++ b/assets/layers/bench/license_info.json @@ -0,0 +1,8 @@ +[ + { + "authors": [], + "path": "bench.svg", + "license": "CC0", + "sources": [] + } +] \ No newline at end of file diff --git a/assets/layers/birdhide/birdhide.json b/assets/layers/birdhide/birdhide.json index 71a80d88d1..08ff0f22f5 100644 --- a/assets/layers/birdhide/birdhide.json +++ b/assets/layers/birdhide/birdhide.json @@ -241,5 +241,5 @@ } } ], - "wayHandling": 2 + "wayHandling": 1 } \ No newline at end of file diff --git a/assets/layers/information_board/information_board.json b/assets/layers/information_board/information_board.json index 017f3aeb63..db4b5dfcd8 100644 --- a/assets/layers/information_board/information_board.json +++ b/assets/layers/information_board/information_board.json @@ -33,9 +33,6 @@ "icon": { "render": "./assets/layers/information_board/board.svg" }, - "width": { - "render": "8" - }, "iconSize": { "render": "40,40,center" }, diff --git a/assets/layers/parking/parking.json b/assets/layers/parking/parking.json index a8ad2767d5..56da2650a2 100644 --- a/assets/layers/parking/parking.json +++ b/assets/layers/parking/parking.json @@ -1,7 +1,7 @@ { "id": "parking", "name": { - "nl": "parking" + "nl": "Parking" }, "minzoom": 12, "source": { @@ -10,7 +10,8 @@ { "or": [ "amenity=parking", - "amenity=motorcycle_parking" + "amenity=motorcycle_parking", + "amenity=bicycle_parking" ] } ] @@ -22,23 +23,21 @@ }, "mappings": [ { - "if": { - "and": [ - "name:nl~*" - ] - }, + "if": "amenity=parking", "then": { - "nl": "{name:nl}" + "nl": "Auto Parking" } }, { - "if": { - "and": [ - "name~*" - ] - }, + "if": "amenity=motorcycle_parking", "then": { - "nl": "{name}" + "nl": "Motorfiets Parking" + } + }, + { + "if": "amenity=bicycle_parking", + "then": { + "nl": "Fietsenstalling" } } ] @@ -167,13 +166,9 @@ ] } ], - "hideUnderlayingFeaturesMinPercentage": 10, - "wayHandling": 2, - "width": { - "render": "5" - }, + "wayHandling": 1, "iconSize": { - "render": "50,50,center" + "render": "36,36,center" }, "color": { "render": "#E1AD01" @@ -183,6 +178,7 @@ "tags": [ "amenity=parking", "amenity=motorcycle_parking", + "amenity=bicycle_parking", "fixme=Toegevoegd met MapComplete, geometry nog uit te tekenen" ], "title": { diff --git a/assets/layers/picnic_table/picnic_table.json b/assets/layers/picnic_table/picnic_table.json index 672e41f07f..ac46da5363 100644 --- a/assets/layers/picnic_table/picnic_table.json +++ b/assets/layers/picnic_table/picnic_table.json @@ -72,13 +72,9 @@ ] } ], - "hideUnderlayingFeaturesMinPercentage": 0, "icon": { "render": "circle:#e6cf39;./assets/layers/picnic_table/picnic_table.svg" }, - "width": { - "render": "8" - }, "iconSize": { "render": "35,35,center" }, diff --git a/assets/layers/toilet/toilet.json b/assets/layers/toilet/toilet.json index b7a44457e2..cbdd19d286 100644 --- a/assets/layers/toilet/toilet.json +++ b/assets/layers/toilet/toilet.json @@ -38,7 +38,7 @@ "color": { "render": "#0000ff" }, - "wayHandling": 2, + "wayHandling": 1, "presets": [ { "title": { diff --git a/assets/layers/trail/trail.json b/assets/layers/trail/trail.json index d1236bc896..5a031f02e5 100644 --- a/assets/layers/trail/trail.json +++ b/assets/layers/trail/trail.json @@ -393,7 +393,7 @@ }, "wayHandling": 2, "width": { - "render": "5" + "render": "3" }, "iconSize": { "render": "35,35,center" @@ -401,6 +401,9 @@ "color": { "render": "#335D9F" }, + "dashArray": { + "render": "5 5" + }, "presets": [ { "tags": [ diff --git a/assets/layers/visitor_information_centre/visitor_information_centre.json b/assets/layers/visitor_information_centre/visitor_information_centre.json index 07f28942c0..10c3cd0de1 100644 --- a/assets/layers/visitor_information_centre/visitor_information_centre.json +++ b/assets/layers/visitor_information_centre/visitor_information_centre.json @@ -51,13 +51,9 @@ "nl": "Een bezoekerscentrum biedt informatie over een specifieke attractie of bezienswaardigheid waar het is gevestigd." }, "tagRenderings": [], - "hideUnderlayingFeaturesMinPercentage": 0, "icon": { "render": "./assets/layers/visitor_information_centre/information.svg" }, - "width": { - "render": "8" - }, "iconSize": { "render": "40,40,center" }, diff --git a/assets/layers/watermill/license_info.json b/assets/layers/watermill/license_info.json new file mode 100644 index 0000000000..44bc785297 --- /dev/null +++ b/assets/layers/watermill/license_info.json @@ -0,0 +1,8 @@ +[ + { + "authors": [], + "path": "watermill.svg", + "license": "CC0", + "sources": [] + } +] \ No newline at end of file diff --git a/assets/layers/watermill/watermill.json b/assets/layers/watermill/watermill.json index 5789202d5e..1ff9b488df 100644 --- a/assets/layers/watermill/watermill.json +++ b/assets/layers/watermill/watermill.json @@ -1,7 +1,7 @@ { "id": "watermill", "name": { - "nl": "watermolens" + "nl": "Watermolens" }, "minzoom": 12, "source": { @@ -160,9 +160,9 @@ } ], "hideUnderlayingFeaturesMinPercentage": 10, - "wayHandling": 2, - "width": { - "render": "10" + "wayHandling": 1, + "icon": { + "render": "./assets/layers/watermill/watermill.svg" }, "iconSize": { "render": "50,50,center" diff --git a/assets/layers/watermill/watermill.svg b/assets/layers/watermill/watermill.svg new file mode 100644 index 0000000000..05528d3c4d --- /dev/null +++ b/assets/layers/watermill/watermill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/svg/arrow-left-thin.svg b/assets/svg/arrow-left-thin.svg new file mode 100644 index 0000000000..e4329b6151 --- /dev/null +++ b/assets/svg/arrow-left-thin.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/checkbox-empty.svg b/assets/svg/checkbox-empty.svg new file mode 100644 index 0000000000..e4a9dc8663 --- /dev/null +++ b/assets/svg/checkbox-empty.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/checkbox-filled.svg b/assets/svg/checkbox-filled.svg new file mode 100644 index 0000000000..166f917855 --- /dev/null +++ b/assets/svg/checkbox-filled.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/svg/filter.svg b/assets/svg/filter.svg new file mode 100644 index 0000000000..a6783b2484 --- /dev/null +++ b/assets/svg/filter.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/license_info.json b/assets/svg/license_info.json index 82c1793bab..ccdac7acf7 100644 --- a/assets/svg/license_info.json +++ b/assets/svg/license_info.json @@ -1,36 +1,50 @@ [ { - "authors": ["Pieter Vander Vennet"], + "authors": [ + "Pieter Vander Vennet" + ], "path": "direction_masked.svg", "license": "CC0", "sources": [] }, { - "authors": ["Pieter Vander Vennet"], + "authors": [ + "Pieter Vander Vennet" + ], "path": "direction_outline.svg", "license": "CC0", "sources": [] }, { - "authors": ["Pieter Vander Vennet"], + "authors": [ + "Pieter Vander Vennet" + ], "path": "direction_stroke.svg", "license": "CC0", "sources": [] }, { - "authors": ["Pieter Vander Vennet"], + "authors": [ + "Pieter Vander Vennet" + ], "path": "SocialImageForeground.svg", "license": "CC-BY-SA", - "sources": ["https://mapcomplete.osm.be"] + "sources": [ + "https://mapcomplete.osm.be" + ] }, { - "authors": ["Pieter Vander Vennet"], + "authors": [ + "Pieter Vander Vennet" + ], "path": "add.svg", "license": "CC0", "sources": [] }, { - "authors": ["Pieter Vander Vennet"], + "authors": [ + "Pieter Vander Vennet" + ], "path": "addSmall.svg", "license": "CC0", "sources": [] @@ -42,25 +56,33 @@ "sources": [] }, { - "authors": ["Pieter Vander Vennet"], + "authors": [ + "Pieter Vander Vennet" + ], "path": "arrow-left-smooth.svg", "license": "CC0", "sources": [] }, { - "authors": ["Pieter Vander Vennet"], + "authors": [ + "Pieter Vander Vennet" + ], "path": "arrow-right-smooth.svg", "license": "CC0", "sources": [] }, { - "authors": ["Pieter Vander Vennet"], + "authors": [ + "Pieter Vander Vennet" + ], "path": "back.svg", "license": "CC0", "sources": [] }, { - "authors": ["Github"], + "authors": [ + "Github" + ], "path": "bug.svg", "license": "MIT", "sources": [ @@ -71,26 +93,35 @@ { "path": "camera-plus.svg", "license": "CC-BY-SA 3.0", - "authors": ["Dave Gandy", "Pieter Vander Vennet"], + "authors": [ + "Dave Gandy", + "Pieter Vander Vennet" + ], "sources": [ "https://fontawesome.com/", "https://commons.wikimedia.org/wiki/File:Camera_font_awesome.svg" ] }, { - "authors": ["Pieter Vander Vennet"], + "authors": [ + "Pieter Vander Vennet" + ], "path": "checkmark.svg", "license": "CC0", "sources": [] }, { - "authors": ["Pieter Vander Vennet"], + "authors": [ + "Pieter Vander Vennet" + ], "path": "circle.svg", "license": "CC0", "sources": [] }, { - "authors": ["Pieter Vander Vennet"], + "authors": [ + "Pieter Vander Vennet" + ], "path": "clock.svg", "license": "CC0", "sources": [] @@ -132,7 +163,9 @@ "sources": [] }, { - "authors": ["Dave Gandy"], + "authors": [ + "Dave Gandy" + ], "path": "delete_icon.svg", "license": "CC-BY-SA", "sources": [ @@ -164,7 +197,9 @@ "sources": [] }, { - "authors": ["The Tango Desktop Project"], + "authors": [ + "The Tango Desktop Project" + ], "path": "floppy.svg", "license": "CC0", "sources": [ @@ -185,19 +220,29 @@ "sources": [] }, { - "authors": ["Timothy Miller"], + "authors": [ + "Timothy Miller" + ], "path": "home.svg", "license": "CC-BY-SA 3.0", - "sources": ["https://commons.wikimedia.org/wiki/File:Home-icon.svg"] + "sources": [ + "https://commons.wikimedia.org/wiki/File:Home-icon.svg" + ] }, { - "authors": ["Timothy Miller"], + "authors": [ + "Timothy Miller" + ], "path": "home_white_bg.svg", "license": "CC-BY-SA 3.0", - "sources": ["https://commons.wikimedia.org/wiki/File:Home-icon.svg"] + "sources": [ + "https://commons.wikimedia.org/wiki/File:Home-icon.svg" + ] }, { - "authors": ["JOSM Team"], + "authors": [ + "JOSM Team" + ], "path": "josm_logo.svg", "license": "CC0", "sources": [ @@ -220,7 +265,9 @@ { "path": "Ornament-Horiz-0.svg", "license": "CC-BY", - "authors": ["Nightwolfdezines"], + "authors": [ + "Nightwolfdezines" + ], "sources": [ "https://www.vecteezy.com/vector-art/226361-ornaments-and-flourishes" ] @@ -228,7 +275,9 @@ { "path": "Ornament-Horiz-1.svg", "license": "CC-BY", - "authors": ["Nightwolfdezines"], + "authors": [ + "Nightwolfdezines" + ], "sources": [ "https://www.vecteezy.com/vector-art/226361-ornaments-and-flourishes" ] @@ -236,7 +285,9 @@ { "path": "Ornament-Horiz-2.svg", "license": "CC-BY", - "authors": ["Nightwolfdezines"], + "authors": [ + "Nightwolfdezines" + ], "sources": [ "https://www.vecteezy.com/vector-art/226361-ornaments-and-flourishes" ] @@ -244,7 +295,9 @@ { "path": "Ornament-Horiz-3.svg", "license": "CC-BY", - "authors": ["Nightwolfdezines"], + "authors": [ + "Nightwolfdezines" + ], "sources": [ "https://www.vecteezy.com/vector-art/226361-ornaments-and-flourishes" ] @@ -252,7 +305,9 @@ { "path": "Ornament-Horiz-4.svg", "license": "CC-BY", - "authors": ["Nightwolfdezines"], + "authors": [ + "Nightwolfdezines" + ], "sources": [ "https://www.vecteezy.com/vector-art/226361-ornaments-and-flourishes" ] @@ -260,7 +315,9 @@ { "path": "Ornament-Horiz-5.svg", "license": "CC-BY", - "authors": ["Nightwolfdezines"], + "authors": [ + "Nightwolfdezines" + ], "sources": [ "https://www.vecteezy.com/vector-art/226361-ornaments-and-flourishes" ] @@ -268,7 +325,9 @@ { "path": "Ornament-Horiz-6.svg", "license": "CC-BY", - "authors": ["Nightwolfdezines"], + "authors": [ + "Nightwolfdezines" + ], "sources": [ "https://www.vecteezy.com/vector-art/226361-ornaments-and-flourishes" ] @@ -310,16 +369,25 @@ "sources": [] }, { - "authors": ["Pieter Vander Vennet", " OSM"], + "authors": [ + "Pieter Vander Vennet", + " OSM" + ], "path": "mapcomplete_logo.svg", "license": "Logo; CC-BY-SA", - "sources": ["https://mapcomplete.osm.be"] + "sources": [ + "https://mapcomplete.osm.be" + ] }, { - "authors": ["Mapillary"], + "authors": [ + "Mapillary" + ], "path": "mapillary.svg", "license": "Logo; All rights reserved", - "sources": ["https://mapillary.com/"] + "sources": [ + "https://mapillary.com/" + ] }, { "authors": [], @@ -343,22 +411,32 @@ "authors": [], "path": "osm-copyright.svg", "license": "logo; all rights reserved", - "sources": ["https://www.OpenStreetMap.org"] + "sources": [ + "https://www.OpenStreetMap.org" + ] }, { - "authors": ["OpenStreetMap U.S. Chapter"], + "authors": [ + "OpenStreetMap U.S. Chapter" + ], "path": "osm-logo-us.svg", "license": "Logo", - "sources": ["https://www.openstreetmap.us/"] + "sources": [ + "https://www.openstreetmap.us/" + ] }, { "authors": [], "path": "osm-logo.svg", "license": "logo; all rights reserved", - "sources": ["https://www.OpenStreetMap.org"] + "sources": [ + "https://www.OpenStreetMap.org" + ] }, { - "authors": ["GitHub Octicons"], + "authors": [ + "GitHub Octicons" + ], "path": "pencil.svg", "license": "MIT", "sources": [ @@ -367,10 +445,14 @@ ] }, { - "authors": ["@ tyskrat"], + "authors": [ + "@ tyskrat" + ], "path": "phone.svg", "license": "CC-BY 3.0", - "sources": ["https://www.onlinewebfonts.com/icon/1059"] + "sources": [ + "https://www.onlinewebfonts.com/icon/1059" + ] }, { "authors": [], @@ -385,10 +467,14 @@ "sources": [] }, { - "authors": ["@fatih"], + "authors": [ + "@fatih" + ], "path": "pop-out.svg", "license": "CC-BY 3.0", - "sources": ["https://www.onlinewebfonts.com/icon/2151"] + "sources": [ + "https://www.onlinewebfonts.com/icon/2151" + ] }, { "authors": [], @@ -403,7 +489,9 @@ "sources": [] }, { - "authors": ["OOjs UI Team and other contributors"], + "authors": [ + "OOjs UI Team and other contributors" + ], "path": "search.svg", "license": "MIT", "sources": [ @@ -430,13 +518,19 @@ "sources": [] }, { - "authors": ["@felpgrc"], + "authors": [ + "@felpgrc" + ], "path": "statistics.svg", "license": "CC-BY 3.0", - "sources": ["https://www.onlinewebfonts.com/icon/197818"] + "sources": [ + "https://www.onlinewebfonts.com/icon/197818" + ] }, { - "authors": ["MGalloway (WMF)"], + "authors": [ + "MGalloway (WMF)" + ], "path": "translate.svg", "license": "CC-BY-SA 3.0", "sources": [ @@ -450,45 +544,99 @@ "sources": [] }, { - "authors": ["Wikidata"], + "authors": [ + "Wikidata" + ], "path": "wikidata.svg", "license": "Logo; All rights reserved", - "sources": ["https://www.wikidata.org"] + "sources": [ + "https://www.wikidata.org" + ] }, { - "authors": ["Wikimedia"], + "authors": [ + "Wikimedia" + ], "path": "wikimedia-commons-white.svg", "license": "Logo; All rights reserved", - "sources": ["https://commons.wikimedia.org"] + "sources": [ + "https://commons.wikimedia.org" + ] }, { - "authors": ["Wikipedia"], + "authors": [ + "Wikipedia" + ], "path": "wikipedia.svg", "license": "Logo; All rights reserved", - "sources": ["https://www.wikipedia.org/"] + "sources": [ + "https://www.wikipedia.org/" + ] }, { - "authors": ["Mapillary"], + "authors": [ + "Mapillary" + ], "path": "mapillary_black.svg", "license": "Logo; All rights reserved", - "sources": ["https://www.mapillary.com/"] + "sources": [ + "https://www.mapillary.com/" + ] }, { - "authors": ["Hannah Declerck"], + "authors": [ + "Hannah Declerck" + ], "path": "location.svg", "license": "CC0", "sources": [] }, { - "authors": ["Hannah Declerck"], + "authors": [ + "Hannah Declerck" + ], "path": "min-zoom.svg", "license": "CC0", "sources": [] }, { - "authors": ["Hannah Declerck"], + "authors": [ + "Hannah Declerck" + ], "path": "plus-zoom.svg", "license": "CC0", "sources": [] + }, + { + "authors": [ + "Hannah Declerck" + ], + "path": "filter.svg", + "license": "CC0", + "sources": [] + }, + { + "authors": [ + "Hannah Declerck" + ], + "path": "checkbox-empty.svg", + "license": "CC0", + "sources": [] + }, + { + "authors": [ + "Hannah Declerck" + ], + "path": "checkbox-filled.svg", + "license": "CC0", + "sources": [] + }, + { + "authors": [ + "Hannah Declerck" + ], + "path": "arrow-left-thin.svg", + "license": "CC0", + "sources": [] } -] +] \ No newline at end of file diff --git a/assets/themes/.DS_Store b/assets/themes/.DS_Store new file mode 100644 index 0000000000..7d02d7d4e6 Binary files /dev/null and b/assets/themes/.DS_Store differ diff --git a/assets/themes/natuurpunt/bench.svg b/assets/themes/natuurpunt/bench.svg new file mode 100644 index 0000000000..ddab90ca99 --- /dev/null +++ b/assets/themes/natuurpunt/bench.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/themes/natuurpunt/birdhide.svg b/assets/themes/natuurpunt/birdhide.svg new file mode 100644 index 0000000000..730e3f712f --- /dev/null +++ b/assets/themes/natuurpunt/birdhide.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/themes/natuurpunt/drips.svg b/assets/themes/natuurpunt/drips.svg new file mode 100644 index 0000000000..94517d71eb --- /dev/null +++ b/assets/themes/natuurpunt/drips.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/themes/natuurpunt/information.svg b/assets/themes/natuurpunt/information.svg new file mode 100644 index 0000000000..248cbccb52 --- /dev/null +++ b/assets/themes/natuurpunt/information.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/themes/natuurpunt/information_board.svg b/assets/themes/natuurpunt/information_board.svg new file mode 100644 index 0000000000..42d2b07459 --- /dev/null +++ b/assets/themes/natuurpunt/information_board.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/themes/natuurpunt/license_info.json b/assets/themes/natuurpunt/license_info.json index 29b7ace4ba..c192defd61 100644 --- a/assets/themes/natuurpunt/license_info.json +++ b/assets/themes/natuurpunt/license_info.json @@ -8,5 +8,113 @@ "sources": [ "https://www.natuurpunt.be/" ] + }, + { + "authors": [], + "path": "bench.svg", + "license": "CC0", + "sources": [] + }, + { + "authors": [], + "path": "watermill.svg", + "license": "CC0", + "sources": [] + }, + { + "authors": [], + "path": "information.svg", + "license": "CC0", + "sources": [] + }, + { + "authors": [], + "path": "trail.svg", + "license": "CC0", + "sources": [] + }, + { + "authors": [], + "path": "toilets.svg", + "license": "CC0", + "sources": [] + }, + { + "authors": [], + "path": "urinal.svg", + "license": "CC0", + "sources": [] + }, + { + "authors": [], + "path": "wheelchair.svg", + "license": "CC0", + "sources": [] + }, + { + "authors": [], + "path": "walk_wheelchair.svg", + "license": "CC0", + "sources": [] + }, + { + "authors": [], + "path": "information_board.svg", + "license": "CC0", + "sources": [] + }, + { + "authors": [], + "path": "pushchair.svg", + "license": "CC0", + "sources": [] + }, + { + "authors": [], + "path": "picnic_table.svg", + "license": "CC0", + "sources": [] + }, + { + "authors": [], + "path": "parking.svg", + "license": "CC0", + "sources": [] + }, + { + "authors": [], + "path": "parkingbike.svg", + "license": "CC0", + "sources": [] + }, + { + "authors": [], + "path": "parkingmotor.svg", + "license": "CC0", + "sources": [] + }, + { + "authors": [], + "path": "parkingwheels.svg", + "license": "CC0", + "sources": [] + }, + { + "authors": [], + "path": "nature_reserve.svg", + "license": "CC0", + "sources": [] + }, + { + "authors": [], + "path": "drips.svg", + "license": "CC0", + "sources": [] + }, + { + "authors": [], + "path": "birdhide.svg", + "license": "CC0", + "sources": [] } ] \ No newline at end of file diff --git a/assets/themes/natuurpunt/nature_reserve.svg b/assets/themes/natuurpunt/nature_reserve.svg new file mode 100644 index 0000000000..e1085da76c --- /dev/null +++ b/assets/themes/natuurpunt/nature_reserve.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/themes/natuurpunt/natuurpunt.json b/assets/themes/natuurpunt/natuurpunt.json index f14c42a8cf..9d43a5d1a3 100644 --- a/assets/themes/natuurpunt/natuurpunt.json +++ b/assets/themes/natuurpunt/natuurpunt.json @@ -21,14 +21,34 @@ "version": "0", "startLat": 51.20875, "startLon": 3.22435, - "startZoom": 12, + "startZoom": 15, "widenFactor": 0.05, "socialImage": "", + "defaultBackgroundId": "CartoDB.Positron", "layers": [ { "builtin": [ - "nature_reserve", - "trail", + "nature_reserve" + ], + "override": { + "source": { + "osmTags": { + "+and": [ + "operator~.*[nN]atuurpunt.*" + ] + }, + "geoJson": "https://pietervdvn.github.io/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", + "geoJsonZoomLevel": 12, + "isOsmCache": true + }, + "minzoom": "10", + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/nature_reserve.svg" + } + } + }, + { + "builtin": [ "visitor_information_centre" ], "override": { @@ -39,31 +59,195 @@ ] }, "geoJson": "https://pietervdvn.github.io/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", - "geoJsonZoomLevel": 8, + "geoJsonZoomLevel": 12, "isOsmCache": true + }, + "minzoom": "10", + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/information.svg" } } }, { "builtin": [ - "birdhide", - "toilet", - "drinking_water", - "picnic_table" + "trail" ], "override": { - "minzoom": "14" + "source": { + "osmTags": { + "+and": [ + "operator~.*[nN]atuurpunt.*" + ] + }, + "geoJson": "https://pietervdvn.github.io/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", + "geoJsonZoomLevel": 12, + "isOsmCache": true + }, + "minzoom": "13", + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/trail.svg", + "mappings": [ + { + "if": "wheelchair=yes", + "then": "circle:#FE6F32;./assets/themes/natuurpunt/walk_wheelchair.svg" + }, + { + "if": "pushchair=yes", + "then": "circle:#FE6F32;./assets/themes/natuurpunt/pushchair.svg" + } + ] + } + } + }, + { + "builtin": [ + "toilet" + ], + "override": { + "minzoom": "15", + "source": { + "geoJson": "https://pietervdvn.github.io/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", + "geoJsonZoomLevel": 12, + "isOsmCache": true + }, + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/toilets.svg", + "mappings": [ + { + "if": "wheelchair=yes", + "then": "circle:#FE6F32;./assets/themes/natuurpunt/wheelchair.svg" + }, + { + "if": "toilets:position=urinals", + "then": "circle:#FE6F32;./assets/themes/natuurpunt/urinal.svg" + } + ] + } + } + }, + { + "builtin": [ + "birdhide" + ], + "override": { + "minzoom": "15", + "source": { + "geoJson": "https://pietervdvn.github.io/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", + "geoJsonZoomLevel": 12, + "isOsmCache": true + }, + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/birdhide.svg" + } + } + }, + { + "builtin": [ + "picnic_table" + ], + "override": { + "minzoom": "16", + "source": { + "geoJson": "https://pietervdvn.github.io/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", + "geoJsonZoomLevel": 12, + "isOsmCache": true + }, + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/picnic_table.svg" + } + } + }, + { + "builtin": [ + "drinking_water" + ], + "override": { + "minzoom": "16", + "source": { + "geoJson": "https://pietervdvn.github.io/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", + "geoJsonZoomLevel": 12, + "isOsmCache": true + }, + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/drips.svg" + } + } + }, + { + "builtin": [ + "parking" + ], + "override": { + "minzoom": "16", + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/parking.svg", + "mappings": [ + { + "if": "amenity=bicycle_parking", + "then": "circle:#FE6F32;./assets/themes/natuurpunt/parkingbike.svg" + } + ] + }, + "iconOverlays": [ + { + "if": "amenity=motorcycle_parking", + "then": "circle:#335D9F;./assets/themes/natuurpunt/parkingmotor.svg", + "badge": true + }, + { + "if": "capacity:disabled=yes", + "then": "circle:#335D9F;./assets/themes/natuurpunt/parkingwheels.svg", + "badge": true + } + ] } }, { "builtin": [ - "bench", - "watermill", - "parking", "information_board" ], "override": { - "minzoom": "17" + "minzoom": "16", + "source": { + "geoJson": "https://pietervdvn.github.io/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", + "geoJsonZoomLevel": 12, + "isOsmCache": true + }, + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/information_board.svg" + } + } + }, + { + "builtin": [ + "bench" + ], + "override": { + "minzoom": "18", + "source": { + "geoJson": "https://pietervdvn.github.io/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", + "geoJsonZoomLevel": 12, + "isOsmCache": true + }, + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/bench.svg" + } + } + }, + { + "builtin": [ + "watermill" + ], + "override": { + "minzoom": "18", + "source": { + "geoJson": "https://pietervdvn.github.io/natuurpunt_cache/natuurpunt_{layer}_{z}_{x}_{y}.geojson", + "geoJsonZoomLevel": 12, + "isOsmCache": true + }, + "icon": { + "render": "circle:#FE6F32;./assets/themes/natuurpunt/watermill.svg" + } } } ], diff --git a/assets/themes/natuurpunt/parking.svg b/assets/themes/natuurpunt/parking.svg new file mode 100644 index 0000000000..fceb3f23f7 --- /dev/null +++ b/assets/themes/natuurpunt/parking.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/themes/natuurpunt/parkingbike.svg b/assets/themes/natuurpunt/parkingbike.svg new file mode 100644 index 0000000000..e22f683e53 --- /dev/null +++ b/assets/themes/natuurpunt/parkingbike.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/themes/natuurpunt/parkingmotor.svg b/assets/themes/natuurpunt/parkingmotor.svg new file mode 100644 index 0000000000..0fc18d9a68 --- /dev/null +++ b/assets/themes/natuurpunt/parkingmotor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/themes/natuurpunt/parkingwheels.svg b/assets/themes/natuurpunt/parkingwheels.svg new file mode 100644 index 0000000000..3e08f733bb --- /dev/null +++ b/assets/themes/natuurpunt/parkingwheels.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/themes/natuurpunt/picnic_table.svg b/assets/themes/natuurpunt/picnic_table.svg new file mode 100644 index 0000000000..6629c237ef --- /dev/null +++ b/assets/themes/natuurpunt/picnic_table.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/themes/natuurpunt/pushchair.svg b/assets/themes/natuurpunt/pushchair.svg new file mode 100644 index 0000000000..725e1dee32 --- /dev/null +++ b/assets/themes/natuurpunt/pushchair.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/themes/natuurpunt/toilets.svg b/assets/themes/natuurpunt/toilets.svg new file mode 100644 index 0000000000..006dcceca5 --- /dev/null +++ b/assets/themes/natuurpunt/toilets.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/themes/natuurpunt/trail.svg b/assets/themes/natuurpunt/trail.svg new file mode 100644 index 0000000000..3839ed493b --- /dev/null +++ b/assets/themes/natuurpunt/trail.svg @@ -0,0 +1 @@ + diff --git a/assets/themes/natuurpunt/urinal.svg b/assets/themes/natuurpunt/urinal.svg new file mode 100644 index 0000000000..90038efb6a --- /dev/null +++ b/assets/themes/natuurpunt/urinal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/themes/natuurpunt/walk_wheelchair.svg b/assets/themes/natuurpunt/walk_wheelchair.svg new file mode 100644 index 0000000000..6f2e46fe9c --- /dev/null +++ b/assets/themes/natuurpunt/walk_wheelchair.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/themes/natuurpunt/watermill.svg b/assets/themes/natuurpunt/watermill.svg new file mode 100644 index 0000000000..1c62cd1473 --- /dev/null +++ b/assets/themes/natuurpunt/watermill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/themes/natuurpunt/wheelchair.svg b/assets/themes/natuurpunt/wheelchair.svg new file mode 100644 index 0000000000..9a7124a876 --- /dev/null +++ b/assets/themes/natuurpunt/wheelchair.svg @@ -0,0 +1 @@ + \ No newline at end of file