Added cyclofix capacity cargo; covered; non-bike shop; pump dyn title

This commit is contained in:
Pieter Fiers 2020-07-29 13:16:21 +02:00
parent a7bb4a1fcc
commit 948ff74a8b
23 changed files with 624 additions and 91 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@ dist/*
node_modules
.cache/*
.idea/*
scratch

View File

View File

@ -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]
}
}
}
}
}

View File

@ -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;

View File

@ -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(),

View File

@ -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 {

View File

@ -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,

View File

@ -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"
}
});
}
}

View File

@ -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}
]
});
}
}

View File

@ -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
}
});
}
}

View File

@ -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
}
});
}
}

View File

@ -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}
]
});
}
}

View File

@ -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)
;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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<T> extends InputElement<T>{
@ -16,9 +17,11 @@ export class InputElementWrapper<T> extends InputElement<T>{
) {
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)
}

View File

@ -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 <a href='tel:{phone}' target='_blank'>{phone}</a>",
nl: "Het telefoonnummer van {category} is <a href='tel:{phone}' target='_blank'>{phone}</a>"
}),
websiteOf: new T({
en: "What is the website of {category}?",
nl: "Wat is de website van {category}?"
}),
websiteIs: new T({
en: "Website: <a href='{website}' target='_blank'>{website}</a>",
nl: "Website: <a href='{website}' target='_blank'>{website}</a>"
})
}
}
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,164 @@
<svg width="98" height="121" viewBox="0 0 98 121" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M53.0445 111.094C51.2614 114.981 45.7386 114.981 43.9555 111.094L13.2124 44.085C11.6928 40.7729 14.1129 37 17.7569 37L79.2431 37C82.8871 37 85.3072 40.7729 83.7876 44.085L53.0445 111.094Z" fill="#805793"/>
<circle cx="49" cy="49" r="49" fill="#805793"/>
<g filter="url(#filter0_d)">
<ellipse cx="20" cy="32.5" rx="7" ry="5.5" fill="white"/>
</g>
<g filter="url(#filter1_d)">
<ellipse cx="63.5" cy="32.5" rx="7.5" ry="5.5" fill="white"/>
</g>
<g filter="url(#filter2_d)">
<ellipse cx="78" cy="32.5" rx="7" ry="5.5" fill="white"/>
</g>
<g filter="url(#filter3_d)">
<ellipse cx="49" cy="32.5" rx="7" ry="5.5" fill="white"/>
</g>
<g filter="url(#filter4_d)">
<ellipse cx="34.5" cy="32.5" rx="7.5" ry="5.5" fill="white"/>
</g>
<path d="M19.5 24.5L30.3253 31.5H8.67468L19.5 24.5Z" fill="white"/>
<path d="M79 24.5L89.3253 31.5H67.6747L79 24.5Z" fill="white"/>
<rect x="19" y="20" width="60" height="14" fill="white"/>
<rect x="6" y="31" width="85" height="1" fill="#805793"/>
<g filter="url(#filter5_d)">
<rect x="21" y="38" width="4" height="34" fill="white"/>
</g>
<g filter="url(#filter6_d)">
<rect x="74" y="39" width="4" height="34" fill="white"/>
</g>
<g filter="url(#filter7_d)">
<rect x="16" y="72" width="67" height="5" fill="white"/>
</g>
<g filter="url(#filter8_d)">
<path d="M43.1579 63.8659H33.5872L38.8076 52.2202H44.028H47.7983H51.8586M51.8586 52.2202L50.4085 48.9355H52.4386H54.4688M51.8586 52.2202L52.4386 53.7132L53.5987 56.1021L56.789 63.8659M51.8586 52.2202L49.1034 56.6993L47.4502 59.3867M46.3481 61.1784L47.4502 59.3867M40.8377 55.5049L43.7379 62.0742L37.9375 48.9355L39.6776 52.5188M45.4781 62.6714L47.4502 59.3867" stroke="white" stroke-width="2"/>
<path d="M46.0937 63.3578C46.0937 64.25 45.395 64.9481 44.5635 64.9481C43.7321 64.9481 43.0334 64.25 43.0334 63.3578C43.0334 62.4657 43.7321 61.7676 44.5635 61.7676C45.395 61.7676 46.0937 62.4657 46.0937 63.3578Z" stroke="white"/>
<path d="M39.7932 62.7607C39.7932 66.359 36.9675 69.2258 33.5427 69.2258C30.1179 69.2258 27.2921 66.359 27.2921 62.7607C27.2921 59.1623 30.1179 56.2955 33.5427 56.2955C36.9675 56.2955 39.7932 59.1623 39.7932 62.7607Z" stroke="white" stroke-width="2"/>
<ellipse cx="60.279" cy="57.3748" rx="0.210172" ry="0.216394" fill="black" fill-opacity="0.49"/>
</g>
<g filter="url(#filter9_d)">
<path d="M43.1579 63.8659H33.5872L38.8076 52.2202H44.028H47.7983H51.8586M51.8586 52.2202L50.4085 48.9355H52.4386H54.4688M51.8586 52.2202L52.4386 53.7132L53.5987 56.1021L56.789 63.8659M51.8586 52.2202L49.1034 56.6993L47.4502 59.3867M46.3481 61.1784L47.4502 59.3867M40.8377 55.5049L43.7379 62.0742L37.9375 48.9355L39.6776 52.5188M45.4781 62.6714L47.4502 59.3867" stroke="white" stroke-width="2"/>
<path d="M46.0937 63.3578C46.0937 64.25 45.395 64.9481 44.5635 64.9481C43.7321 64.9481 43.0334 64.25 43.0334 63.3578C43.0334 62.4657 43.7321 61.7676 44.5635 61.7676C45.395 61.7676 46.0937 62.4657 46.0937 63.3578Z" stroke="white"/>
<path d="M39.7932 62.7607C39.7932 66.359 36.9675 69.2258 33.5427 69.2258C30.1179 69.2258 27.2921 66.359 27.2921 62.7607C27.2921 59.1623 30.1179 56.2955 33.5427 56.2955C36.9675 56.2955 39.7932 59.1623 39.7932 62.7607Z" stroke="white" stroke-width="2"/>
<ellipse cx="60.279" cy="57.3748" rx="0.210172" ry="0.216394" fill="black" fill-opacity="0.49"/>
</g>
<g filter="url(#filter10_d)">
<circle cx="56.5" cy="63.5" r="6.5" stroke="white" stroke-width="2"/>
</g>
<path d="M63.2749 56.3586C62.6089 56.7271 61.7919 56.2445 61.7916 55.4825L61.7903 51.7748C61.79 51.0004 62.6308 50.5198 63.2971 50.9134L66.5391 52.8283C67.2055 53.2219 67.1923 54.1913 66.5155 54.5658L63.2749 56.3586Z" fill="white"/>
<g filter="url(#filter11_d)">
<rect width="7.82366" height="6.20457" rx="1" transform="matrix(0.490888 -0.871223 0.870592 0.492006 61.4893 51.5931)" fill="white"/>
</g>
<path d="M66.0398 53.2049C62.045 51.8388 64.4607 45.5484 68.7455 48.7409" stroke="#805793" stroke-width="0.75"/>
<path d="M63.0112 49.4193L66.8764 51.3548M63.4944 48.4516L67.5528 50.4839" stroke="#805793" stroke-width="0.75"/>
<circle cx="63.5" cy="54.5" r="0.5" fill="#805793"/>
<defs>
<filter id="filter0_d" x="9" y="27" width="22" height="19" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
<filter id="filter1_d" x="52" y="27" width="23" height="19" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
<filter id="filter2_d" x="67" y="27" width="22" height="19" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
<filter id="filter3_d" x="38" y="27" width="22" height="19" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
<filter id="filter4_d" x="23" y="27" width="23" height="19" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
<filter id="filter5_d" x="17" y="38" width="12" height="42" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
<filter id="filter6_d" x="70" y="39" width="12" height="42" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
<filter id="filter7_d" x="12" y="72" width="75" height="13" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
<filter id="filter8_d" x="22.2921" y="47.9355" width="42.197" height="30.2904" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
<filter id="filter9_d" x="22.2921" y="47.9355" width="42.197" height="30.2904" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
<filter id="filter10_d" x="45" y="56" width="23" height="23" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
<filter id="filter11_d" x="57.8512" y="45.1395" width="16.5185" height="17.1438" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

View File

@ -980,6 +980,10 @@ form {
font-weight: bold;
}
.question-text img {
max-width: 100%;
}
.question-subtext{
font-size: medium;
font-weight: normal;

View File

@ -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",

Binary file not shown.

36
test.ts
View File

@ -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())