Bug fixes with preprocessing; add streetwidth map

This commit is contained in:
Pieter Vander Vennet 2020-07-17 17:21:07 +02:00
parent c5b9e66bd2
commit 636bad97b3
15 changed files with 13279 additions and 41 deletions

View file

@ -8,6 +8,8 @@ import { WalkByBrussels } from "./Layouts/WalkByBrussels";
import { All } from "./Layouts/All"; import { All } from "./Layouts/All";
import { Layout } from "./Layout"; import { Layout } from "./Layout";
import {MetaMap} from "./Layouts/MetaMap"; import {MetaMap} from "./Layouts/MetaMap";
import {Widths} from "./Layers/Widths";
import {StreetWidth} from "./Layouts/StreetWidth";
export class AllKnownLayouts { export class AllKnownLayouts {
public static allSets: any = AllKnownLayouts.AllLayouts(); public static allSets: any = AllKnownLayouts.AllLayouts();
@ -21,6 +23,7 @@ export class AllKnownLayouts {
new Bookcases(), new Bookcases(),
new WalkByBrussels(), new WalkByBrussels(),
new MetaMap(), new MetaMap(),
new StreetWidth(),
all all
/*new Toilets(), /*new Toilets(),
new Statues(), new Statues(),

View file

@ -56,7 +56,16 @@ export class LayerDefinition {
*/ */
elementsToShow: TagDependantUIElementConstructor[]; elementsToShow: TagDependantUIElementConstructor[];
style: (tags: any) => { color: string, icon: any }; /**
* A simple styling for the geojson element
* color is the color for areas and ways
* icon is the Leaflet icon
* Note that this is passed entirely to leaflet, so other leaflet attributes work too
*/
style: (tags: any) => {
color: string,
icon: any ,
};
/** /**
* If an object of the next layer is contained for this many percent in this feature, it is eaten and not shown * If an object of the next layer is contained for this many percent in this feature, it is eaten and not shown

View file

@ -6,7 +6,7 @@ import FixedText from "../Questions/FixedText";
import {TagRenderingOptions} from "../TagRendering"; import {TagRenderingOptions} from "../TagRendering";
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload"; import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
export class DrinkingWaterLayer extends LayerDefinition { export class DrinkingWater extends LayerDefinition {
constructor() { constructor() {
super(); super();
@ -31,7 +31,9 @@ export class DrinkingWaterLayer extends LayerDefinition {
this.elementsToShow = [ this.elementsToShow = [
new OperatorTag(), new OperatorTag(),
]; ];
this.elementsToShow = [new ImageCarouselWithUploadConstructor(), new TagRenderingOptions({ this.elementsToShow = [
new ImageCarouselWithUploadConstructor(),
new TagRenderingOptions({
question: "How easy is it to fill water bottles?", question: "How easy is it to fill water bottles?",
mappings: [ mappings: [
{ k: new Tag("bottle", "yes"), txt: "It is easy to refill water bottles" }, { k: new Tag("bottle", "yes"), txt: "It is easy to refill water bottles" },

View file

@ -52,19 +52,13 @@ export class GrbToFix extends LayerDefinition {
question: "Wat is het huisnummer?", question: "Wat is het huisnummer?",
tagsPreprocessor: tags => { tagsPreprocessor: tags => {
const newTags = {};
newTags["addr:housenumber"] = tags["addr:housenumber"]
newTags["addr:street"] = tags["addr:street"]
const telltale = "GRB thinks that this has number "; const telltale = "GRB thinks that this has number ";
const index = tags.fixme.indexOf(telltale); const index = tags.fixme.indexOf(telltale);
if (index >= 0) { if (index >= 0) {
const housenumber = tags.fixme.slice(index + telltale.length); const housenumber = tags.fixme.slice(index + telltale.length);
newTags["grb:housenumber:human"] = housenumber; tags["grb:housenumber:human"] = housenumber;
newTags["grb:housenumber"] = housenumber == "no number" ? "" : housenumber; tags["grb:housenumber"] = housenumber == "no number" ? "" : housenumber;
} }
return newTags;
}, },
freeform: { freeform: {
key: "addr:housenumber", key: "addr:housenumber",

View file

@ -0,0 +1,213 @@
import {LayerDefinition} from "../LayerDefinition";
import {And, Not, Or, Tag} from "../../Logic/TagsFilter";
import {TagRenderingOptions} from "../TagRendering";
import {UIEventSource} from "../../UI/UIEventSource";
import {Park} from "./Park";
export class Widths extends LayerDefinition {
private cyclistWidth: number;
private carWidth: number;
private readonly _bothSideParking = new Tag("parking:lane:both", "parallel");
private readonly _noSideParking = new Tag("parking:lane:both", "no_parking");
private readonly _leftSideParking =
new And([new Tag("parking:lane:left", "parallel"), new Tag("parking:lane:right", "no_parking")]);
private readonly _rightSideParking =
new And([new Tag("parking:lane:right", "parallel"), new Tag("parking:lane:left", "no_parking")]);
private readonly _oneSideParking = new Or([this._leftSideParking, this._rightSideParking]);
private readonly _carfree = new Or([new Tag("highway", "pedestrian"), new Tag("highway", "living_street")])
private readonly _notCarFree = new Not(this._carfree);
private calcProps(properties) {
let parkingStateKnown = true;
let parallelParkingCount = 0;
if (this._oneSideParking.matchesProperties(properties)) {
parallelParkingCount = 1;
} else if (this._bothSideParking.matchesProperties(properties)) {
parallelParkingCount = 2;
} else if (this._noSideParking.matchesProperties(properties)) {
parallelParkingCount = 0;
} else {
parkingStateKnown = false;
console.log("No parking data for ", properties.name, properties.id, properties)
}
let onewayCar = properties.oneway === "yes";
let onewayBike = properties["oneway:bicycle"] === "yes" ||
(onewayCar && properties["oneway:bicycle"] === undefined)
let carWidth = (onewayCar ? 1 : 2) * this.carWidth;
let cyclistWidth = (onewayBike ? 1 : 2) * this.cyclistWidth;
const width = parseFloat(properties["width:carriageway"]);
const targetWidth =
carWidth +
cyclistWidth +
parallelParkingCount * this.carWidth;
return {
parkingLanes: parallelParkingCount,
parkingStateKnown: parkingStateKnown,
width: width,
targetWidth: targetWidth,
onewayBike: onewayBike
}
}
constructor(carWidth: number,
cyclistWidth: number) {
super();
this.carWidth = carWidth;
this.cyclistWidth = cyclistWidth;
this.name = "widths";
this.overpassFilter = new Tag("width:carriageway", "*");
this.title = new TagRenderingOptions({
freeform: {
renderTemplate: "{name}",
template: "$$$",
key: "name"
}
})
const self = this;
this.style = (properties) => {
let c = "#0c0";
const props = self.calcProps(properties);
if (props.width < props.targetWidth) {
c = "#f00";
}
let dashArray = undefined;
if (!props.parkingStateKnown) {
c = "#f0f"
}
if (this._carfree.matchesProperties(properties)) {
c = "#aaa";
}
if (props.onewayBike) {
dashArray = [20, 8]
}
if (props.width > 15) {
c = "#ffb72b"
}
return {
icon: null,
color: c,
weight: 7,
dashArray: dashArray
}
}
this.elementsToShow = [
new TagRenderingOptions({
mappings: [
{
k: this._bothSideParking,
txt: "Auto's kunnen langs beide zijden parkeren.<br+>Dit gebruikt <b>" + (this.carWidth * 2) + "m</b><br/>"
},
{
k: this._oneSideParking,
txt: "Auto's kunnen langs één kant parkeren.<br/>Dit gebruikt <b>" + this.carWidth + "m</b><br/>"
},
{k: this._noSideParking, txt: "Auto's mogen hier niet parkeren"},
{k: null, txt: "Nog geen parkeerinformatie bekend"}
]
}).OnlyShowIf(this._notCarFree),
new TagRenderingOptions({
mappings: [
{
k: new Tag("oneway:bicycle", "yes"),
txt: "Eenrichtingsverkeer, óók voor fietsers. Dit gebruikt <b>" + (this.carWidth + this.cyclistWidth) + "m</b>"
},
{
k: new And([new Tag("oneway", "yes"), new Tag("oneway:bicycle", "no")]),
txt: "Tweerichtingverkeer voor fietsers, eenrichting voor auto's Dit gebruikt <b>" + (this.carWidth + 2 * this.cyclistWidth) + "m</b>"
},
{
k: new Tag("oneway", "yes"),
txt: "Eenrichtingsverkeer voor iedereen. Dit gebruikt <b>" + (this.carWidth + this.cyclistWidth) + "m</b>"
},
{
k: null,
txt: "Tweerichtingsverkeer voor iedereen. Dit gebruikt <b>" + (2 * this.carWidth + 2 * this.cyclistWidth) + "m</b>"
}
]
}).OnlyShowIf(this._notCarFree),
new TagRenderingOptions(
{
tagsPreprocessor: (tags) => {
const props = self.calcProps(tags);
tags.targetWidth = props.targetWidth;
console.log("PREP", tags)
},
freeform: {
key: "width:carriageway",
renderTemplate: "De totale nodige ruimte voor vlot en veilig verkeer is dus <b>{targetWidth}m</b>.",
template: "$$$",
}
}
).OnlyShowIf(this._notCarFree),
new TagRenderingOptions({
mappings: [
{k:new Tag("highway","living_street"),txt: "Dit is een woonerf"},
{k:new Tag("highway","pedestrian"),txt: "Hier mogen enkel voetgangers komen"}
]
}),
new TagRenderingOptions({
mappings: [
{
k: new Tag("sidewalk", "none"),
txt: "De afstand van huis tot huis is <b>{width:carriageway}m</b>"
},
{
k: new Tag("sidewalk", "left"),
txt: "De afstand van huis tot voetpad is <b>{width:carriageway}m</b>"
},
{
k: new Tag("sidewalk", "right"),
txt: "De afstand van huis tot voetpad is <b>{width:carriageway}m</b>"
},
{
k: new Tag("sidewalk", "both"),
txt: "De afstand van voetpad tot voetpad is <b>{width:carriageway}m</b>"
},
{
k: new Tag("sidewalk", ""),
txt: "De straatbreedte is <b>{width:carriageway}m</b>"
}
]
})
]
}
}

View file

@ -2,6 +2,7 @@ import {Layout} from "../Layout";
import BikeParkings from "../Layers/BikeParkings"; import BikeParkings from "../Layers/BikeParkings";
import BikeServices from "../Layers/BikeStations"; import BikeServices from "../Layers/BikeStations";
import {GhostBike} from "../Layers/GhostBike"; import {GhostBike} from "../Layers/GhostBike";
import {DrinkingWater, DrinkingWaterLayer} from "../Layers/DrinkingWater";
export default class Cyclofix extends Layout { export default class Cyclofix extends Layout {
@ -9,7 +10,7 @@ export default class Cyclofix extends Layout {
super( super(
"pomp", "pomp",
"Cyclofix bicycle infrastructure", "Cyclofix bicycle infrastructure",
[new GhostBike(), new BikeServices(), new BikeParkings()], [new GhostBike(), new BikeServices(), new BikeParkings(), new DrinkingWater()],
16, 16,
50.8465573, 50.8465573,
4.3516970, 4.3516970,

View file

@ -0,0 +1,34 @@
import {Layout} from "../Layout";
import * as Layer from "../Layers/Bookcases";
import {Widths} from "../Layers/Widths";
import {UIEventSource} from "../../UI/UIEventSource";
export class StreetWidth extends Layout{
constructor() {
super( "width",
"Straatbreedtes in Brugge",
[new Widths(
2.2,
1.5
)],
15,
51.20875,
3.22435,
"<h3>De straat is opgebruikt</h3>" +
"<p>Er is steeds meer druk op de openbare ruimte. Voetgangers, fietsers, steps, auto's, bussen, bestelwagens, buggies, cargobikes, ... willen allemaal hun deel van de openbare ruimte.</p>" +
"" +
"<p>In deze studie nemen we Brugge onder de loep en kijken we hoe breed elke straat is én hoe breed elke straat zou moeten zijn voor een veilig én vlot verkeer.</p>" +
"Verschillende ingrepen kunnen de stad teruggeven aan de inwoners en de stad leefbaarder en levendiger maken.<br/>" +
"Denk aan:" +
"<ul>" +
"<li>De autovrije zone's uitbreiden</li>" +
"<li>De binnenstad fietszone maken</li>" +
"<li>Het aantal woonerven uitbreiden</li>" +
"<li>Grotere auto's meer belasten - ze nemen immers meer parkeerruimte in.</li>" +
"</ul>",
"",
"");
}
}

View file

@ -1,5 +1,5 @@
import { Layout } from "../Layout"; import { Layout } from "../Layout";
import { DrinkingWaterLayer } from "../Layers/DrinkingWater"; import { DrinkingWater } from "../Layers/DrinkingWater";
import { NatureReserves } from "../Layers/NatureReserves"; import { NatureReserves } from "../Layers/NatureReserves";
import { Park } from "../Layers/Park"; import { Park } from "../Layers/Park";
@ -7,7 +7,7 @@ export class WalkByBrussels extends Layout {
constructor() { constructor() {
super("walkbybrussels", super("walkbybrussels",
"Drinking Water Spots", "Drinking Water Spots",
[new DrinkingWaterLayer(), new Park(), new NatureReserves()], [new DrinkingWater(), new Park(), new NatureReserves()],
10, 10,
50.8435, 50.8435,
4.3688, 4.3688,

View file

@ -4,6 +4,9 @@ import {TagRenderingOptions} from "../TagRendering";
export class WikipediaLink extends TagRenderingOptions { export class WikipediaLink extends TagRenderingOptions {
private static FixLink(value: string): string { private static FixLink(value: string): string {
if (value === undefined) {
return undefined;
}
// @ts-ignore // @ts-ignore
if (value.startsWith("https")) { if (value.startsWith("https")) {
return value; return value;
@ -20,6 +23,11 @@ export class WikipediaLink extends TagRenderingOptions {
static options = { static options = {
priority: 10, priority: 10,
// question: "Wat is het overeenstemmende wkipedia-artikel?", // question: "Wat is het overeenstemmende wkipedia-artikel?",
tagsPreprocessor: (tags) => {
if (tags.wikipedia !== undefined) {
tags.wikipedia = WikipediaLink.FixLink(tags.wikipedia);
}
},
freeform: { freeform: {
key: "wikipedia", key: "wikipedia",
template: "$$$", template: "$$$",
@ -28,19 +36,8 @@ export class WikipediaLink extends TagRenderingOptions {
"<a href='{wikipedia}' target='_blank'>" + "<a href='{wikipedia}' target='_blank'>" +
"<img width='64px' src='./assets/wikipedia.svg' alt='wikipedia'>" + "<img width='64px' src='./assets/wikipedia.svg' alt='wikipedia'>" +
"</a></span>", "</a></span>",
placeholder: "", placeholder: ""
tagsPreprocessor: (tags) => {
const newTags = {};
for (const k in tags) {
if (k === "wikipedia") {
newTags["wikipedia"] = WikipediaLink.FixLink(tags[k]);
} else {
newTags[k] = tags[k];
}
}
return newTags;
}
}, },
} }

View file

@ -80,15 +80,16 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
/** /**
* In some very rare cases, tags have to be rewritten before displaying * In some very rare cases, tags have to be rewritten before displaying
* This function adds this * This function can be used for that.
* This function is ran on a _copy_ of the original properties
*/ */
tagsPreprocessor?: ((tags: any) => any) tagsPreprocessor?: ((tags: any) => void)
}) { }) {
this.options = options; this.options = options;
} }
OnlyShowIf(dependencies): TagDependantUIElementConstructor { OnlyShowIf(tagsFilter: TagsFilter): TagDependantUIElementConstructor {
return new OnlyShowIfConstructor(dependencies, this); return new OnlyShowIfConstructor(tagsFilter, this);
} }
@ -187,7 +188,18 @@ class TagRendering extends UIElement implements TagDependantUIElement {
this._question = options.question; this._question = options.question;
this._priority = options.priority ?? 0; this._priority = options.priority ?? 0;
this._primer = options.primer ?? ""; this._primer = options.primer ?? "";
this._tagsPreprocessor = options.tagsPreprocessor; this._tagsPreprocessor = function (properties) {
if (options.tagsPreprocessor === undefined) {
return properties;
}
const newTags = {};
for (const k in properties) {
newTags[k] = properties[k];
}
options.tagsPreprocessor(newTags);
return newTags;
};
this._mapping = []; this._mapping = [];
this._renderMapping = []; this._renderMapping = [];
this._freeform = options.freeform; this._freeform = options.freeform;
@ -325,12 +337,7 @@ class TagRendering extends UIElement implements TagDependantUIElement {
} }
private ApplyTemplate(template: string): string { private ApplyTemplate(template: string): string {
let tags = this._source.data; const tags = this._tagsPreprocessor(this._source.data);
if (this._tagsPreprocessor !== undefined) {
tags = this._tagsPreprocessor(tags);
}
return TagUtils.ApplyTemplate(template, tags); return TagUtils.ApplyTemplate(template, tags);
} }

