diff --git a/.gitignore b/.gitignore index 5b305a5..5eac4bb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ dist/* node_modules .cache/* .idea/* +scratch diff --git a/Customizations/Layers/BikeCafes.ts b/Customizations/Layers/BikeCafes.ts new file mode 100644 index 0000000..e69de29 diff --git a/Customizations/Layers/BikeOtherShops.ts b/Customizations/Layers/BikeOtherShops.ts new file mode 100644 index 0000000..80d7c79 --- /dev/null +++ b/Customizations/Layers/BikeOtherShops.ts @@ -0,0 +1,114 @@ +import { LayerDefinition } from "../LayerDefinition"; +import Translations from "../../UI/i18n/Translations"; +import {And, Tag, Or} from "../../Logic/TagsFilter"; +import { ImageCarouselWithUploadConstructor } from "../../UI/Image/ImageCarouselWithUpload"; +import ShopRetail from "../Questions/bike/ShopRetail"; +import ShopPump from "../Questions/bike/ShopPump"; +import ShopRental from "../Questions/bike/ShopRental"; +import ShopRepair from "../Questions/bike/ShopRepair"; +import ShopDiy from "../Questions/bike/ShopDiy"; +import ShopName from "../Questions/bike/ShopName"; +import ShopSecondHand from "../Questions/bike/ShopSecondHand"; +import { TagRenderingOptions } from "../TagRendering"; +import { PhoneNumberQuestion } from "../Questions/PhoneNumberQuestion"; +import Website from "../Questions/Website"; + + +function anyValueExcept(key: string, exceptValue: string) { + return new And([ + new Tag(key, "*"), + new Tag(key, exceptValue, true) + ]) +} + +export default class BikeOtherShops extends LayerDefinition { + private readonly sellsBikes = new Tag("service:bicycle:retail", "yes") + private readonly repairsBikes = anyValueExcept("service:bicycle:repair", "no") + private readonly rentsBikes = new Tag("service:bicycle:rental", "yes") + private readonly hasPump = new Tag("service:bicycle:pump", "yes") + private readonly hasDiy = new Tag("service:bicycle:diy", "yes") + private readonly sellsSecondHand = anyValueExcept("service:bicycle:repair", "no") + private readonly hasBikeServices = new Or([ + this.sellsBikes, + this.repairsBikes, + // this.rentsBikes, + // this.hasPump, + // this.hasDiy, + // this.sellsSecondHand + ]) + + private readonly to = Translations.t.cyclofix.nonBikeShop + + constructor() { + super(); + this.name = this.to.name + this.icon = "./assets/bike/non_bike_repair_shop.svg" + this.overpassFilter = new And([ + anyValueExcept("shop", "bicycle"), + this.hasBikeServices + ]) + this.newElementTags = undefined + this.maxAllowedOverlapPercentage = 10 + this.wayHandling = LayerDefinition.WAYHANDLING_CENTER_AND_WAY + + this.minzoom = 13; + this.style = this.generateStyleFunction(); + this.title = new TagRenderingOptions({ + mappings: [ + { + k: new And([new Tag("name", "*"), this.sellsBikes]), + txt: this.to.titleShopNamed + }, + { + k: new And([new Tag("name", "*"), new Tag("service:bicycle:retail", "")]), + txt: this.to.titleShop + }, + { + k: new And([new Tag("name", "*"), new Tag("service:bicycle:retail", "no")]), + txt: this.to.titleRepairNamed + }, + {k: this.sellsBikes, txt: this.to.titleShop}, + {k: new Tag("service:bicycle:retail", " "), txt: this.to.title}, + {k: new Tag("service:bicycle:retail", "no"), txt: this.to.titleRepair}, + { + k: new And([new Tag("name", "*")]), + txt: this.to.titleNamed + }, + {k: null, txt: this.to.title}, + ] + }) + + this.elementsToShow = [ + new ImageCarouselWithUploadConstructor(), + new ShopName(), + new PhoneNumberQuestion("{name}"), + new Website("{name}"), + new ShopRetail(), + new ShopRental(), + new ShopRepair(), + new ShopPump(), + new ShopDiy(), + new ShopSecondHand() + ] + } + + private generateStyleFunction() { + const self = this; + return function (tags: any) { + let icon = "assets/bike/non_bike_repair_shop.svg"; + + if (self.sellsBikes.matchesProperties(tags)) { + icon = "assets/bike/non_bike_shop.svg"; + } + + return { + color: "#00bb00", + icon: { + iconUrl: icon, + iconSize: [50, 50], + iconAnchor: [25, 50] + } + } + } + } +} diff --git a/Customizations/Layers/BikeParkings.ts b/Customizations/Layers/BikeParkings.ts index 765a954..6a45a6c 100644 --- a/Customizations/Layers/BikeParkings.ts +++ b/Customizations/Layers/BikeParkings.ts @@ -1,16 +1,19 @@ import {LayerDefinition} from "../LayerDefinition"; -import {And, Or, Tag} from "../../Logic/TagsFilter"; +import {And, Or, Tag, TagsFilter} from "../../Logic/TagsFilter"; import {OperatorTag} from "../Questions/OperatorTag"; import FixedText from "../Questions/FixedText"; import ParkingType from "../Questions/bike/ParkingType"; +import ParkingCapacity from "../Questions/bike/ParkingCapacity"; import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload"; -import BikeStationOperator from "../Questions/bike/StationOperator"; import Translations from "../../UI/i18n/Translations"; import ParkingOperator from "../Questions/bike/ParkingOperator"; -import {TagRenderingOptions} from "../TagRendering"; +import ParkingAccessCargo from "../Questions/bike/ParkingAccessCargo"; +import ParkingCapacityCargo from "../Questions/bike/ParkingCapacityCargo"; export default class BikeParkings extends LayerDefinition { + private readonly accessCargoDesignated = new Tag("cargo_bike", "designated"); + constructor() { super(); this.name = Translations.t.cyclofix.parking.name; @@ -28,14 +31,9 @@ export default class BikeParkings extends LayerDefinition { new ImageCarouselWithUploadConstructor(), //new ParkingOperator(), new ParkingType(), - new TagRenderingOptions({ - question: "How many bicycles fit in this bicycle parking?", - freeform: { - key: "capacity", - renderTemplate: "Place for {capacity} bikes", - template: "$nat$ bikes fit in here" - } - }) + new ParkingCapacity(), + new ParkingAccessCargo(), + new ParkingCapacityCargo().OnlyShowIf(this.accessCargoDesignated) ]; this.wayHandling = LayerDefinition.WAYHANDLING_CENTER_AND_WAY; diff --git a/Customizations/Layers/BikeShops.ts b/Customizations/Layers/BikeShops.ts index 6cd25d9..9912981 100644 --- a/Customizations/Layers/BikeShops.ts +++ b/Customizations/Layers/BikeShops.ts @@ -1,6 +1,6 @@ import { LayerDefinition } from "../LayerDefinition"; import Translations from "../../UI/i18n/Translations"; -import {And, Tag} from "../../Logic/TagsFilter"; +import {And, Tag, Or} from "../../Logic/TagsFilter"; import FixedText from "../Questions/FixedText"; import { ImageCarouselWithUploadConstructor } from "../../UI/Image/ImageCarouselWithUpload"; import ShopRetail from "../Questions/bike/ShopRetail"; @@ -12,6 +12,7 @@ import ShopName from "../Questions/bike/ShopName"; import ShopSecondHand from "../Questions/bike/ShopSecondHand"; import { TagRenderingOptions } from "../TagRendering"; import {PhoneNumberQuestion} from "../Questions/PhoneNumberQuestion"; +import Website from "../Questions/Website"; export default class BikeShops extends LayerDefinition { @@ -52,6 +53,7 @@ export default class BikeShops extends LayerDefinition { new ImageCarouselWithUploadConstructor(), new ShopName(), new PhoneNumberQuestion("{name}"), + new Website("{name}"), new ShopRetail(), new ShopRental(), new ShopRepair(), diff --git a/Customizations/Layers/BikeStations.ts b/Customizations/Layers/BikeStations.ts index cac60ae..f7f1b22 100644 --- a/Customizations/Layers/BikeStations.ts +++ b/Customizations/Layers/BikeStations.ts @@ -12,6 +12,7 @@ import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWi import PumpOperational from "../Questions/bike/PumpOperational"; import PumpValves from "../Questions/bike/PumpValves"; import Translations from "../../UI/i18n/Translations"; +import { TagRenderingOptions } from "../TagRendering"; export default class BikeStations extends LayerDefinition { @@ -19,6 +20,7 @@ export default class BikeStations extends LayerDefinition { 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"), new Tag("service:bicycle:pump:operational_status", "")]); private readonly tools = new Tag("service:bicycle:tools", "yes"); + private readonly to = Translations.t.cyclofix.station constructor() { super(); @@ -36,7 +38,19 @@ export default class BikeStations extends LayerDefinition { this.minzoom = 13; this.style = this.generateStyleFunction(); - this.title = new FixedText(Translations.t.cyclofix.station.title) + this.title = new TagRenderingOptions({ + mappings: [ + { + k: new And([this.pump, this.tools]), + txt: this.to.titlePumpAndRepair + }, + { + k: new And([this.pump]), + txt: this.to.titlePump + }, + {k: null, txt: this.to.titleRepair}, + ] + }) this.wayHandling = LayerDefinition.WAYHANDLING_CENTER_AND_WAY this.elementsToShow = [ @@ -65,9 +79,9 @@ export default class BikeStations extends LayerDefinition { let iconName = "repair_station.svg"; if (hasTools && hasPump && isOperational) { iconName = "repair_station_pump.svg" - }else if(hasTools){ + } else if(hasTools) { iconName = "repair_station.svg" - }else if(hasPump){ + } else if(hasPump) { if (isOperational) { iconName = "pump.svg" } else { diff --git a/Customizations/Layouts/Cyclofix.ts b/Customizations/Layouts/Cyclofix.ts index 398e211..7aae50f 100644 --- a/Customizations/Layouts/Cyclofix.ts +++ b/Customizations/Layouts/Cyclofix.ts @@ -5,6 +5,7 @@ import BikeShops from "../Layers/BikeShops"; import Translations from "../../UI/i18n/Translations"; import {DrinkingWater} from "../Layers/DrinkingWater"; import Combine from "../../UI/Base/Combine"; +import BikeOtherShops from "../Layers/BikeOtherShops"; export default class Cyclofix extends Layout { @@ -13,7 +14,7 @@ export default class Cyclofix extends Layout { "cyclofix", ["en", "nl", "fr"], Translations.t.cyclofix.title, - [new BikeServices(), new BikeShops(), new DrinkingWater(), new BikeParkings()], + [new BikeServices(), new BikeShops(), new DrinkingWater(), new BikeParkings(), new BikeOtherShops()], 16, 50.8465573, 4.3516970, diff --git a/Customizations/Questions/Website.ts b/Customizations/Questions/Website.ts new file mode 100644 index 0000000..fff24cf --- /dev/null +++ b/Customizations/Questions/Website.ts @@ -0,0 +1,17 @@ +import {TagRenderingOptions} from "../TagRendering"; +import {UIElement} from "../../UI/UIElement"; +import Translations from "../../UI/i18n/Translations"; + + +export default class Website extends TagRenderingOptions { + constructor(category: string | UIElement) { + super({ + question: Translations.t.general.questions.websiteOf.Subs({category: category}), + freeform: { + renderTemplate: Translations.t.general.questions.websiteIs.Subs({category: category}), + template: "$phone$", + key: "phone" + } + }); + } +} diff --git a/Customizations/Questions/bike/ParkingAccessCargo.ts b/Customizations/Questions/bike/ParkingAccessCargo.ts new file mode 100644 index 0000000..186ebc9 --- /dev/null +++ b/Customizations/Questions/bike/ParkingAccessCargo.ts @@ -0,0 +1,20 @@ +import { TagRenderingOptions } from "../../TagRendering"; +import { Tag } from "../../../Logic/TagsFilter"; +import Translations from "../../../UI/i18n/Translations"; + + +export default class ParkingAccessCargo extends TagRenderingOptions { + constructor() { + const key = "cargo_bike" + const to = Translations.t.cyclofix.parking.access_cargo + super({ + priority: 15, + question: to.question.Render(), + mappings: [ + {k: new Tag(key, "yes"), txt: to.yes}, + {k: new Tag(key, "designated"), txt: to.designated}, + {k: new Tag(key, "no"), txt: to.no} + ] + }); + } +} diff --git a/Customizations/Questions/bike/ParkingCapacity.ts b/Customizations/Questions/bike/ParkingCapacity.ts new file mode 100644 index 0000000..99470da --- /dev/null +++ b/Customizations/Questions/bike/ParkingCapacity.ts @@ -0,0 +1,18 @@ +import Translations from "../../../UI/i18n/Translations"; +import { TagRenderingOptions } from "../../TagRendering"; + + +export default class ParkingCapacity extends TagRenderingOptions { + constructor() { + const to = Translations.t.cyclofix.parking.capacity + super({ + priority: 15, + question: to.question, + freeform: { + key: "capacity", + renderTemplate: to.render, + template: to.template + } + }); + } +} diff --git a/Customizations/Questions/bike/ParkingCapacityCargo.ts b/Customizations/Questions/bike/ParkingCapacityCargo.ts new file mode 100644 index 0000000..ad6d923 --- /dev/null +++ b/Customizations/Questions/bike/ParkingCapacityCargo.ts @@ -0,0 +1,19 @@ +import Translations from "../../../UI/i18n/Translations"; +import { TagRenderingOptions } from "../../TagRendering"; +import Combine from "../../../UI/Base/Combine"; + + +export default class ParkingCapacityCargo extends TagRenderingOptions { + constructor() { + const to = Translations.t.cyclofix.parking.capacity_cargo + super({ + priority: 10, + question: to.question, + freeform: { + key: "capacity:cargo_bike", + renderTemplate: to.render, + template: to.template + } + }); + } +} diff --git a/Customizations/Questions/bike/ParkingCovered.ts b/Customizations/Questions/bike/ParkingCovered.ts index e69de29..dff25d8 100644 --- a/Customizations/Questions/bike/ParkingCovered.ts +++ b/Customizations/Questions/bike/ParkingCovered.ts @@ -0,0 +1,19 @@ +import { TagRenderingOptions } from "../../TagRendering"; +import { Tag } from "../../../Logic/TagsFilter"; +import Translations from "../../../UI/i18n/Translations"; + + +export default class ParkingCovered extends TagRenderingOptions { + constructor() { + const key = 'covered' + const to = Translations.t.cyclofix.parking.covered + super({ + priority: 15, + question: to.question.Render(), + mappings: [ + {k: new Tag(key, "yes"), txt: to.yes}, + {k: new Tag(key, "no"), txt: to.no} + ] + }); + } +} diff --git a/Logic/Overpass.ts b/Logic/Overpass.ts index f9eafaf..b3f1d52 100644 --- a/Logic/Overpass.ts +++ b/Logic/Overpass.ts @@ -1,37 +1,34 @@ import {TagsFilter} from "./TagsFilter"; import * as OsmToGeoJson from "osmtogeojson"; import * as $ from "jquery"; -import {Basemap} from "./Basemap"; -import {UIEventSource} from "../UI/UIEventSource"; + /** * Interfaces overpass to get all the latest data */ export class Overpass { - - - private _filter: TagsFilter; - public static testUrl: string = null; + private _filter: TagsFilter + public static testUrl: string = null constructor(filter: TagsFilter) { - this._filter = filter; + this._filter = filter } - private buildQuery(bbox: string): string { - const filters = this._filter.asOverpass(); - let filter = ""; + public buildQuery(bbox: string): string { + const filters = this._filter.asOverpass() + console.log(filters) + let filter = "" for (const filterOr of filters) { - filter += 'nwr' + filterOr + ';'; + filter += 'nwr' + filterOr + ';' } const query = - '[out:json][timeout:25]' + bbox + ';(' + filter + ');out body;>;out skel qt;'; - console.log(query); - return "https://overpass-api.de/api/interpreter?data=" + encodeURIComponent(query); + '[out:json][timeout:25]' + bbox + ';(' + filter + ');out body;>;out skel qt;' + console.log(query) + return "https://overpass-api.de/api/interpreter?data=" + encodeURIComponent(query) } - queryGeoJson(bbox: string, continuation: ((any) => void), onFail: ((reason) => void)): void { - let query = this.buildQuery(bbox); + let query = this.buildQuery(bbox) if(Overpass.testUrl !== null){ console.log("Using testing URL") @@ -53,9 +50,5 @@ export class Overpass { const geojson = OsmToGeoJson.default(json); continuation(geojson); }).fail(onFail) - - ; } - - -} \ No newline at end of file +} diff --git a/Logic/TagsFilter.ts b/Logic/TagsFilter.ts index aaeaf12..021fdd8 100644 --- a/Logic/TagsFilter.ts +++ b/Logic/TagsFilter.ts @@ -51,17 +51,23 @@ export class Regex extends TagsFilter { substituteValues(tags: any) : TagsFilter{ throw "Substituting values is not supported on regex tags" } - } -export class Tag extends TagsFilter { - public key: string; - public value: string; - constructor(key: string, value: string) { +export class Tag extends TagsFilter { + public key: string + public value: string + public invertValue: boolean + + constructor(key: string, value: string, invertValue = false) { super() - this.key = key; - this.value = value; + this.key = key + this.value = value + this.invertValue = invertValue + + if (value === "*" && invertValue) { + throw new Error("Invalid combination: invertValue && value == *") + } } matches(tags: { k: string; v: string }[]): boolean { @@ -69,21 +75,22 @@ export class Tag extends TagsFilter { if (tag.k === this.key) { if (tag.v === "") { // This tag has been removed - return this.value === ""; + return this.value === "" } if (this.value === "*") { // Any is allowed return true; } - return this.value === tag.v; + return this.value === tag.v !== this.invertValue } } - if(this.value === ""){ - return true; + + if (this.value === "") { + return true } - return false; + return this.invertValue } asOverpass(): string[] { @@ -94,17 +101,17 @@ export class Tag extends TagsFilter { // NOT having this key return ['[!"' + this.key + '"]']; } - return ['["' + this.key + '"="' + this.value + '"]']; + const compareOperator = this.invertValue ? '!=' : '=' + return ['["' + this.key + '"' + compareOperator + '"' + this.value + '"]']; } substituteValues(tags: any) { return new Tag(this.key, TagUtils.ApplyTemplate(this.value, tags)); } - } -export class Or extends TagsFilter { +export class Or extends TagsFilter { public or: TagsFilter[] constructor(or: TagsFilter[]) { @@ -112,9 +119,7 @@ export class Or extends TagsFilter { this.or = or; } - matches(tags: { k: string; v: string }[]): boolean { - for (const tagsFilter of this.or) { if (tagsFilter.matches(tags)) { return true; @@ -125,7 +130,6 @@ export class Or extends TagsFilter { } asOverpass(): string[] { - const choices = []; for (const tagsFilter of this.or) { const subChoices = tagsFilter.asOverpass(); @@ -143,11 +147,10 @@ export class Or extends TagsFilter { } return new Or(newChoices); } - } -export class And extends TagsFilter { +export class And extends TagsFilter { public and: TagsFilter[] constructor(and: TagsFilter[]) { @@ -156,7 +159,6 @@ export class And extends TagsFilter { } matches(tags: { k: string; v: string }[]): boolean { - for (const tagsFilter of this.and) { if (!tagsFilter.matches(tags)) { return false; @@ -175,8 +177,7 @@ export class And extends TagsFilter { } asOverpass(): string[] { - - var allChoices = null; + var allChoices: string[] = null; for (const andElement of this.and) { var andElementFilter = andElement.asOverpass(); @@ -185,10 +186,10 @@ export class And extends TagsFilter { continue; } - var newChoices = [] + var newChoices: string[] = [] for (var choice of allChoices) { newChoices.push( - this.combine(choice, andElementFilter) + ...this.combine(choice, andElementFilter) ) } allChoices = newChoices; @@ -205,6 +206,7 @@ export class And extends TagsFilter { } } + export class Not extends TagsFilter{ private not: TagsFilter; @@ -224,12 +226,10 @@ export class Not extends TagsFilter{ substituteValues(tags: any): TagsFilter { return new Not(this.not.substituteValues(tags)); } - } export class TagUtils { - static proprtiesToKV(properties: any): { k: string, v: string }[] { const result = []; for (const k in properties) { @@ -246,5 +246,4 @@ export class TagUtils { } return template; } - -} \ No newline at end of file +} diff --git a/UI/Input/InputElementWrapper.ts b/UI/Input/InputElementWrapper.ts index baf6b07..60f7416 100644 --- a/UI/Input/InputElementWrapper.ts +++ b/UI/Input/InputElementWrapper.ts @@ -2,6 +2,7 @@ import {InputElement} from "./InputElement"; import {UIEventSource} from "../UIEventSource"; import {UIElement} from "../UIElement"; import {FixedUiElement} from "../Base/FixedUiElement"; +import Translations from "../i18n/Translations"; export class InputElementWrapper extends InputElement{ @@ -16,9 +17,11 @@ export class InputElementWrapper extends InputElement{ ) { super(undefined); - this.pre = typeof(pre) === 'string' ? new FixedUiElement(pre) : pre + // this.pre = typeof(pre) === 'string' ? new FixedUiElement(pre) : pre + this.pre = Translations.W(pre) this.input = input; - this.post =typeof(post) === 'string' ? new FixedUiElement(post) : post + // this.post =typeof(post) === 'string' ? new FixedUiElement(post) : post + this.post = Translations.W(post) } diff --git a/UI/i18n/Translations.ts b/UI/i18n/Translations.ts index fca9cb4..23d305d 100644 --- a/UI/i18n/Translations.ts +++ b/UI/i18n/Translations.ts @@ -33,13 +33,13 @@ export default class Translations { type: { render: new T({ en: 'This is a bicycle parking of the type: {bicycle_parking}', - nl: 'Dit is een fietsenparking van het type: {bicycle_parking}', + nl: 'Dit is een fietsparking van het type: {bicycle_parking}', fr: 'TODO: fr' }), template: new T({en: 'Some other type: $$$', nl: 'Een ander type: $$$', fr: 'TODO: fr'}), question: new T({ en: 'What is the type of this bicycle parking?', - nl: 'Van welk type is deze fietsenparking?', + nl: 'Van welk type is deze fietsparking?', fr: 'TODO: fr' }), eg: new T({en: ", for example", nl: ", bijvoorbeeld"}), @@ -50,17 +50,16 @@ export default class Translations { rack: new T({en: 'Rack', nl: 'Rek', fr: 'TODO: fr'}), "two-tier": new T({en: 'Two-tiered', nl: 'Dubbel (twee verdiepingen)', fr: 'TODO: fr'}), }, - operator: { render: new T({ en: 'This bike parking is operated by {operator}', - nl: 'Deze fietsenparking wordt beheerd door {operator}', + nl: 'Deze fietsparking wordt beheerd door {operator}', fr: 'TODO: fr' }), template: new T({en: 'A different operator: $$$', nl: 'Een andere beheerder: $$$', fr: 'TODO: fr'}), question: new T({ - en: 'Who operates this bike station (name of university, shop, city...)?', - nl: 'Wie beheert deze fietsenparking (naam universiteit, winkel, stad...)?', + en: 'Who operates this bike parking (name of university, shop, city...)?', + nl: 'Wie beheert deze fietsparking (naam universiteit, winkel, stad...)?', fr: 'TODO: fr' }), private: new T({ @@ -68,15 +67,90 @@ export default class Translations { nl: 'Wordt beheerd door een privépersoon', fr: 'TODO: fr' }), + }, + covered: { + question: new T({ + en: 'Is this parking covered? Also select "covered" for indoor parkings.', + nl: 'Is deze parking overdekt? Selecteer ook "overdekt" voor fietsparkings binnen een gebouw.', + fr: 'TODO: fr' + }), + yes: new T({ + en: 'This parking is covered (it has a roof)', + nl: 'Deze parking is overdekt (er is een afdak)', + fr: 'TODO: fr' + }), + no: new T({ + en: 'This parking is not covered', + nl: 'Deze parking is niet overdekt', + fr: 'TODO: fr' + }) + }, + capacity: { + question: new T({ + en: "How many bicycles fit in this bicycle parking (including possible cargo bicycles)?", + nl: "Voor hoeveel fietsen is er bij deze fietsparking plaats (inclusief potentiëel bakfietsen)?", + fr: "TODO: fr" + }), + template: new T({ + en: "This parking fits $nat$ bikes", + nl: "Deze parking heeft plaats voor $nat$ fietsen", + fr: "TODO: fr" + }), + render: new T({ + en: "Place for {capacity} bikes (in total)", + nl: "Plaats voor {capacity} fietsen (in totaal)", + fr: "TODO: fr" + }), + }, + capacity_cargo: { + question: new T({ + en: "How many cargo bicycles fit in this bicycle parking?", + nl: "Voor hoeveel bakfietsen heeft deze fietsparking plaats?", + fr: "TODO: fr" + }), + template: new T({ + en: "This parking fits $nat$ cargo bikes", + nl: "Deze parking heeft plaats voor $nat$ fietsen", + fr: "TODO: fr" + }), + render: new T({ + en: "Place for {capacity:cargo_bike} cargo bikes", + nl: "Plaats voor {capacity:cargo_bike} bakfietsen", + fr: "TODO: fr" + }), + }, + access_cargo: { + question: new T({ + en: "Does this bicycle parking have spots for cargo bikes?", + nl: "Heeft deze fietsparking plaats voor bakfietsen?", + fr: "TODO: fr" + }), + yes: new T({ + en: "This parking has room for cargo bikes", + nl: "Deze parking is overdekt (er is een afdak)", + fr: "TODO: fr" + }), + designated: new T({ + en: "This parking has designated (official) spots for cargo bikes.", + nl: "Deze parking is overdekt (er is een afdak)", + fr: "TODO: fr" + }), + no: new T({ + en: "You're not allowed to park cargo bikes", + nl: "Je mag hier geen bakfietsen parkeren", + fr: "TODO: fr" + }) } }, station: { name: new T({ en: 'bike station (repair, pump or both)', - nl: 'fietsstation (herstel, pomp of allebei)', + nl: 'fietspunt (herstel, pomp of allebei)', fr: 'TODO: fr' }), - title: new T({en: 'Bike station', nl: 'Fietsstation', fr: 'TODO: fr'}), + titlePump: new T({en: 'Bike pump', nl: 'Fietspomp', fr: 'TODO: fr'}), + titleRepair: new T({en: 'Bike repair station', nl: 'Herstelpunt', fr: 'TODO: fr'}), + titlePumpAndRepair: new T({en: 'Bike station (pump & repair)', nl: 'Herstelpunt met pomp', fr: 'TODO: fr'}), manometer: { question: new T({ en: 'Does the pump have a pressure indicator or manometer?', @@ -144,8 +218,8 @@ export default class Translations { }, chain: { question: new T({ - en: 'Does this bike station have a special tool to repair your bike chain?', - nl: 'Heeft dit fietsstation een speciale reparatieset voor je ketting?', + en: 'Does this bike repair station have a special tool to repair your bike chain?', + nl: 'Heeft dit herstelpunt een speciale reparatieset voor je ketting?', fr: 'TODO: fr' }), yes: new T({ @@ -162,7 +236,7 @@ export default class Translations { operator: { render: new T({ en: 'This bike station is operated by {operator}', - nl: 'Dit fietsstation wordt beheerd door {operator}', + nl: 'Dit fietspunt wordt beheerd door {operator}', fr: 'TODO: fr' }), template: new T({en: 'A different operator: $$$', nl: 'Een andere beheerder: $$$', fr: 'TODO: fr'}), @@ -180,7 +254,7 @@ export default class Translations { services: { question: new T({ en: 'Which services are available at this bike station?', - nl: 'Welke functies biedt dit fietsstation?', + nl: 'Welke functies biedt dit fietspunt?', fr: 'TODO: fr' }), pump: new T({ @@ -203,7 +277,7 @@ export default class Translations { stand: { question: new T({ en: 'Does this bike station have a hook to suspend your bike with or a stand to elevate it?', - nl: 'Heeft dit fietsstation een haak of standaard om je fiets op te hangen/zetten?', + nl: 'Heeft dit herstelpunt een haak of standaard om je fiets op te hangen/zetten?', fr: 'TODO: fr' }), yes: new T({en: 'There is a hook or stand', nl: 'Er is een haak of standaard', fr: 'TODO: fr'}), @@ -211,17 +285,15 @@ export default class Translations { } }, shop: { - name: new T({en: 'bike shop', nl: 'fietswinkel', fr: 'TODO: fr'}), + name: new T({en: 'bike repair/shop', nl: 'fietszaak', fr: 'TODO: fr'}), - title: new T({en: 'Bike shop', nl: 'Fietszaak', fr: 'TODO: fr'}), + title: new T({en: 'Bike repair/shop', nl: 'Fietszaak', fr: 'TODO: fr'}), titleRepair: new T({en: 'Bike repair', nl: 'Fietsenmaker', fr: 'TODO: fr'}), - titleShop: new T({en: 'Bike repair/shop', nl: 'Fietswinkel', fr: 'TODO: fr'}), + titleShop: new T({en: 'Bike shop', nl: 'Fietswinkel', fr: 'TODO: fr'}), - titleNamed: new T({en: 'Bike repair/shop', nl: 'Fietszaak {name}', fr: 'TODO: fr'}), - titleRepairNamed: new T({en: 'Bike shop', nl: 'Fietsenmaker {name}', fr: 'TODO: fr'}), - titleShopNamed: new T({en: 'Bike repair/shop', nl: 'Fietswinkel {name}', fr: 'TODO: fr'}), - - + titleNamed: new T({en: 'Bike repair/shop {name}', nl: 'Fietszaak {name}', fr: 'TODO: fr'}), + titleRepairNamed: new T({en: 'Bike repair {name}', nl: 'Fietsenmaker {name}', fr: 'TODO: fr'}), + titleShopNamed: new T({en: 'Bike shop {name}', nl: 'Fietswinkel {name}', fr: 'TODO: fr'}), retail: { question: new T({ @@ -310,6 +382,17 @@ export default class Translations { }), } }, + nonBikeShop: { + name: new T({en: 'shop that sells/repairs bikes', nl: 'winkel die fietsen verkoopt/herstelt', fr: 'TODO: fr'}), + + title: new T({en: 'Shop that sells/repairs bikes', nl: 'Winkel die fietsen verkoopt/herstelt', fr: 'TODO: fr'}), + titleRepair: new T({en: 'Shop that repairs bikes', nl: 'Winkel die fietsen herstelt', fr: 'TODO: fr'}), + titleShop: new T({en: 'Shop that sells bikes', nl: 'Winkel die fietsen verkoopt', fr: 'TODO: fr'}), + + titleNamed: new T({en: '{name} (sells/repairs bikes)', nl: '{name} (verkoopt/herstelt fietsen)', fr: 'TODO: fr'}), + titleRepairNamed: new T({en: '{name} (repairs bikes)', nl: '{name} (herstelt fietsen)', fr: 'TODO: fr'}), + titleShopNamed: new T({en: '{name} (sells bikes)', nl: '{name} (verkoopt fietsen)', fr: 'TODO: fr'}), + }, drinking_water: { title: new T({ en: 'Drinking water', @@ -502,8 +585,15 @@ export default class Translations { phoneNumberIs: new T({ en: "The phone number of this {category} is {phone}", nl: "Het telefoonnummer van {category} is {phone}" + }), + websiteOf: new T({ + en: "What is the website of {category}?", + nl: "Wat is de website van {category}?" + }), + websiteIs: new T({ + en: "Website: {website}", + nl: "Website: {website}" }) - } } } diff --git a/assets/bike/non_bike_repair_shop.svg b/assets/bike/non_bike_repair_shop.svg new file mode 100644 index 0000000..6305908 --- /dev/null +++ b/assets/bike/non_bike_repair_shop.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/bike/non_bike_shop.svg b/assets/bike/non_bike_shop.svg new file mode 100644 index 0000000..6d0bd50 --- /dev/null +++ b/assets/bike/non_bike_shop.svg @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/bike/staples-annotated.png b/assets/bike/staples-annotated.png new file mode 100644 index 0000000..a558083 Binary files /dev/null and b/assets/bike/staples-annotated.png differ diff --git a/index.css b/index.css index f3aba3c..d61fef0 100644 --- a/index.css +++ b/index.css @@ -980,6 +980,10 @@ form { font-weight: bold; } +.question-text img { + max-width: 100%; +} + .question-subtext{ font-size: medium; font-weight: normal; diff --git a/package.json b/package.json index 24a4144..5dfe76d 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ ] }, "scripts": { - "start": "parcel *.html UI/** Logic/** assets/**/* vendor/* vendor/*/*", + "start": "parcel *.html UI/** Logic/** assets/**/* assets/* vendor/* vendor/*/*", "generate": "ts-node createLayouts.ts", "build": "rm -rf dist/ && parcel build --public-url ./ *.html assets/* assets/**/* vendor/* vendor/*/*", "clean": "./clean.sh", diff --git a/static/staples-annotated.xcf b/static/staples-annotated.xcf new file mode 100644 index 0000000..b5dd783 Binary files /dev/null and b/static/staples-annotated.xcf differ diff --git a/test.ts b/test.ts index e69de29..a2530e4 100644 --- a/test.ts +++ b/test.ts @@ -0,0 +1,36 @@ +import { And, Tag, Or } from "./Logic/TagsFilter"; +import { Overpass } from "./Logic/Overpass"; + + +function anyValueExcept(key: string, exceptValue: string) { + return new And([ + new Tag(key, "*"), + new Tag(key, exceptValue, true) + ]) +} + +const sellsBikes = new Tag("service:bicycle:retail", "yes") +const repairsBikes = anyValueExcept("service:bicycle:repair", "no") +const rentsBikes = new Tag("service:bicycle:rental", "yes") +const hasPump = new Tag("service:bicycle:pump", "yes") +const hasDiy = new Tag("service:bicycle:diy", "yes") +const sellsSecondHand = anyValueExcept("service:bicycle:repair", "no") +const hasBikeServices = new Or([ + sellsBikes, + repairsBikes, + rentsBikes, + hasPump, + hasDiy, + sellsSecondHand +]) + +const overpassFilter = new And([ + new Tag("shop", "bicycle", true), + hasBikeServices +]) + +const overpass = new Overpass(overpassFilter) + +// console.log(overpass.buildQuery('bbox:51.12246976163816,3.1045767593383795,51.289518504257174,3.2848313522338866')) + +console.log(overpassFilter.asOverpass())