diff --git a/Customizations/AllKnownLayouts.ts b/Customizations/AllKnownLayouts.ts index cc4f2fb71..b5165c4ef 100644 --- a/Customizations/AllKnownLayouts.ts +++ b/Customizations/AllKnownLayouts.ts @@ -1,22 +1,32 @@ -import {Groen} from "./Layouts/Groen"; -import {Toilets} from "./Layouts/Toilets"; -import {GRB} from "./Layouts/GRB"; -import {Statues} from "./Layouts/Statues"; -import {Bookcases} from "./Layouts/Bookcases"; +import { Groen } from "./Layouts/Groen"; +import { Toilets } from "./Layouts/Toilets"; +import { GRB } from "./Layouts/GRB"; +import { Statues } from "./Layouts/Statues"; +import { Bookcases } from "./Layouts/Bookcases"; import Cyclofix from "./Layouts/Cyclofix"; -import {All} from "./Layouts/All"; -import {Layout} from "./Layout"; +import { WalkByBrussels } from "./Layouts/WalkByBrussels"; +import { All } from "./Layouts/All"; +import { Layout } from "./Layout"; +import {MetaMap} from "./Layouts/MetaMap"; +import {Widths} from "./Layers/Widths"; +import {StreetWidth} from "./Layouts/StreetWidth"; +import {NatureReserves} from "./Layers/NatureReserves"; +import {Natuurpunt} from "./Layouts/Natuurpunt"; export class AllKnownLayouts { public static allSets = AllKnownLayouts.AllLayouts(); private static AllLayouts(): Map { const all = new All(); - const layouts : Layout[] = [ + const layouts: Layout[] = [ new Groen(), new GRB(), new Cyclofix(), new Bookcases(), + new WalkByBrussels(), + new MetaMap(), + new StreetWidth(), + new Natuurpunt(), all /*new Toilets(), new Statues(), @@ -29,4 +39,13 @@ export class AllKnownLayouts { } return allSets; } + + public static GetSets(layoutNames): any { + const all = new All(); + for (const name of layoutNames) { + all.layers = all.layers.concat(AllKnownLayouts.allSets[name].layers); + } + + return all; + } } diff --git a/Customizations/LayerDefinition.ts b/Customizations/LayerDefinition.ts index 14f9b401b..1065f43fb 100644 --- a/Customizations/LayerDefinition.ts +++ b/Customizations/LayerDefinition.ts @@ -56,7 +56,16 @@ export class LayerDefinition { */ elementsToShow: TagDependantUIElementConstructor[]; - style: (tags: any) => { color: string, icon: any }; + /** + * A simple styling for the geojson element + * color is the color for areas and ways + * icon is the Leaflet icon + * Note that this is passed entirely to leaflet, so other leaflet attributes work too + */ + style: (tags: any) => { + color: string, + icon: any, + }; /** * If an object of the next layer is contained for this many percent in this feature, it is eaten and not shown @@ -64,6 +73,36 @@ export class LayerDefinition { maxAllowedOverlapPercentage: number = undefined; + constructor(options: { + name: string, + newElementTags: Tag[], + icon: string, + minzoom: number, + overpassFilter: TagsFilter, + title?: TagRenderingOptions, + elementsToShow?: TagDependantUIElementConstructor[], + maxAllowedOverlapPercentage?: number, + style?: (tags: any) => { + color: string, + icon: any + } + } = undefined) { + if (options === undefined) { + console.log("No options!") + return; + } + this.name = options.name; + this.maxAllowedOverlapPercentage = options.maxAllowedOverlapPercentage ?? 0; + this.newElementTags = options.newElementTags; + this.icon = options.icon; + this.minzoom = options.minzoom; + this.overpassFilter = options.overpassFilter; + this.title = options.title; + this.elementsToShow = options.elementsToShow; + this.style = options.style; + console.log(this) + } + asLayer(basemap: Basemap, allElements: ElementStorage, changes: Changes, userDetails: UIEventSource, selectedElement: UIEventSource, showOnPopup: (tags: UIEventSource<(any)>) => UIElement): FilteredLayer { diff --git a/Customizations/Layers/BikeParkings.ts b/Customizations/Layers/BikeParkings.ts index 141ba2946..4b707291a 100644 --- a/Customizations/Layers/BikeParkings.ts +++ b/Customizations/Layers/BikeParkings.ts @@ -39,7 +39,8 @@ export default class BikeParkings extends LayerDefinition { color: "#00bb00", icon: L.icon({ iconUrl: self.icon, - iconSize: [50, 50] + iconSize: [50, 50], + iconAnchor: [25,50] }) }; }; diff --git a/Customizations/Layers/BikeShop.ts b/Customizations/Layers/BikeShop.ts new file mode 100644 index 000000000..3e4413ad6 --- /dev/null +++ b/Customizations/Layers/BikeShop.ts @@ -0,0 +1,112 @@ +import {TagRenderingOptions} from "../TagRendering"; +import {LayerDefinition} from "../LayerDefinition"; +import {Tag} from "../../Logic/TagsFilter"; +import L from "leaflet"; +import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload"; +import {NameQuestion} from "../Questions/NameQuestion"; + +export class BikeShop extends LayerDefinition { + + + private readonly sellsBikes = new Tag("service:bicycle:retail", "yes"); + private readonly repairsBikes = new Tag("service:bicycle:repair", "yes"); + + constructor() { + super( + { + name: "bike shop or repair", + icon: "assets/bike/repair_shop.svg", + minzoom: 14, + overpassFilter: new Tag("shop", "bicycle"), + newElementTags: [new Tag("shop", "bicycle")] + } + ); + + this.title = new TagRenderingOptions({ + mappings: [ + {k: this.sellsBikes, txt: "Bicycle shop"}, + {k: new Tag("service:bicycle:retail", "no"), txt: "Bicycle repair"}, + {k: new Tag("service:bicycle:retail", ""), txt: "Bicycle repair/shop"}, + ] + }) + + + this.elementsToShow = [ + new ImageCarouselWithUploadConstructor(), + new TagRenderingOptions({ + question: "What is the name of this bicycle shop?", + freeform: { + key: "name", + renderTemplate: "The name of this bicycle shop is {name}", + template: "The name of this bicycle shop is $$$" + } + }), + + new TagRenderingOptions({ + question: "Can one buy a bike here?", + mappings: [ + {k: this.sellsBikes, txt: "Bikes are sold here"}, + {k: new Tag("service:bicycle:retail", "no"), txt: "No bikes are sold here"}, + ] + }), + + new TagRenderingOptions({ + question: "Can one buy a new bike here?", + mappings: [ + {k: new Tag("service:bicycle:second_hand", "yes"), txt: "Second-hand bikes are sold here"}, + {k: new Tag("service:bicycle:second_hand", "only"), txt: "All bicycles sold here are second-hand"}, + {k: new Tag("service:bicycle:second_hand", "no"), txt: "Only brand new bikes are sold here"}, + ] + }).OnlyShowIf(this.sellsBikes), + + + new TagRenderingOptions({ + question: "Does this shop repair bicycles?", + mappings: [ + {k: this.repairsBikes, txt: "Bikes are repaired here, by the shop owner (for a fee)"}, + {k: new Tag("service:bicycle:repair", "only_sold"), txt: "Only bikes that were bought here, are repaired"}, + {k: new Tag("service:bicycle:repair", "brand"), txt: "Only bikes of a fixed brand are repaired here"}, + {k: new Tag("service:bicycle:repair", "no"), txt: "Bikes are not repaired here"}, + ] + }), + + new TagRenderingOptions({ + question: "Can one hire a new bike here?", + mappings: [ + {k: new Tag("service:bicycle:rental", "yes"), txt: "Bikes can be rented here"}, + {k: new Tag("service:bicycle:rental", "no"), txt: "Bikes cannot be rented here"}, + ] + }).OnlyShowIf(this.sellsBikes), + + new TagRenderingOptions({ + question: "Are there tools here so that one can repair their own bike?", + mappings: [ + {k: new Tag("service:bicycle:diy", "yes"), txt: "Tools for DIY are available here"}, + {k: new Tag("service:bicycle:diy", "no"), txt: "No tools for DIY are available here"}, + ] + }), + ] + + + this.style = (tags) => { + let icon = "assets/bike/repair_shop.svg"; + + if (this.sellsBikes.matchesProperties(tags)) { + icon = "assets/bike/shop.svg"; + } + + return { + color: "#ff0000", + icon: L.icon({ + iconUrl: icon, + iconSize: [50, 50], + iconAnchor: [25, 50] + }) + } + } + + + } + + +} \ No newline at end of file diff --git a/Customizations/Layers/BikeStations.ts b/Customizations/Layers/BikeStations.ts index 65b43f03f..ac3210cb5 100644 --- a/Customizations/Layers/BikeStations.ts +++ b/Customizations/Layers/BikeStations.ts @@ -18,7 +18,7 @@ import Translations from "../../UI/i18n/Translations"; export default class BikeStations extends LayerDefinition { private readonly pump = new Tag("service:bicycle:pump", "yes"); private readonly pumpOperationalAny = new Tag("service:bicycle:pump:operational_status", "yes"); - private readonly pumpOperationalOk = new Or([new Tag("service:bicycle:pump:operational_status", "yes"), new Tag("service:bicycle:pump:operational_status", "operational"), new Tag("service:bicycle:pump:operational_status", "ok")]); + private readonly pumpOperationalOk = new Or([new Tag("service:bicycle:pump:operational_status", "yes"), new Tag("service:bicycle:pump:operational_status", "operational"), new Tag("service:bicycle:pump:operational_status", "ok"), new Tag("service:bicycle:pump:operational_status", "")]); private readonly tools = new Tag("service:bicycle:tools", "yes"); constructor() { @@ -32,7 +32,6 @@ export default class BikeStations extends LayerDefinition { this.newElementTags = [ new Tag("amenity", "bicycle_repair_station") - // new Tag("fixme", "Toegevoegd met MapComplete, geometry nog uit te tekenen") ]; this.maxAllowedOverlapPercentage = 10; @@ -61,7 +60,7 @@ export default class BikeStations extends LayerDefinition { const self = this; return function (properties: any) { const hasPump = self.pump.matchesProperties(properties) - const isOperational = !self.pumpOperationalAny.matchesProperties(properties) || self.pumpOperationalOk.matchesProperties(properties) + const isOperational = self.pumpOperationalOk.matchesProperties(properties) const hasTools = self.tools.matchesProperties(properties) let iconName = "" if (hasPump) { @@ -75,14 +74,19 @@ export default class BikeStations extends LayerDefinition { } } } else { - iconName = "repair_station.svg" + if (!self.pump.matchesProperties(properties)) { + iconName = "repair_station.svg" + } else { + iconName = "repair_station.svg" + } } const iconUrl = `./assets/bike/${iconName}` return { color: "#00bb00", icon: L.icon({ iconUrl: iconUrl, - iconSize: [50, 50] + iconSize: [50, 50], + iconAnchor: [25,50] }) }; }; diff --git a/Customizations/Layers/Birdhide.ts b/Customizations/Layers/Birdhide.ts new file mode 100644 index 000000000..52ce7743a --- /dev/null +++ b/Customizations/Layers/Birdhide.ts @@ -0,0 +1,146 @@ +import {LayerDefinition} from "../LayerDefinition"; +import {And, Or, Tag} from "../../Logic/TagsFilter"; +import {TagRenderingOptions} from "../TagRendering"; +import FixedText from "../Questions/FixedText"; +import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload"; +import L from "leaflet"; + +export class Birdhide extends LayerDefinition { + + private static readonly birdhide = new Tag("leisure", "bird_hide"); + + + constructor() { + super({ + name: "vogelkijkplaats", + overpassFilter: Birdhide.birdhide, + elementsToShow: [new FixedText("hi")], + icon: "assets/nature/birdhide.svg", + minzoom: 12, + newElementTags: [Birdhide.birdhide], + style(tags: any): { color: string; icon: any } { + return {color: "", icon: undefined}; + }, + }); + + function rmStart(toRemove: string, title: string): string { + if (title.toLowerCase().indexOf(toRemove.toLowerCase()) == 0) { + return title.substr(toRemove.length).trim(); + } + return title; + + } + + function rmStarts(toRemove: string[], title: string) { + for (const toRm of toRemove) { + title = rmStart(toRm, title); + } + return title; + } + + this.title = new TagRenderingOptions({ + tagsPreprocessor: (tags) => { + if (tags.name) { + const nm = + rmStarts( + ["Vogelkijkhut", "Vogelkijkwand", "Kijkwand", "Kijkhut"], + tags.name); + + tags.name = " '" + nm + "'"; + } else { + tags.name = ""; + } + }, + mappings: [ + { + k: new And([new Tag("shelter", "no"), new Tag("building", "")]), + txt: "Vogelkijkwand{name}" + }, + { + k: new And([new Tag("amenity", "shelter"), new Tag("building", "yes")]), + txt: "Vogelijkhut{name}" + }, + { + k: new Tag("amenity", "shelter"), + txt: "Vogelijkhut{name}" + }, + { + k: new Tag("building", "yes"), + txt: "Vogelijkhut{name}" + }, + {k: null, txt: "Vogelkijkplaats{name}"} + ] + }); + + + this.style = (properties) => { + let icon = "assets/nature/birdhide.svg"; + if (new Or([new Tag("amenity", "shelter"), new Tag("building", "yes"), new Tag("shelter", "yes")]).matchesProperties(properties)) { + icon = "assets/nature/birdshelter.svg"; + } + + return { + color: "#0000bb", + icon: L.icon({ + iconUrl: icon, + iconSize: [40,40], + iconAnchor: [20,20] + }) + } + } + + + this.elementsToShow = [ + new ImageCarouselWithUploadConstructor(), + + new TagRenderingOptions({ + question: "Is dit een kijkwand of kijkhut?", + mappings: [ + { + k: new And([new Tag("shelter", "no"), new Tag("building", ""), new Tag("amenity", "")]), + txt: "Vogelkijkwand" + }, + { + k: new And([new Tag("amenity", "shelter"), new Tag("building", "yes"), new Tag("shelter", "yes")]), + txt: "Vogelijkhut" + } + ] + }), + new TagRenderingOptions({ + question: "Is ze rolstoeltoegankelijk?", + mappings: [ + { + k: new Tag("wheelchair", "no"), + txt: "Niet rolstoeltoegankelijk" + }, + { + k: new Tag("wheelchair", "limited"), + txt: "Een rolstoel raakt er, maar het is niet makkelijk" + }, + { + k: new Tag("wheelchair", "yes"), + txt: "Een rolstoel raakt er gemakkelijk" + } + ] + }), + + new TagRenderingOptions({ + question: "Wie beheert deze?", + freeform: { + key: "operator", + template: "Beheer door $$$", + renderTemplate: "Beheer door {operator}", + placeholder: "organisatie" + }, + mappings: [ + {k: new Tag("operator", "Natuurpunt"), txt: "Natuurpunt"}, + {k: new Tag("operator", "Agentschap Natuur en Bos"), txt: "het Agentschap Natuur en Bos (ANB)"}, + + ] + }) + + + ]; + + } +} \ No newline at end of file diff --git a/Customizations/Layers/Bookcases.ts b/Customizations/Layers/Bookcases.ts index 52e0b86e0..25953f44c 100644 --- a/Customizations/Layers/Bookcases.ts +++ b/Customizations/Layers/Bookcases.ts @@ -5,6 +5,7 @@ import {QuestionDefinition} from "../../Logic/Question"; import {TagRenderingOptions} from "../TagRendering"; import {NameInline} from "../Questions/NameInline"; import {NameQuestion} from "../Questions/NameQuestion"; +import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload"; export class Bookcases extends LayerDefinition { @@ -19,7 +20,7 @@ export class Bookcases extends LayerDefinition { this.title = new NameInline("ruilboekenkastje"); this.elementsToShow = [ - + new ImageCarouselWithUploadConstructor(), new TagRenderingOptions({ question: "Heeft dit boekenruilkastje een naam?", freeform: { diff --git a/Customizations/Layers/DrinkingWater.ts b/Customizations/Layers/DrinkingWater.ts new file mode 100644 index 000000000..7bdd63240 --- /dev/null +++ b/Customizations/Layers/DrinkingWater.ts @@ -0,0 +1,62 @@ +import {LayerDefinition} from "../LayerDefinition"; +import {And, Or, Tag} from "../../Logic/TagsFilter"; +import {OperatorTag} from "../Questions/OperatorTag"; +import * as L from "leaflet"; +import FixedText from "../Questions/FixedText"; +import {TagRenderingOptions} from "../TagRendering"; +import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload"; + +export class DrinkingWater extends LayerDefinition { + + constructor() { + super(); + this.name = "drinking water"; + this.icon = "./assets/bike/drinking_water.svg"; + + this.overpassFilter = new Or([ + new And([ + new Tag("amenity", "drinking_water") + ]) + ]); + + + this.newElementTags = [ + new Tag("amenity", "drinking_water"), + ]; + this.maxAllowedOverlapPercentage = 10; + + this.minzoom = 13; + this.style = this.generateStyleFunction(); + this.title = new FixedText("Drinking water"); + this.elementsToShow = [ + new OperatorTag(), + ]; + this.elementsToShow = [ + new ImageCarouselWithUploadConstructor(), + new TagRenderingOptions({ + question: "How easy is it to fill water bottles?", + mappings: [ + { k: new Tag("bottle", "yes"), txt: "It is easy to refill water bottles" }, + { k: new Tag("bottle", "no"), txt: "Water bottles may not fit" } + ], + })]; + + } + + + private generateStyleFunction() { + const self = this; + return function (properties: any) { + + return { + color: "#00bb00", + icon: new L.icon({ + iconUrl: self.icon, + iconSize: [50, 50], + iconAnchor: [25,50] + }) + }; + }; + } + +} \ No newline at end of file diff --git a/Customizations/Layers/GrbToFix.ts b/Customizations/Layers/GrbToFix.ts index d008f2f9a..5c7fcf9ba 100644 --- a/Customizations/Layers/GrbToFix.ts +++ b/Customizations/Layers/GrbToFix.ts @@ -52,19 +52,13 @@ export class GrbToFix extends LayerDefinition { question: "Wat is het huisnummer?", tagsPreprocessor: tags => { - const newTags = {}; - newTags["addr:housenumber"] = tags["addr:housenumber"] - newTags["addr:street"] = tags["addr:street"] - const telltale = "GRB thinks that this has number "; const index = tags.fixme.indexOf(telltale); if (index >= 0) { const housenumber = tags.fixme.slice(index + telltale.length); - newTags["grb:housenumber:human"] = housenumber; - newTags["grb:housenumber"] = housenumber == "no number" ? "" : housenumber; + tags["grb:housenumber:human"] = housenumber; + tags["grb:housenumber"] = housenumber == "no number" ? "" : housenumber; } - - return newTags; }, freeform: { key: "addr:housenumber", diff --git a/Customizations/Layers/InformationBoard.ts b/Customizations/Layers/InformationBoard.ts new file mode 100644 index 000000000..240e9e16f --- /dev/null +++ b/Customizations/Layers/InformationBoard.ts @@ -0,0 +1,117 @@ +import {LayerDefinition} from "../LayerDefinition"; +import FixedText from "../Questions/FixedText"; +import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload"; +import {TagRenderingOptions} from "../TagRendering"; +import {And, Tag} from "../../Logic/TagsFilter"; +import L from "leaflet"; + +export class InformationBoard extends LayerDefinition { + constructor() { + super({ + name: "Informatiebord", + minzoom: 12, + overpassFilter: new Tag("tourism", "information"), + newElementTags: [new Tag("tourism", "information")], + maxAllowedOverlapPercentage: 0, + icon: "assets/nature/info.png", + }); + + const isMap = new Tag("information", "map"); + const isOsmSource = new Tag("map_source", "OpenStreetMap"); + + this.title = new TagRenderingOptions({ + mappings: [ + {k: isMap, txt: "Kaart"}, + {k:null, txt: "Informatiebord"} + ] + }); + + this.style = (properties) => { + let icon = "assets/nature/info.png"; + if (isMap.matchesProperties(properties)) { + icon = "assets/map.svg"; + if (isOsmSource.matchesProperties(properties)) { + icon = "assets/osm-logo-white-bg.svg"; + + const attr = properties["map_source:attribution"]; + if (attr == "sticker") { + icon = "assets/map-stickered.svg" + } else if (attr == "no") { + icon = "assets/osm-logo-buggy-attr.svg" + } + } + } + + return { + color: "#000000", + icon: L.icon( + { + iconUrl: icon, + iconSize: [50, 50] + } + ) + }; + } + + + this.elementsToShow = [ + + new ImageCarouselWithUploadConstructor(), + + new TagRenderingOptions({ + question: "Heeft dit informatiebord een kaart?", + mappings: [ + {k: new Tag("information","board"), txt: "Dit is een informatiebord"}, + {k: isMap, txt: "Dit is een kaart"} + ] + }), + + new TagRenderingOptions({ + question: "Is this map based on OpenStreetMap?", + mappings: [ + { + k: isOsmSource, + txt: "This map is based on OpenStreetMap" + }, + { + k: new And([new Tag("map_source:attribution", ""), new Tag("map_source","")]), + txt: "Unknown" + }, + ], + freeform: { + key: "map_source", + extraTags: new Tag("map_source:attribution", ""), + renderTemplate: "The map data is based on {map_source}", + template: "The map data is based on $$$" + } + }).OnlyShowIf(isMap), + new TagRenderingOptions({ + question: "Is the attribution present?", + mappings: [ + { + k: new Tag("map_source:attribution", "yes"), + txt: "OpenStreetMap is clearly attribute, including the ODBL-license" + }, + { + k: new Tag("map_source:attribution", "incomplete"), + txt: "OpenStreetMap is clearly attribute, but the license is not mentioned" + }, + { + k: new Tag("map_source:attribution", "sticker"), + txt: "OpenStreetMap wasn't mentioned, but someone put an OpenStreetMap-sticker on it" + }, + { + k: new Tag("map_source:attribution", "no"), + txt: "There is no attribution at all" + }, + { + k: new Tag("map_source:attribution", "none"), + txt: "There is no attribution at all" + } + ] + }).OnlyShowIf(new Tag("map_source", "OpenStreetMap")) + ] + + + } +} \ No newline at end of file diff --git a/Customizations/Layers/Map.ts b/Customizations/Layers/Map.ts new file mode 100644 index 000000000..aeadf2920 --- /dev/null +++ b/Customizations/Layers/Map.ts @@ -0,0 +1,96 @@ +import {LayerDefinition} from "../LayerDefinition"; +import FixedText from "../Questions/FixedText"; +import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload"; +import {TagRenderingOptions} from "../TagRendering"; +import {And, Tag} from "../../Logic/TagsFilter"; +import L from "leaflet"; + +export class Map extends LayerDefinition { + constructor() { + super(); + this.name = "Map"; + this.title = new FixedText("Map"); + this.minzoom = 12; + + this.overpassFilter = new Tag("information", "map"); + this.newElementTags = [new Tag("tourism", "information"), new Tag("information", "map")]; + + + const isOsmSource = new Tag("map_source", "OpenStreetMap"); + + + + this.style = (properties) => { + let icon = "assets/map.svg"; + if(isOsmSource.matchesProperties(properties)){ + icon = "assets/osm-logo-white-bg.svg"; + + const attr = properties["map_source:attribution"]; + if(attr == "sticker"){ + icon = "assets/map-stickered.svg" + }else if(attr == "no"){ + icon = "assets/osm-logo-buggy-attr.svg" + } + + } + + return { + color: "#000000", + icon: L.icon( + { + iconUrl: icon, + iconSize: [50, 50] + } + ) + }; + } + + + this.elementsToShow = [ + + new ImageCarouselWithUploadConstructor(), + + new TagRenderingOptions({ + question: "Is this map based on OpenStreetMap?", + mappings: [ + { + k: isOsmSource, + txt: "This map is based on OpenStreetMap" + }, + ], + freeform: { + key: "map_source", + renderTemplate: "The map data is based on {map_source}", + template: "The map data is based on $$$" + } + }), + new TagRenderingOptions({ + question: "Is the attribution present?", + mappings: [ + { + k: new Tag("map_source:attribution", "yes"), + txt: "OpenStreetMap is clearly attribute, including the ODBL-license" + }, + { + k: new Tag("map_source:attribution", "incomplete"), + txt: "OpenStreetMap is clearly attribute, but the license is not mentioned" + }, + { + k: new Tag("map_source:attribution", "sticker"), + txt: "OpenStreetMap wasn't mentioned, but someone put an OpenStreetMap-sticker on it" + }, + { + k: new Tag("map_source:attribution", "no"), + txt: "There is no attribution at all" + }, + { + k: new Tag("map_source:attribution", "none"), + txt: "There is no attribution at all" + } + ] + }).OnlyShowIf(new Tag("map_source", "OpenStreetMap")) + ] + + + } +} \ No newline at end of file diff --git a/Customizations/Layers/NatureReserves.ts b/Customizations/Layers/NatureReserves.ts index 5d492922e..6adde3134 100644 --- a/Customizations/Layers/NatureReserves.ts +++ b/Customizations/Layers/NatureReserves.ts @@ -10,18 +10,18 @@ import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWi export class NatureReserves extends LayerDefinition { - constructor() { + constructor(moreQuests: boolean = false) { super(); this.name = "natuurgebied"; this.icon = "./assets/tree_white_background.svg"; this.overpassFilter = - new Or([new Tag("leisure", "nature_reserve"), new Tag("boundary","protected_area")]); + new Or([new Tag("leisure", "nature_reserve"), new Tag("boundary", "protected_area")]); this.maxAllowedOverlapPercentage = 10; this.newElementTags = [new Tag("leisure", "nature_reserve"), new Tag("fixme", "Toegevoegd met MapComplete, geometry nog uit te tekenen")] this.minzoom = 13; - this.title = new NameInline("natuurreservaat"); + this.title = new NameInline("natuurreservaat"); this.style = this.generateStyleFunction(); this.elementsToShow = [ new ImageCarouselWithUploadConstructor(), @@ -30,6 +30,67 @@ export class NatureReserves extends LayerDefinition { new OperatorTag(), new DescriptionQuestion("natuurgebied") ]; + + + const extraRenderings = [ + new TagRenderingOptions({ + question: "Mogen honden in dit natuurgebied?", + mappings: [ + {k: new Tag("dog", "leashed"), txt: "Honden moeten aan de leiband"}, + {k: new Tag("dog", "no"), txt: "Honden zijn niet toegestaan"}, + {k: new Tag("dog", "yes"), txt: "Honden zijn welkom"}, + ] + }).OnlyShowIf(new Tag("access", "yes")), + new TagRenderingOptions({ + question: "Op welke website kunnen we meer informatie vinden over dit natuurgebied?", + freeform: { + key:"website", + renderTemplate: "Meer informatie", + template: "$$$" + } + }), + new TagRenderingOptions({ + question: "Wie is de conservator van dit gebied?
" + + "Geef de naam van de conservator énkel als die duidelijk online staat gepubliceerd.", + freeform: { + renderTemplate: "De conservator van dit gebied is {curator}", + template: "$$$", + key: "curator" + } + }), + new TagRenderingOptions( + { + question: "Wat is het email-adres van de beheerder?
" + + "Geef bij voorkeur het emailadres van de Natuurpunt-afdeling; geef enkel een email-adres van de conservator als dit duidelijk is gepubliceerd", + freeform: { + renderTemplate: "Bij problemen of vragen, de conservator kan bereikt worden via " + + "{email}", + template: "$$$", + key: "email" + } + }), + new TagRenderingOptions( + { + question: "Wat is het telefoonnummer van de beheerder?
" + + "Geef bij voorkeur het telefoonnummer van de Natuurpunt-afdeling; geef enkel een email-adres van de conservator als dit duidelijk is gepubliceerd", + freeform: { + renderTemplate: "Bij problemen of vragen, de {conservator} kan bereikt worden via " + + "{phone}", + template: "$$$", + key: "phone" + } + + }), + + + ]; + + if (moreQuests) { + this.elementsToShow = + this.elementsToShow.concat(extraRenderings); + } + + } diff --git a/Customizations/Layers/Widths.ts b/Customizations/Layers/Widths.ts new file mode 100644 index 000000000..6589399a0 --- /dev/null +++ b/Customizations/Layers/Widths.ts @@ -0,0 +1,309 @@ +import {LayerDefinition} from "../LayerDefinition"; +import {And, Not, Or, Tag} from "../../Logic/TagsFilter"; +import {TagRenderingOptions} from "../TagRendering"; +import {UIEventSource} from "../../UI/UIEventSource"; +import {Park} from "./Park"; + +export class Widths extends LayerDefinition { + + private cyclistWidth: number; + private carWidth: number; + private pedestrianWidth: number; + + private readonly _bothSideParking = new Tag("parking:lane:both", "parallel"); + private readonly _noSideParking = new Tag("parking:lane:both", "no_parking"); + private readonly _otherParkingMode = + new Or([ + new Tag("parking:lane:both", "perpendicular"), + new Tag("parking:lane:left", "perpendicular"), + new Tag("parking:lane:right", "perpendicular"), + new Tag("parking:lane:both", "diagonal"), + new Tag("parking:lane:left", "diagonal"), + new Tag("parking:lane:right", "diagonal"), + ]) + + + private readonly _leftSideParking = + new And([new Tag("parking:lane:left", "parallel"), new Tag("parking:lane:right", "no_parking")]); + private readonly _rightSideParking = + new And([new Tag("parking:lane:right", "parallel"), new Tag("parking:lane:left", "no_parking")]); + + + private _sidewalkBoth = new Tag("sidewalk", "both"); + private _sidewalkLeft = new Tag("sidewalk", "left"); + private _sidewalkRight = new Tag("sidewalk", "right"); + private _sidewalkNone = new Tag("sidewalk", "none"); + + + private readonly _oneSideParking = new Or([this._leftSideParking, this._rightSideParking]); + + private readonly _carfree = new Or([new Tag("highway", "pedestrian"), new Tag("highway", "living_street")]) + private readonly _notCarFree = new Not(this._carfree); + + private calcProps(properties) { + let parkingStateKnown = true; + let parallelParkingCount = 0; + + if (this._oneSideParking.matchesProperties(properties)) { + parallelParkingCount = 1; + } else if (this._bothSideParking.matchesProperties(properties)) { + parallelParkingCount = 2; + } else if (this._noSideParking.matchesProperties(properties)) { + parallelParkingCount = 0; + } else if (this._otherParkingMode.matchesProperties(properties)) { + parallelParkingCount = 0; + } else { + parkingStateKnown = false; + console.log("No parking data for ", properties.name, properties.id, properties) + } + + + let pedestrianFlowNeeded = 0; + + if (this._sidewalkBoth.matchesProperties(properties)) { + pedestrianFlowNeeded = 0; + } else if (this._sidewalkNone.matchesProperties(properties)) { + pedestrianFlowNeeded = 2; + } else if (this._sidewalkLeft.matchesProperties(properties) || this._sidewalkRight.matches(properties)) { + pedestrianFlowNeeded = 1; + } else { + pedestrianFlowNeeded = -1; + } + + + let onewayCar = properties.oneway === "yes"; + let onewayBike = properties["oneway:bicycle"] === "yes" || + (onewayCar && properties["oneway:bicycle"] === undefined) + + let cyclingAllowed = + !(properties.bicycle === "use_sidepath" + || properties.bicycle === "no"); + + let carWidth = (onewayCar ? 1 : 2) * this.carWidth; + let cyclistWidth = 0; + if (cyclingAllowed) { + cyclistWidth = (onewayBike ? 1 : 2) * this.cyclistWidth; + } + + const width = parseFloat(properties["width:carriageway"]); + + + const targetWidth = + carWidth + + cyclistWidth + + Math.max(0, pedestrianFlowNeeded) * this.pedestrianWidth + + parallelParkingCount * this.carWidth; + + return { + parkingLanes: parallelParkingCount, + parkingStateKnown: parkingStateKnown, + width: width, + targetWidth: targetWidth, + onewayBike: onewayBike, + pedestrianFlowNeeded: pedestrianFlowNeeded, + cyclingAllowed: cyclingAllowed + } + } + + + constructor(carWidth: number, + cyclistWidth: number, + pedestrianWidth: number) { + super(); + this.carWidth = carWidth; + this.cyclistWidth = cyclistWidth; + this.pedestrianWidth = pedestrianWidth; + this.minzoom = 12; + + function r(n: number) { + const pre = Math.floor(n); + const post = Math.floor((n * 10) % 10); + return "" + pre + "." + post; + } + + this.name = "widths"; + this.overpassFilter = new Tag("width:carriageway", "*"); + + this.title = new TagRenderingOptions({ + freeform: { + renderTemplate: "{name}", + template: "$$$", + key: "name" + } + }) + + const self = this; + this.style = (properties) => { + + let c = "#f00"; + + + const props = self.calcProps(properties); + if (props.pedestrianFlowNeeded > 0) { + c = "#fa0" + } + if (props.width >= props.targetWidth || !props.cyclingAllowed) { + c = "#0c0"; + } + + if (!props.parkingStateKnown && properties["note:width:carriageway"] === undefined) { + c = "#f0f" + } + + if (this._carfree.matchesProperties(properties)) { + c = "#aaa"; + } + + + // Mark probably wrong data + if (props.width > 15) { + c = "#f0f" + } + + let dashArray = undefined; + if (props.onewayBike) { + dashArray = [20, 8] + } + return { + icon: null, + color: c, + weight: 7, + dashArray: dashArray + } + } + + this.elementsToShow = [ + new TagRenderingOptions({ + question: "Mogen auto's hier parkeren?", + mappings: [ + { + k: this._bothSideParking, + txt: "Auto's kunnen langs beide zijden parkeren.Dit gebruikt " + r(this.carWidth * 2) + "m
" + }, + { + k: this._oneSideParking, + txt: "Auto's kunnen langs één kant parkeren.
Dit gebruikt " + r(this.carWidth) + "m
" + }, + { + k: this._otherParkingMode, + txt: "Deze straat heeft dwarsparkeren of diagonaalparkeren aan minstens één zijde. Deze parkeerruimte is niet opgenomen in de straatbreedte." + }, + {k: this._noSideParking, txt: "Auto's mogen hier niet parkeren"}, + // {k: null, txt: "Nog geen parkeerinformatie bekend"} + ], + freeform: { + key: "note:width:carriageway", + renderTemplate: "{note:width:carriageway}", + template: "$$$", + } + }).OnlyShowIf(this._notCarFree), + + + new TagRenderingOptions({ + mappings: [ + { + k: this._sidewalkNone, + txt: "Deze straat heeft geen voetpaden. Voetgangers hebben hier " + r(this.pedestrianWidth * 2) + "m nodig" + }, + { + k: new Or([this._sidewalkLeft, this._sidewalkRight]), + txt: "Deze straat heeft een voetpad aan één kant. Voetgangers hebben hier " + r(this.pedestrianWidth) + "m nodig" + }, + {k: this._sidewalkBoth, txt: "Deze straat heeft voetpad aan beide zijden."}, + ], + freeform: { + key: "note:width:carriageway", + renderTemplate: "{note:width:carriageway}", + template: "$$$", + } + }).OnlyShowIf(this._notCarFree), + + + new TagRenderingOptions({ + mappings: [ + { + k: new Tag("bicycle", "use_sidepath"), + txt: "Er is een afgescheiden, verplicht te gebruiken fietspad. Fietsen op dit wegsegment hoeft dus niet" + }, + { + k: new Tag("bicycle", "no"), + txt: "Fietsen is hier niet toegestaan" + }, + { + k: new Tag("oneway:bicycle", "yes"), + txt: "Eenrichtingsverkeer, óók voor fietsers. Dit gebruikt " + r(this.carWidth + this.cyclistWidth) + "m" + }, + { + k: new And([new Tag("oneway", "yes"), new Tag("oneway:bicycle", "no")]), + txt: "Tweerichtingverkeer voor fietsers, eenrichting voor auto's Dit gebruikt " + r(this.carWidth + 2 * this.cyclistWidth) + "m" + }, + { + k: new Tag("oneway", "yes"), + txt: "Eenrichtingsverkeer voor iedereen. Dit gebruikt " + (this.carWidth + this.cyclistWidth) + "m" + }, + { + k: null, + txt: "Tweerichtingsverkeer voor iedereen. Dit gebruikt " + r(2 * this.carWidth + 2 * this.cyclistWidth) + "m" + } + ] + }).OnlyShowIf(this._notCarFree), + + new TagRenderingOptions( + { + tagsPreprocessor: (tags) => { + const props = self.calcProps(tags); + tags.targetWidth = r(props.targetWidth); + tags.short = ""; + if (props.width < props.targetWidth) { + tags.short = "Er is dus " + r(props.targetWidth - props.width) + "m te weinig" + } + }, + freeform: { + key: "width:carriageway", + renderTemplate: "De totale nodige ruimte voor vlot en veilig verkeer is dus {targetWidth}m
" + + "{short}", + template: "$$$", + } + } + ).OnlyShowIf(this._notCarFree), + + + new TagRenderingOptions({ + mappings: [ + {k:new Tag("highway","living_street"),txt: "Dit is een woonerf"}, + {k:new Tag("highway","pedestrian"),txt: "Deze weg is autovrij"} + ] + }), + + new TagRenderingOptions({ + mappings: [ + { + k: new Tag("sidewalk", "none"), + txt: "De afstand van huis tot huis is {width:carriageway}m" + }, + { + k: new Tag("sidewalk", "left"), + txt: "De afstand van huis tot voetpad is {width:carriageway}m" + }, + { + k: new Tag("sidewalk", "right"), + txt: "De afstand van huis tot voetpad is {width:carriageway}m" + }, + { + k: new Tag("sidewalk", "both"), + txt: "De afstand van voetpad tot voetpad is {width:carriageway}m" + }, + { + k: new Tag("sidewalk", ""), + txt: "De straatbreedte is {width:carriageway}m" + } + + ] + }) + + + ] + + } + +} \ No newline at end of file diff --git a/Customizations/Layouts/Cyclofix.ts b/Customizations/Layouts/Cyclofix.ts index 9c9148b49..e1b40c450 100644 --- a/Customizations/Layouts/Cyclofix.ts +++ b/Customizations/Layouts/Cyclofix.ts @@ -4,6 +4,8 @@ import BikeServices from "../Layers/BikeStations"; import BikeShops from "../Layers/BikeShops"; import {GhostBike} from "../Layers/GhostBike"; import Translations from "../../UI/i18n/Translations"; +import {DrinkingWater} from "../Layers/DrinkingWater"; +import {BikeShop} from "../Layers/BikeShop"; export default class Cyclofix extends Layout { @@ -11,7 +13,7 @@ export default class Cyclofix extends Layout { super( "pomp", Translations.t.cylofix.title, - [/*new GhostBike(),*/ new BikeServices(), new BikeParkings(), new BikeShops], + [/*new GhostBike(),*/ new BikeServices(), new BikeParkings(), new BikeShops(), new DrinkingWater()], 16, 50.8465573, 4.3516970, diff --git a/Customizations/Layouts/MetaMap.ts b/Customizations/Layouts/MetaMap.ts new file mode 100644 index 000000000..8302f985d --- /dev/null +++ b/Customizations/Layouts/MetaMap.ts @@ -0,0 +1,18 @@ +import {Layout} from "../Layout"; +import * as Layer from "../Layers/Bookcases"; +import {Map} from "../Layers/Map"; + +export class MetaMap extends Layout{ + constructor() { + super( "metamap", + "Open Map Map", + [new Map()], + 1, + 0, + 0, + + + "

Open Map Map

\n" + + "This map is a map of physical maps, as known by OpenStreetMap."); + } +} \ No newline at end of file diff --git a/Customizations/Layouts/Natuurpunt.ts b/Customizations/Layouts/Natuurpunt.ts new file mode 100644 index 000000000..8216fcb7e --- /dev/null +++ b/Customizations/Layouts/Natuurpunt.ts @@ -0,0 +1,20 @@ +import {Layout} from "../Layout"; +import {Birdhide} from "../Layers/Birdhide"; +import {InformationBoard} from "../Layers/InformationBoard"; +import {NatureReserves} from "../Layers/NatureReserves"; + +export class Natuurpunt extends Layout{ + constructor() { + super( + "natuurpunt", + "De natuur in", + [new Birdhide(), new InformationBoard(), new NatureReserves(true)], + 12, + 51.20875, + 3.22435, + "

Natuurpuntstuff

", + "", + "" + ); + } +} \ No newline at end of file diff --git a/Customizations/Layouts/StreetWidth.ts b/Customizations/Layouts/StreetWidth.ts new file mode 100644 index 000000000..f93e8a1ad --- /dev/null +++ b/Customizations/Layouts/StreetWidth.ts @@ -0,0 +1,35 @@ +import {Layout} from "../Layout"; +import * as Layer from "../Layers/Bookcases"; +import {Widths} from "../Layers/Widths"; +import {UIEventSource} from "../../UI/UIEventSource"; + +export class StreetWidth extends Layout{ + + constructor() { + super( "width", + "Straatbreedtes in Brugge", + [new Widths( + 2, + 1.5, + 0.75 + + )], + 15, + 51.20875, + 3.22435, + "

De straat is opgebruikt

" + + "

Er is steeds meer druk op de openbare ruimte. Voetgangers, fietsers, steps, auto's, bussen, bestelwagens, buggies, cargobikes, ... willen allemaal hun deel van de openbare ruimte.

" + + "" + + "

In deze studie nemen we Brugge onder de loep en kijken we hoe breed elke straat is én hoe breed elke straat zou moeten zijn voor een veilig én vlot verkeer.

" + + "Verschillende ingrepen kunnen de stad teruggeven aan de inwoners en de stad leefbaarder en levendiger maken.
" + + "Denk aan:" + + "
    " + + "
  • De autovrije zone's uitbreiden
  • " + + "
  • De binnenstad fietszone maken
  • " + + "
  • Het aantal woonerven uitbreiden
  • " + + "
  • Grotere auto's meer belasten - ze nemen immers meer parkeerruimte in.
  • " + + "
", + "", + ""); + } +} \ No newline at end of file diff --git a/Customizations/Layouts/WalkByBrussels.ts b/Customizations/Layouts/WalkByBrussels.ts new file mode 100644 index 000000000..ca8fdc4b4 --- /dev/null +++ b/Customizations/Layouts/WalkByBrussels.ts @@ -0,0 +1,28 @@ +import { Layout } from "../Layout"; +import { DrinkingWater } from "../Layers/DrinkingWater"; +import { NatureReserves } from "../Layers/NatureReserves"; +import { Park } from "../Layers/Park"; + +export class WalkByBrussels extends Layout { + constructor() { + super("walkbybrussels", + "Drinking Water Spots", + [new DrinkingWater(), new Park(), new NatureReserves()], + 10, + 50.8435, + 4.3688, + + + "

Drinking water

\n" + + "\n" + + "

" + + "Help with creating a map of drinking water points!" + + , + "

Start by creating an account\n" + + " or by " + + " logging in.

", + "Start by clicking a pin and answering the questions"); + } + +} \ No newline at end of file diff --git a/Customizations/OnlyShowIf.ts b/Customizations/OnlyShowIf.ts index c6f6eafa9..4d629f0f7 100644 --- a/Customizations/OnlyShowIf.ts +++ b/Customizations/OnlyShowIf.ts @@ -17,9 +17,9 @@ export class OnlyShowIfConstructor implements TagDependantUIElementConstructor{ this._embedded = embedded; } - construct(tags: UIEventSource, changes: Changes): TagDependantUIElement { - return new OnlyShowIf(tags, - this._embedded.construct(tags, changes), + construct(dependencies): TagDependantUIElement { + return new OnlyShowIf(dependencies.tags, + this._embedded.construct(dependencies), this._tagsFilter); } diff --git a/Customizations/Questions/WikipediaLink.ts b/Customizations/Questions/WikipediaLink.ts index 03be61e46..7d9087c00 100644 --- a/Customizations/Questions/WikipediaLink.ts +++ b/Customizations/Questions/WikipediaLink.ts @@ -4,6 +4,9 @@ import {TagRenderingOptions} from "../TagRendering"; export class WikipediaLink extends TagRenderingOptions { private static FixLink(value: string): string { + if (value === undefined) { + return undefined; + } // @ts-ignore if (value.startsWith("https")) { return value; @@ -20,6 +23,11 @@ export class WikipediaLink extends TagRenderingOptions { static options = { priority: 10, // question: "Wat is het overeenstemmende wkipedia-artikel?", + tagsPreprocessor: (tags) => { + if (tags.wikipedia !== undefined) { + tags.wikipedia = WikipediaLink.FixLink(tags.wikipedia); + } + }, freeform: { key: "wikipedia", template: "$$$", @@ -28,19 +36,8 @@ export class WikipediaLink extends TagRenderingOptions { "" + "wikipedia" + "", - placeholder: "", - tagsPreprocessor: (tags) => { + placeholder: "" - const newTags = {}; - for (const k in tags) { - if (k === "wikipedia") { - newTags["wikipedia"] = WikipediaLink.FixLink(tags[k]); - } else { - newTags[k] = tags[k]; - } - } - return newTags; - } }, } diff --git a/Customizations/TagRendering.ts b/Customizations/TagRendering.ts index 60b071372..bee4c805f 100644 --- a/Customizations/TagRendering.ts +++ b/Customizations/TagRendering.ts @@ -80,14 +80,15 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor { /** * In some very rare cases, tags have to be rewritten before displaying - * This function adds this + * This function can be used for that. + * This function is ran on a _copy_ of the original properties */ - tagsPreprocessor?: ((tags: any) => any) + tagsPreprocessor?: ((tags: any) => void) }) { this.options = options; } - - OnlyShowIf(tagsFilter: TagsFilter) : TagDependantUIElementConstructor{ + + OnlyShowIf(tagsFilter: TagsFilter): TagDependantUIElementConstructor { return new OnlyShowIfConstructor(tagsFilter, this); } @@ -111,8 +112,8 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor { } - construct(tags: UIEventSource, changes: Changes): TagDependantUIElement { - return new TagRendering(tags, changes, this.options); + construct(dependencies: { tags: UIEventSource, changes: Changes }): TagDependantUIElement { + return new TagRendering(dependencies.tags, dependencies.changes, this.options); } IsKnown(properties: any): boolean { @@ -183,11 +184,22 @@ class TagRendering extends UIElement implements TagDependantUIElement { this._userDetails = changes.login.userDetails; this.ListenTo(this._userDetails); - + this._question = options.question; this._priority = options.priority ?? 0; this._primer = options.primer ?? ""; - this._tagsPreprocessor = options.tagsPreprocessor; + this._tagsPreprocessor = function (properties) { + if (options.tagsPreprocessor === undefined) { + return properties; + } + const newTags = {}; + for (const k in properties) { + newTags[k] = properties[k]; + } + options.tagsPreprocessor(newTags); + return newTags; + }; + this._mapping = []; this._renderMapping = []; this._freeform = options.freeform; @@ -313,9 +325,9 @@ class TagRendering extends UIElement implements TagDependantUIElement { const cancelContents = this._editMode.map((isEditing) => { if (isEditing) { - return "Annuleren"; + return "Cancel"; } else { - return "Overslaan (Ik weet het niet zeker...)"; + return "Skip this question"; } }); // And at last, set up the skip button @@ -325,12 +337,7 @@ class TagRendering extends UIElement implements TagDependantUIElement { } private ApplyTemplate(template: string): string { - let tags = this._source.data; - if (this._tagsPreprocessor !== undefined) { - tags = this._tagsPreprocessor(tags); - } - - + const tags = this._tagsPreprocessor(this._source.data); return TagUtils.ApplyTemplate(template, tags); } diff --git a/Customizations/UIElementConstructor.ts b/Customizations/UIElementConstructor.ts index d90ce0cdd..61f16ae54 100644 --- a/Customizations/UIElementConstructor.ts +++ b/Customizations/UIElementConstructor.ts @@ -5,7 +5,7 @@ import {UIElement} from "../UI/UIElement"; export interface TagDependantUIElementConstructor { - construct(tags: UIEventSource, changes: Changes): TagDependantUIElement; + construct(dependencies: {tags: UIEventSource, changes: Changes}): TagDependantUIElement; IsKnown(properties: any): boolean; IsQuestioning(properties: any): boolean; Priority(): number; diff --git a/Logic/Changes.ts b/Logic/Changes.ts index de58a769c..e0dd9ce8d 100644 --- a/Logic/Changes.ts +++ b/Logic/Changes.ts @@ -117,7 +117,7 @@ console.log("Received change",key, value) return geojson; } - public uploadAll(optionalContinuation: (() => void)) { + public uploadAll(optionalContinuation: (() => void) = undefined) { const self = this; this.isSaving.setData(true); diff --git a/Logic/FilteredLayer.ts b/Logic/FilteredLayer.ts index 6ee431b0c..76a9d9c2f 100644 --- a/Logic/FilteredLayer.ts +++ b/Logic/FilteredLayer.ts @@ -1,11 +1,11 @@ -import {Basemap} from "./Basemap"; -import {TagsFilter, TagUtils} from "./TagsFilter"; -import {UIEventSource} from "../UI/UIEventSource"; -import {ElementStorage} from "./ElementStorage"; -import {Changes} from "./Changes"; +import { Basemap } from "./Basemap"; +import { TagsFilter, TagUtils } from "./TagsFilter"; +import { UIEventSource } from "../UI/UIEventSource"; +import { ElementStorage } from "./ElementStorage"; +import { Changes } from "./Changes"; import L from "leaflet" -import {GeoOperations} from "./GeoOperations"; -import {UIElement} from "../UI/UIElement"; +import { GeoOperations } from "./GeoOperations"; +import { UIElement } from "../UI/UIElement"; /*** * A filtered layer is a layer which offers a 'set-data' function @@ -20,6 +20,7 @@ export class FilteredLayer { public readonly name: string; public readonly filters: TagsFilter; + public readonly isDisplayed: UIEventSource = new UIEventSource(true); private readonly _map: Basemap; private readonly _maxAllowedOverlap: number; @@ -65,6 +66,16 @@ export class FilteredLayer { this._style = style; this._storage = storage; this._maxAllowedOverlap = maxAllowedOverlap; + const self = this; + this.isDisplayed.addCallback(function (isDisplayed) { + if (self._geolayer !== undefined && self._geolayer !== null) { + if (isDisplayed) { + self._geolayer.addTo(self._map.map); + } else { + self._map.map.removeLayer(self._geolayer); + } + } + }) } @@ -93,7 +104,7 @@ export class FilteredLayer { const notShadowed = []; for (const feature of leftoverFeatures) { - if (this._maxAllowedOverlap !== undefined && this._maxAllowedOverlap >= 0) { + if (this._maxAllowedOverlap !== undefined && this._maxAllowedOverlap > 0) { if (GeoOperations.featureIsContainedInAny(feature, selfFeatures, this._maxAllowedOverlap)) { // This feature is filtered away continue; @@ -164,7 +175,7 @@ export class FilteredLayer { radius: 25, color: style.color }); - + } else { marker = L.marker(latLng, { icon: style.icon @@ -204,7 +215,9 @@ export class FilteredLayer { } }); - this._geolayer.addTo(this._map.map); + if (this.isDisplayed.data) { + this._geolayer.addTo(this._map.map); + } } diff --git a/Logic/OsmConnection.ts b/Logic/OsmConnection.ts index 66a4719d3..648d9bbb5 100644 --- a/Logic/OsmConnection.ts +++ b/Logic/OsmConnection.ts @@ -123,11 +123,12 @@ export class OsmConnection { public preferenceSources : any = {} public GetPreference(key: string) : UIEventSource{ - if(this.preferenceSources[key] !== undefined){ + if (this.preferenceSources[key] !== undefined) { return this.preferenceSources[key]; } - this.UpdatePreferences(); - console.log("Getting preference object", key, "currently upstreamed as ",this.preferences.data[key] ); + if (this.userDetails.data.loggedIn) { + this.UpdatePreferences(); + } const pref = new UIEventSource(this.preferences.data[key]); pref.addCallback((v) => { this.SetPreference(key, v); diff --git a/Logic/StrayClickHandler.ts b/Logic/StrayClickHandler.ts index 85ccc9d7b..89acbccea 100644 --- a/Logic/StrayClickHandler.ts +++ b/Logic/StrayClickHandler.ts @@ -32,15 +32,16 @@ export class StrayClickHandler { self._lastMarker = L.marker([lastClick.lat, lastClick.lon]); const uiElement = uiToShow(); const popup = L.popup().setContent(uiElement.Render()); - uiElement.Activate(); uiElement.Update(); + uiElement.Activate(); self._lastMarker.addTo(map); self._lastMarker.bindPopup(popup).openPopup(); self._lastMarker.on("click", () => { leftMessage.setData(self._uiToShow); }); - + uiElement.Update(); + uiElement.Activate(); }); selectElement.addCallback(() => { diff --git a/Logic/TagsFilter.ts b/Logic/TagsFilter.ts index dec52ac64..aaeaf124d 100644 --- a/Logic/TagsFilter.ts +++ b/Logic/TagsFilter.ts @@ -25,6 +25,10 @@ export class Regex extends TagsFilter { } matches(tags: { k: string; v: string }[]): boolean { + if(!(tags instanceof Array)){ + throw "You used 'matches' on something that is not a list. Did you mean to use 'matchesProperties'?" + } + for (const tag of tags) { if (tag.k === this._k) { if (tag.v === "") { @@ -201,6 +205,28 @@ export class And extends TagsFilter { } } +export class Not extends TagsFilter{ + private not: TagsFilter; + + constructor(not: TagsFilter) { + super(); + this.not = not; + } + + asOverpass(): string[] { + throw "Not supported yet" + } + + matches(tags: { k: string; v: string }[]): boolean { + return !this.not.matches(tags); + } + + substituteValues(tags: any): TagsFilter { + return new Not(this.not.substituteValues(tags)); + } + +} + export class TagUtils { diff --git a/README.md b/README.md index 9994bee2e..4ca29f4ce 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ Furthermore, it shows images present in the `image` tag or, if a `wikidata` or ` - [Buurtnatuur.be](http://buurntatuur.be), developed for the Belgian [Green party](https://www.groen.be/). They also funded the initial development! - [Cyclofix](https://pietervdvn.github.io/MapComplete/index.html?quests=pomp), further development on [Open Summer of Code](https://summerofcode.be/) funded by [Brussels Mobility](https://mobilite-mobiliteit.brussels/en) - [Bookcases](https://pietervdvn.github.io/MapComplete/index.html?quests=bookcases#element) cause I like to collect them. +- [Map of Maps](https://pietervdvn.github.io/MapComplete/index.html?layout=metamap#element), after a tweet Have a theme idea? Drop it in the [issues](https://github.com/pietervdvn/MapComplete/issues) diff --git a/UI/Base/CheckBox.ts b/UI/Base/CheckBox.ts new file mode 100644 index 000000000..9cdb21e8f --- /dev/null +++ b/UI/Base/CheckBox.ts @@ -0,0 +1,19 @@ +import {UIElement} from "../UIElement"; +import {UIEventSource} from "../UIEventSource"; + + +export class CheckBox extends UIElement{ + private data: UIEventSource; + + constructor(data: UIEventSource) { + super(data); + this.data = data; + + } + + + protected InnerRender(): string { + return "Current val: "+this.data.data; + } + +} \ No newline at end of file diff --git a/UI/Base/VerticalCombine.ts b/UI/Base/VerticalCombine.ts index 2174a90e7..82d2f3d53 100644 --- a/UI/Base/VerticalCombine.ts +++ b/UI/Base/VerticalCombine.ts @@ -25,16 +25,4 @@ export class VerticalCombine extends UIElement { } return "
" + html + "
"; } - InnerUpdate(htmlElement: HTMLElement) { - for (const element of this._elements){ - element.Update(); - } - } - - Activate() { - for (const element of this._elements){ - element.Activate(); - } - } - } \ No newline at end of file diff --git a/UI/CenterMessageBox.ts b/UI/CenterMessageBox.ts index 19c5d1a66..af1020cf0 100644 --- a/UI/CenterMessageBox.ts +++ b/UI/CenterMessageBox.ts @@ -40,11 +40,10 @@ export class CenterMessageBox extends UIElement { if (this._centermessage.data != "") { return this._centermessage.data; } - - if (this._zoomInMore.data) { - return Translations.t.centerMessage.zoomIn.txt; - } else if (this._queryRunning.data) { + if (this._queryRunning.data) { return Translations.t.centerMessage.loadingData.txt; + } else if (this._zoomInMore.data) { + return Translations.t.centerMessage.zoomIn.txt; } return Translations.t.centerMessage.ready.txt; } diff --git a/UI/FeatureInfoBox.ts b/UI/FeatureInfoBox.ts index 2c7832c13..441c63a1c 100644 --- a/UI/FeatureInfoBox.ts +++ b/UI/FeatureInfoBox.ts @@ -41,12 +41,13 @@ export class FeatureInfoBox extends UIElement { this._userDetails = userDetails; this.ListenTo(userDetails); + const deps = {tags:this._tagsES , changes:this._changes} this._infoboxes = []; elementsToShow = elementsToShow ?? [] for (const tagRenderingOption of elementsToShow) { this._infoboxes.push( - tagRenderingOption.construct(this._tagsES, this._changes)); + tagRenderingOption.construct(deps)); } title = title ?? new TagRenderingOptions( @@ -55,9 +56,9 @@ export class FeatureInfoBox extends UIElement { } ) - this._title = new TagRenderingOptions(title.options).construct(this._tagsES, this._changes); - this._osmLink =new OsmLink().construct(this._tagsES, this._changes); - this._wikipedialink = new WikipediaLink().construct(this._tagsES, this._changes); + this._title = new TagRenderingOptions(title.options).construct(deps); + this._osmLink =new OsmLink().construct(deps); + this._wikipedialink = new WikipediaLink().construct(deps); } @@ -113,19 +114,5 @@ export class FeatureInfoBox extends UIElement { "" + ""; } - - Activate() { - super.Activate(); - for (const infobox of this._infoboxes) { - infobox.Activate(); - } - } - - Update() { - super.Update(); - this._title.Update(); - for (const infobox of this._infoboxes) { - infobox.Update(); - } - } + } diff --git a/UI/Image/ImageCarousel.ts b/UI/Image/ImageCarousel.ts index 9d4e1fd00..b3f40d8c5 100644 --- a/UI/Image/ImageCarousel.ts +++ b/UI/Image/ImageCarousel.ts @@ -23,10 +23,10 @@ export class ImageCarouselConstructor implements TagDependantUIElementConstructo return 0; } - construct(tags: UIEventSource, changes: Changes): TagDependantUIElement { - return new ImageCarousel(tags, changes); + construct(dependencies: { tags: UIEventSource, changes: Changes }): TagDependantUIElement { + return new ImageCarousel(dependencies.tags, dependencies.changes); } - + } export class ImageCarousel extends TagDependantUIElement { diff --git a/UI/Image/ImageCarouselWithUpload.ts b/UI/Image/ImageCarouselWithUpload.ts index 231044aae..38ebe206f 100644 --- a/UI/Image/ImageCarouselWithUpload.ts +++ b/UI/Image/ImageCarouselWithUpload.ts @@ -19,8 +19,8 @@ export class ImageCarouselWithUploadConstructor implements TagDependantUIElement return 0; } - construct(tags: UIEventSource, changes: Changes): TagDependantUIElement { - return new ImageCarouselWithUpload(tags, changes); + construct(dependencies): TagDependantUIElement { + return new ImageCarouselWithUpload(dependencies); } } @@ -28,8 +28,10 @@ class ImageCarouselWithUpload extends TagDependantUIElement { private _imageElement: ImageCarousel; private _pictureUploader: ImageUploadFlow; - constructor(tags: UIEventSource, changes: Changes) { - super(tags); + constructor(dependencies: {tags: UIEventSource, changes: Changes}) { + super(dependencies.tags); + const tags = dependencies.tags; + const changes = dependencies.changes; this._imageElement = new ImageCarousel(tags, changes); const userDetails = changes.login.userDetails; const license = changes.login.GetPreference( "mapcomplete-pictures-license"); diff --git a/UI/ImageUploadFlow.ts b/UI/ImageUploadFlow.ts index b04070a94..7258d916c 100644 --- a/UI/ImageUploadFlow.ts +++ b/UI/ImageUploadFlow.ts @@ -10,7 +10,6 @@ import Translations from "./i18n/Translations"; export class ImageUploadFlow extends UIElement { private _licensePicker: UIElement; private _selectedLicence: UIEventSource; - private _licenseExplanation: UIElement; private _isUploading: UIEventSource = new UIEventSource(0) private _uploadOptions: (license: string) => { title: string; description: string; handleURL: (url: string) => void; allDone: (() => void) }; private _userdetails: UIEventSource; @@ -44,21 +43,6 @@ export class ImageUploadFlow extends UIElement { this._selectedLicence = licensePicker.selectedElement; - const licenseExplanations = { - "CC-BY-SA 4.0": - "Creative Commonse met naamsvermelding en gelijk delen
" + - "Je foto mag door iedereen gratis gebruikt worden, als ze je naam vermelden én ze afgeleide werken met deze licentie en attributie delen.", - "CC-BY 4.0": - "Creative Commonse met naamsvermelding
" + - "Je foto mag door iedereen gratis gebruikt worden, als ze je naam vermelden", - "CC0": - "Geen copyright
Je foto mag door iedereen voor alles gebruikt worden" - } - this._licenseExplanation = new VariableUiElement( - this._selectedLicence.map((license) => { - return licenseExplanations[license] - }) - ); } @@ -67,25 +51,29 @@ export class ImageUploadFlow extends UIElement { if (!this._userdetails.data.loggedIn) { return `
${Translations.t.image.pleaseLogin.Render()}
`; } + + let uploadingMessage = ""; if (this._isUploading.data == 1) { return `${Translations.t.image.uploadingPicture.Render()}` } if (this._isUploading.data > 0) { - return "Bezig met uploaden, nog " + this._isUploading.data + " foto's te gaan..." + uploadingMessage = "Uploading multiple pictures, " + this._isUploading.data + " left..." } return "" + "
" + - + "" + "("

Naar de kaart

"), + new VariableUiElement(new UIEventSource("

Return to the map

"), () => { document.getElementById("to-the-map").onclick = function () { uielement.setData(undefined); diff --git a/UI/PendingChanges.ts b/UI/PendingChanges.ts index de76d1748..e0411a538 100644 --- a/UI/PendingChanges.ts +++ b/UI/PendingChanges.ts @@ -1,20 +1,24 @@ import {UIElement} from "./UIElement"; import {UIEventSource} from "./UIEventSource"; +import {Changes} from "../Logic/Changes"; export class PendingChanges extends UIElement { private _pendingChangesCount: UIEventSource; private _countdown: UIEventSource; private _isSaving: UIEventSource; - constructor(pendingChangesCount: UIEventSource, - countdown: UIEventSource, - isSaving: UIEventSource) { - super(pendingChangesCount); - this.ListenTo(isSaving); + constructor(changes: Changes, + countdown: UIEventSource) { + super(changes.pendingChangesES); + this.ListenTo(changes.isSaving); this.ListenTo(countdown); - this._pendingChangesCount = pendingChangesCount; + this._pendingChangesCount = changes.pendingChangesES; this._countdown = countdown; - this._isSaving = isSaving; + this._isSaving = changes.isSaving; + + this.onClick(() => { + changes.uploadAll(); + }) } protected InnerRender(): string { diff --git a/UI/QuestionPicker.ts b/UI/QuestionPicker.ts index b9cf0240e..82baac402 100644 --- a/UI/QuestionPicker.ts +++ b/UI/QuestionPicker.ts @@ -43,10 +43,5 @@ export class QuestionPicker extends UIElement { highestQ.CreateHtml(this.source).Render() + "
"; } - InnerUpdate(htmlElement: HTMLElement) { - } - - Activate() { - } } \ No newline at end of file diff --git a/UI/SaveButton.ts b/UI/SaveButton.ts index 3bba390a7..f78fbd4a5 100644 --- a/UI/SaveButton.ts +++ b/UI/SaveButton.ts @@ -19,7 +19,7 @@ export class SaveButton extends UIElement { ) { return "Opslaan" } - return "Opslaan"; + return "Save"; } } \ No newline at end of file diff --git a/UI/SearchAndGo.ts b/UI/SearchAndGo.ts index 3916168a3..5bff8412e 100644 --- a/UI/SearchAndGo.ts +++ b/UI/SearchAndGo.ts @@ -8,8 +8,8 @@ import {Basemap} from "../Logic/Basemap"; export class SearchAndGo extends UIElement { - private _placeholder = new UIEventSource("Zoek naar een locatie...") - private _searchField = new TextField(this._placeholder); + private _placeholder = new UIEventSource("Search a location...") + private _searchField = new TextField(this._placeholder, undefined); private _foundEntries = new UIEventSource([]); private _map: Basemap; @@ -35,7 +35,7 @@ export class SearchAndGo extends UIElement { private RunSearch() { const searchString = this._searchField.value.data; this._searchField.Clear(); - this._placeholder.setData("Bezig met zoeken..."); + this._placeholder.setData("Searching..."); const self = this; Geocoding.Search(searchString, this._map, (result) => { @@ -50,10 +50,10 @@ export class SearchAndGo extends UIElement { [bb[1], bb[3]] ] self._map.map.fitBounds(bounds); - this._placeholder.setData("Zoek naar een locatie..."); + this._placeholder.setData("Search a location..."); }, () => { - this._placeholder.setData("Niets gevonden: er ging iets mis"); + this._placeholder.setData("Something went wrong. Try again."); }); } diff --git a/UI/SimpleAddUI.ts b/UI/SimpleAddUI.ts index b23543755..1d9f3d61f 100644 --- a/UI/SimpleAddUI.ts +++ b/UI/SimpleAddUI.ts @@ -42,7 +42,7 @@ export class SimpleAddUI extends UIElement { //