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/MoreScreen.ts b/UI/MoreScreen.ts new file mode 100644 index 0000000..e69de29 diff --git a/UI/i18n/Translations.ts b/UI/i18n/Translations.ts index a442a8e..90d1ed4 100644 --- a/UI/i18n/Translations.ts +++ b/UI/i18n/Translations.ts @@ -19,12 +19,21 @@ export default class Translations { fr: 'Cyclofix - Une carte ouverte pour les cyclistes' }), description: new T({ - en: "On this map we want to collect data about the whereabouts of bicycle pumps and public racks in Brussels and everywhere else." + - "As a result, cyclists will be able to quickly find the nearest infrastructure for their needs.", - nl: "Op deze kaart willen we gegevens verzamelen over de locatie van fietspompen en openbare stelplaatsen in Brussel en overal ter wereld." + - "Hierdoor kunnen fietsers snel de dichtstbijzijnde infrastructuur vinden die voldoet aan hun behoeften.", - fr: "Sur cette carte, nous voulons collecter des données sur la localisation des pompes à vélo et des supports publics à Bruxelles." + - "Les cyclistes pourront ainsi trouver rapidement l'infrastructure la plus proche pour leurs besoins." + en: "The goal of this map is to present cyclists with an easy-to-use solution to find the appropriate infrastructure for their needs.

" + //this works in spoken language: ; think about the nearest bike repair station for example + "You can track your precise location (mobile only) and select layers that are relevant for you in the bottom left corner. " + + "You can also use this tool to add or edit pins (points of interest) to the map and provide more data by answering the questions.

" + + "All changes you make will automatically be saved in the global database of OpenStreetMap and can be freely re-used by others.

" + + "For more information about the cyclofix project, go to cyclofix.osm.be.", + nl: "Het doel van deze kaart is om fietsers een gebruiksvriendelijke oplossing te bieden voor het vinden van de juiste infrastructuur voor hun behoeften.

" + //; denk bijvoorbeeld aan de dichtstbijzijnde fietsherstelplaats. + "U kunt uw exacte locatie volgen (enkel mobiel) en in de linkerbenedenhoek categorieën selecteren die voor u relevant zijn. " + + "U kunt deze tool ook gebruiken om 'spelden' aan de kaart toe te voegen of te bewerken en meer gegevens te verstrekken door de vragen te beantwoorden.

" + + "Alle wijzigingen die u maakt worden automatisch opgeslagen in de wereldwijde database van OpenStreetMap en kunnen door anderen vrij worden hergebruikt.

" + + "Bekijk voor meer info over cyclofix ook cyclofix.osm.be.", + fr: "Le but de cette carte est de présenter aux cyclistes une solution facile à utiliser pour trouver l'infrastructure appropriée à leurs besoins.

" + //; pensez par exemple à la station de réparation de vélos la plus proche. + "Vous pouvez suivre votre localisation précise (mobile uniquement) et sélectionner les couches qui vous concernent dans le coin inférieur gauche. " + + "Vous pouvez également utiliser cet outil pour ajouter ou modifier des épingles (points d'intérêt) sur la carte et fournir plus de données en répondant aux questions.

" + + "Toutes les modifications que vous apportez seront automatiquement enregistrées dans la base de données mondiale d'OpenStreetMap et peuvent être librement réutilisées par d'autres.

" + + "Pour plus d'informations sur le projet cyclofix, rendez-vous sur cyclofix.osm.be." }), freeFormPlaceholder: new T({en: 'specify', nl: 'specifieer', fr: 'TODO: fr'}), parking: { @@ -33,13 +42,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: 'Ceci est un parking à vélo de type {bicycle_parking}' }), template: new T({en: 'Some other type: $$$', nl: 'Een ander type: $$$', fr: "D'autres types: $$$"}), 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: 'Quelle type de parking s\'agit il? ' }), eg: new T({en: ", for example", nl: ", bijvoorbeeld", fr: ",par example"}), @@ -50,11 +59,10 @@ export default class Translations { rack: new T({en: 'Rack', nl: 'Rek', fr: 'Râtelier'}), "two-tier": new T({en: 'Two-tiered', nl: 'Dubbel (twee verdiepingen)', fr: 'Superposé'}), }, - 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: 'Ce parking est opéré par {operator}' }), template: new T({en: 'A different operator: $$$', nl: 'Een andere beheerder: $$$', fr: 'TODO: fr'}), @@ -68,15 +76,95 @@ export default class Translations { nl: 'Wordt beheerd door een privépersoon', fr: 'Opéré par un tier privé' }), + }, + 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: 'station velo (réparation, pompe à vélo)' }), - title: new T({en: 'Bike station', nl: 'Fietsstation', fr: 'Station vélo'}), + // title: new T({en: 'Bike station', nl: 'Fietsstation', fr: 'Station vélo'}), Old, non-dynamic title + 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?', @@ -84,7 +172,11 @@ export default class Translations { fr: 'Est-ce que la pompe à un manomètre integré?' }), yes: new T({en: 'There is a manometer', nl: 'Er is een luchtdrukmeter', fr: 'Il y a un manomètre'}), - no: new T({en: 'There is no manometer', nl: 'Er is geen luchtdrukmeter', fr: 'Il n\'y a pas de manomètre'}), + no: new T({ + en: 'There is no manometer', + nl: 'Er is geen luchtdrukmeter', + fr: 'Il n\'y a pas de manomètre' + }), broken: new T({ en: 'There is manometer but it is broken', nl: 'Er is een luchtdrukmeter maar die is momenteel defect', @@ -144,8 +236,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: 'Est-ce que cette station vélo a un outils specifique pour réparer la chaîne du velo?' }), yes: new T({ @@ -162,7 +254,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: 'Cette station vélo est opéré par {operator}' }), template: new T({en: 'A different operator: $$$', nl: 'Een andere beheerder: $$$', fr: 'TODO: fr'}), @@ -180,7 +272,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: 'Quels services sont valables à cette station vélo?' }), pump: new T({ @@ -203,7 +295,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: 'Est-ce que cette station vélo à un crochet pour suspendre son velo ou une accroche pour l\'élevé?' }), yes: new T({en: 'There is a hook or stand', nl: 'Er is een haak of standaard', fr: 'Oui il y a un crochet ou une accroche'}), @@ -211,16 +303,23 @@ export default class Translations { } }, shop: { - name: new T({en: 'bike shop', nl: 'fietswinkel', fr: 'magasin de vélo'}), - - title: new T({en: 'Bike shop', nl: 'Fietszaak', fr: 'Magasin de vélo'}), - titleRepair: new T({en: 'Bike repair', nl: 'Fietsenmaker', fr: 'Réparateur de vélo'}), - titleShop: new T({en: 'Bike repair/shop', nl: 'Fietswinkel', fr: 'Magasin et réparateur de vélo'}), - - titleNamed: new T({en: 'Bike repair/shop', nl: 'Fietszaak {name}', fr: 'Magasin de vélo'}), - titleRepairNamed: new T({en: 'Bike shop', nl: 'Fietsenmaker {name}', fr: 'Réparateur de vélo'}), - titleShopNamed: new T({en: 'Bike repair/shop', nl: 'Fietswinkel {name}', fr: 'Magasin et réparateur de vélo'}), + name: new T({en: 'bike repair/shop', nl: 'fietszaak', fr: 'magasin ou réparateur de vélo'}), + title: new T({en: 'Bike repair/shop', nl: 'Fietszaak', fr: 'Magasin et réparateur de vélo'}), + titleRepair: new T({en: 'Bike repair', nl: 'Fietsenmaker', fr: 'Réparateur de vélo'}), + titleShop: new T({en: 'Bike shop', nl: 'Fietswinkel', fr: 'Magasin de vélo'}), + + titleNamed: new T({ + en: 'Bike repair/shop {name}', + nl: 'Fietszaak {name}', + fr: 'Magasin et réparateur de vélo {name}' + }), + titleRepairNamed: new T({ + en: 'Bike repair {name}', + nl: 'Fietsenmaker {name}', + fr: 'Réparateur de vélo {name}' + }), + titleShopNamed: new T({en: 'Bike shop {name}', nl: 'Fietswinkel {name}', fr: 'Magasin de vélo {name}'}), retail: { @@ -229,7 +328,11 @@ export default class Translations { nl: 'Verkoopt deze winkel fietsen?', fr: 'Est-ce que ce magasin vend des vélos?' }), - yes: new T({en: 'This shop sells bikes', nl: 'Deze winkel verkoopt fietsen', fr: 'Ce magasin vend des vélos'}), + yes: new T({ + en: 'This shop sells bikes', + nl: 'Deze winkel verkoopt fietsen', + fr: 'Ce magasin vend des vélos' + }), no: new T({ en: 'This shop doesn\'t sell bikes', nl: 'Deze winkel verkoopt geen fietsen', @@ -310,6 +413,33 @@ 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', @@ -526,6 +656,14 @@ 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}" }) }, @@ -553,8 +691,6 @@ export default class Translations { embedIntro: new T({ en: "

Embed on your website

Please, embed this map into your website. We encourage you to do it - you don't even have to ask permission. It is free, and always will be. The more people using this, the more valuable it becomes." }) - - } } } 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 e4bcdae..611a2c1 100644 --- a/index.css +++ b/index.css @@ -956,6 +956,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 3020614..8463906 100644 --- a/test.ts +++ b/test.ts @@ -1,14 +1,37 @@ -import {TabbedComponent} from "./UI/Base/TabbedComponent"; -import {FixedUiElement} from "./UI/Base/FixedUiElement"; -import {Bookcases} from "./Customizations/Layouts/Bookcases"; -import {ShareScreen} from "./UI/ShareScreen"; -import {UIEventSource} from "./UI/UIEventSource"; + +import { And, Tag, Or } from "./Logic/TagsFilter"; +import { Overpass } from "./Logic/Overpass"; -const layout = new Bookcases(); +function anyValueExcept(key: string, exceptValue: string) { + return new And([ + new Tag(key, "*"), + new Tag(key, exceptValue, true) + ]) +} -new ShareScreen(layout, new UIEventSource<{zoom: number, lat: number, lon: number}>({ - zoom: 16, - lat: 51.5, - lon:3.2 -})).AttachTo("maindiv") \ No newline at end of file +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())