View file

@ -25,6 +25,10 @@ export class Regex extends TagsFilter {
} }
matches(tags: { k: string; v: string }[]): boolean { matches(tags: { k: string; v: string }[]): boolean {
if(!(tags instanceof Array)){
throw "You used 'matches' on something that is not a list. Did you mean to use 'matchesProperties'?"
}
for (const tag of tags) { for (const tag of tags) {
if (tag.k === this._k) { if (tag.k === this._k) {
if (tag.v === "") { if (tag.v === "") {
@ -201,6 +205,28 @@ export class And extends TagsFilter {
} }
} }
export class Not extends TagsFilter{
private not: TagsFilter;
constructor(not: TagsFilter) {
super();
this.not = not;
}
asOverpass(): string[] {
throw "Not supported yet"
}
matches(tags: { k: string; v: string }[]): boolean {
return !this.not.matches(tags);
}
substituteValues(tags: any): TagsFilter {
return new Not(this.not.substituteValues(tags));
}
}
export class TagUtils { export class TagUtils {

View file

@ -20,6 +20,7 @@ Furthermore, it shows images present in the `image` tag or, if a `wikidata` or `
- [Buurtnatuur.be](http://buurntatuur.be), developed for the Belgian [Green party](https://www.groen.be/). They also funded the initial development! - [Buurtnatuur.be](http://buurntatuur.be), developed for the Belgian [Green party](https://www.groen.be/). They also funded the initial development!
- [Cyclofix](https://pietervdvn.github.io/MapComplete/index.html?quests=pomp), further development on [Open Summer of Code](https://summerofcode.be/) funded by [Brussels Mobility](https://mobilite-mobiliteit.brussels/en) - [Cyclofix](https://pietervdvn.github.io/MapComplete/index.html?quests=pomp), further development on [Open Summer of Code](https://summerofcode.be/) funded by [Brussels Mobility](https://mobilite-mobiliteit.brussels/en)
- [Bookcases](https://pietervdvn.github.io/MapComplete/index.html?quests=bookcases#element) cause I like to collect them. - [Bookcases](https://pietervdvn.github.io/MapComplete/index.html?quests=bookcases#element) cause I like to collect them.
- [Map of Maps](https://pietervdvn.github.io/MapComplete/index.html?layout=metamap#element), after a tweet
Have a theme idea? Drop it in the [issues](https://github.com/pietervdvn/MapComplete/issues) Have a theme idea? Drop it in the [issues](https://github.com/pietervdvn/MapComplete/issues)

View file

@ -19,8 +19,8 @@ export class ImageCarouselWithUploadConstructor implements TagDependantUIElement
return 0; return 0;
} }
construct(tags: UIEventSource<any>, changes: Changes): TagDependantUIElement { construct(dependencies): TagDependantUIElement {
return new ImageCarouselWithUpload(tags, changes); return new ImageCarouselWithUpload(dependencies);
} }
} }

12951
assets/streetwidths.geojson Normal file

File diff suppressed because it is too large Load diff

View file

@ -41,7 +41,7 @@ if (location.hostname === "localhost" || location.hostname === "127.0.0.1") {
dryRun = true; dryRun = true;
// If you have a testfile somewhere, enable this to spoof overpass // If you have a testfile somewhere, enable this to spoof overpass
// This should be hosted independantly, e.g. with `cd assets; webfsd -p 8080` + a CORS plugin to disable cors rules // This should be hosted independantly, e.g. with `cd assets; webfsd -p 8080` + a CORS plugin to disable cors rules
Overpass.testUrl = null; // "http://127.0.0.1:8080/test.json"; Overpass.testUrl = "http://127.0.0.1:8080/streetwidths.geojson";
} }