| 
									
										
										
										
											2020-10-19 12:08:42 +02:00
										 |  |  | import {GeoOperations} from "./GeoOperations"; | 
					
						
							|  |  |  | import CodeGrid from "./Web/CodeGrid"; | 
					
						
							|  |  |  | import State from "../State"; | 
					
						
							|  |  |  | import opening_hours from "opening_hours"; | 
					
						
							| 
									
										
										
										
											2020-10-30 00:56:46 +01:00
										 |  |  | import {And, Or, Tag} from "./Tags"; | 
					
						
							|  |  |  | import {Utils} from "../Utils"; | 
					
						
							| 
									
										
										
										
											2020-10-19 12:08:42 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class SimpleMetaTagger { | 
					
						
							| 
									
										
										
										
											2020-10-23 01:50:37 +02:00
										 |  |  |     private _f: (feature: any, index: number) => void; | 
					
						
							| 
									
										
										
										
											2020-10-19 12:08:42 +02:00
										 |  |  |     public readonly keys: string[]; | 
					
						
							|  |  |  |     public readonly doc: string; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-23 01:50:37 +02:00
										 |  |  |     constructor(keys: string[], doc: string, f: ((feature: any, index: number) => void)) { | 
					
						
							| 
									
										
										
										
											2020-10-19 12:08:42 +02:00
										 |  |  |         this.keys = keys; | 
					
						
							|  |  |  |         this.doc = doc; | 
					
						
							|  |  |  |         this._f = f; | 
					
						
							|  |  |  |         for (const key of keys) { | 
					
						
							|  |  |  |             if (!key.startsWith('_')) { | 
					
						
							|  |  |  |                 throw `Incorrect metakey ${key}: it should start with underscore (_)` | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     addMetaTags(features: any[]) { | 
					
						
							| 
									
										
										
										
											2020-10-23 01:50:37 +02:00
										 |  |  |         for (let i = 0; i < features.length; i++) { | 
					
						
							|  |  |  |             let feature = features[i]; | 
					
						
							|  |  |  |             this._f(feature, i); | 
					
						
							| 
									
										
										
										
											2020-10-19 12:08:42 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Metatagging adds various tags to the elements, e.g. lat, lon, surface area, ... | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * All metatags start with an underscore | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export default class MetaTagging { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-30 00:56:46 +01:00
										 |  |  |     private static latlon = new SimpleMetaTagger(["_lat", "_lon"], "The latitude and longitude of the point (or centerpoint in the case of a way/area)", | 
					
						
							|  |  |  |         (feature => { | 
					
						
							|  |  |  |             const centerPoint = GeoOperations.centerpoint(feature); | 
					
						
							|  |  |  |             const lat = centerPoint.geometry.coordinates[1]; | 
					
						
							|  |  |  |             const lon = centerPoint.geometry.coordinates[0]; | 
					
						
							|  |  |  |             feature.properties["_lat"] = "" + lat; | 
					
						
							|  |  |  |             feature.properties["_lon"] = "" + lon; | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     private static surfaceArea = new SimpleMetaTagger( | 
					
						
							|  |  |  |         ["_surface", "_surface:ha"], "The surface area of the feature, in square meters and in hectare. Not set on points and ways", | 
					
						
							|  |  |  |         (feature => { | 
					
						
							|  |  |  |             const sqMeters = GeoOperations.surfaceAreaInSqMeters(feature); | 
					
						
							|  |  |  |             feature.properties["_surface"] = "" + sqMeters; | 
					
						
							|  |  |  |             feature.properties["_surface:ha"] = "" + Math.floor(sqMeters / 1000) / 10; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     private static country = new SimpleMetaTagger( | 
					
						
							|  |  |  |         ["_country"], "The country code of the point", | 
					
						
							|  |  |  |         ((feature, index) => { | 
					
						
							|  |  |  |             const centerPoint = GeoOperations.centerpoint(feature); | 
					
						
							|  |  |  |             const lat = centerPoint.geometry.coordinates[1]; | 
					
						
							|  |  |  |             const lon = centerPoint.geometry.coordinates[0] | 
					
						
							|  |  |  |             // But the codegrid SHOULD be a number!
 | 
					
						
							|  |  |  |             CodeGrid.getCode(lat, lon, (error, code) => { | 
					
						
							|  |  |  |                 if (error === null) { | 
					
						
							|  |  |  |                     feature.properties["_country"] = code; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     // There is a huge performance issue: if there are ~1000 features receiving a ping at the same time, 
 | 
					
						
							|  |  |  |                     // The application hangs big time
 | 
					
						
							|  |  |  |                     // So we disable pinging all together
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 } else { | 
					
						
							|  |  |  |                     console.warn("Could not determine country for", feature.properties.id, error); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  |     private static isOpen = new SimpleMetaTagger( | 
					
						
							|  |  |  |         ["_isOpen", "_isOpen:description"], "If 'opening_hours' is present, it will add the current state of the feature (being 'yes' or 'no", | 
					
						
							|  |  |  |         (feature => { | 
					
						
							|  |  |  |             const tagsSource = State.state.allElements.addOrGetElement(feature); | 
					
						
							|  |  |  |             tagsSource.addCallback(tags => { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 if (tags["opening_hours"] !== undefined && tags["_country"] !== undefined) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     if (tags._isOpen !== undefined) { | 
					
						
							|  |  |  |                         // Already defined
 | 
					
						
							|  |  |  |                         return; | 
					
						
							| 
									
										
										
										
											2020-10-19 12:08:42 +02:00
										 |  |  |                     } | 
					
						
							| 
									
										
										
										
											2020-10-30 00:56:46 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |                     const oh = new opening_hours(tags["opening_hours"], { | 
					
						
							|  |  |  |                         lat: tags._lat, | 
					
						
							|  |  |  |                         lon: tags._lon, | 
					
						
							|  |  |  |                         address: { | 
					
						
							|  |  |  |                             country_code: tags._country | 
					
						
							| 
									
										
										
										
											2020-10-23 01:50:37 +02:00
										 |  |  |                         } | 
					
						
							| 
									
										
										
										
											2020-10-30 00:56:46 +01:00
										 |  |  |                     }, {tag_key: "opening_hours"}); | 
					
						
							| 
									
										
										
										
											2020-10-23 01:50:37 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-30 00:56:46 +01:00
										 |  |  |                     const updateTags = () => { | 
					
						
							|  |  |  |                         tags["_isOpen"] = oh.getState() ? "yes" : "no"; | 
					
						
							|  |  |  |                         const comment = oh.getComment(); | 
					
						
							|  |  |  |                         if (comment) { | 
					
						
							|  |  |  |                             tags["_isOpen:description"] = comment; | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                         const nextChange = oh.getNextChange() as Date; | 
					
						
							|  |  |  |                         if (nextChange !== undefined) { | 
					
						
							|  |  |  |                             window.setTimeout( | 
					
						
							|  |  |  |                                 updateTags, | 
					
						
							|  |  |  |                                 (nextChange.getTime() - (new Date()).getTime()) | 
					
						
							|  |  |  |                             ) | 
					
						
							| 
									
										
										
										
											2020-10-19 12:08:42 +02:00
										 |  |  |                         } | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2020-10-30 00:56:46 +01:00
										 |  |  |                     updateTags(); | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2020-10-19 12:08:42 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             }) | 
					
						
							| 
									
										
										
										
											2020-10-30 00:56:46 +01:00
										 |  |  |         }) | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public static carriageWayWidth = new SimpleMetaTagger( | 
					
						
							|  |  |  |         ["_width:needed","_width:needed:no_pedestrians", "_width:difference"], | 
					
						
							| 
									
										
										
										
											2020-11-02 12:38:04 +01:00
										 |  |  |         "Legacy for a specific project calculating the needed width for safe traffic on a road. Only activated if 'width:carriageway' is present", | 
					
						
							| 
									
										
										
										
											2020-10-30 00:56:46 +01:00
										 |  |  |         (feature: any, index: number) => { | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-02 12:38:04 +01:00
										 |  |  |             const properties = feature.properties; | 
					
						
							|  |  |  |             if(properties["width:carriageway"] === undefined){ | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |              | 
					
						
							| 
									
										
										
										
											2020-10-30 00:56:46 +01:00
										 |  |  |             const carWidth = 2; | 
					
						
							|  |  |  |             const cyclistWidth = 1.5; | 
					
						
							|  |  |  |             const pedestrianWidth = 0.75; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const _leftSideParking = | 
					
						
							|  |  |  |                 new And([new Tag("parking:lane:left", "parallel"), new Tag("parking:lane:right", "no_parking")]); | 
					
						
							|  |  |  |             const _rightSideParking = | 
					
						
							|  |  |  |                 new And([new Tag("parking:lane:right", "parallel"), new Tag("parking:lane:left", "no_parking")]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const _bothSideParking = new Tag("parking:lane:both", "parallel"); | 
					
						
							|  |  |  |             const _noSideParking = new Tag("parking:lane:both", "no_parking"); | 
					
						
							|  |  |  |             const _otherParkingMode = | 
					
						
							|  |  |  |                 new Or([ | 
					
						
							|  |  |  |                     new Tag("parking:lane:both", "perpendicular"), | 
					
						
							|  |  |  |                     new Tag("parking:lane:left", "perpendicular"), | 
					
						
							|  |  |  |                     new Tag("parking:lane:right", "perpendicular"), | 
					
						
							|  |  |  |                     new Tag("parking:lane:both", "diagonal"), | 
					
						
							|  |  |  |                     new Tag("parking:lane:left", "diagonal"), | 
					
						
							|  |  |  |                     new Tag("parking:lane:right", "diagonal"), | 
					
						
							|  |  |  |                 ]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const _sidewalkBoth = new Tag("sidewalk", "both"); | 
					
						
							|  |  |  |             const _sidewalkLeft = new Tag("sidewalk", "left"); | 
					
						
							|  |  |  |             const _sidewalkRight = new Tag("sidewalk", "right"); | 
					
						
							|  |  |  |             const _sidewalkNone = new Tag("sidewalk", "none"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             let parkingStateKnown = true; | 
					
						
							|  |  |  |             let parallelParkingCount = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const _oneSideParking = new Or([_leftSideParking, _rightSideParking]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (_oneSideParking.matchesProperties(properties)) { | 
					
						
							|  |  |  |                 parallelParkingCount = 1; | 
					
						
							|  |  |  |             } else if (_bothSideParking.matchesProperties(properties)) { | 
					
						
							|  |  |  |                 parallelParkingCount = 2; | 
					
						
							|  |  |  |             } else if (_noSideParking.matchesProperties(properties)) { | 
					
						
							|  |  |  |                 parallelParkingCount = 0; | 
					
						
							|  |  |  |             } else if (_otherParkingMode.matchesProperties(properties)) { | 
					
						
							|  |  |  |                 parallelParkingCount = 0; | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 parkingStateKnown = false; | 
					
						
							|  |  |  |                 console.log("No parking data for ", properties.name, properties.id, properties) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             let pedestrianFlowNeeded; | 
					
						
							|  |  |  |             if (_sidewalkBoth.matchesProperties(properties)) { | 
					
						
							|  |  |  |                 pedestrianFlowNeeded = 0; | 
					
						
							|  |  |  |             } else if (_sidewalkNone.matchesProperties(properties)) { | 
					
						
							|  |  |  |                 pedestrianFlowNeeded = 2; | 
					
						
							|  |  |  |             } else if (_sidewalkLeft.matchesProperties(properties) || _sidewalkRight.matchesProperties(properties)) { | 
					
						
							|  |  |  |                 pedestrianFlowNeeded = 1; | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 pedestrianFlowNeeded = -1; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             let onewayCar = properties.oneway === "yes"; | 
					
						
							|  |  |  |             let onewayBike = properties["oneway:bicycle"] === "yes" || | 
					
						
							|  |  |  |                 (onewayCar && properties["oneway:bicycle"] === undefined) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             let cyclingAllowed = | 
					
						
							|  |  |  |                 !(properties.bicycle === "use_sidepath" | 
					
						
							|  |  |  |                     || properties.bicycle === "no"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             let carWidthUsed = (onewayCar ? 1 : 2) * carWidth; | 
					
						
							|  |  |  |             properties["_width:needed:cars"] = Utils.Round(carWidthUsed); | 
					
						
							|  |  |  |             properties["_width:needed:parking"] = Utils.Round(parallelParkingCount * carWidth) | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             let cyclistWidthUsed = 0; | 
					
						
							|  |  |  |             if (cyclingAllowed) { | 
					
						
							|  |  |  |                 cyclistWidthUsed = (onewayBike ? 1 : 2) * cyclistWidth; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             properties["_width:needed:cyclists"] = Utils.Round(cyclistWidthUsed) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const width = parseFloat(properties["width:carriageway"]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const targetWidthIgnoringPedestrians = | 
					
						
							|  |  |  |                 carWidthUsed + | 
					
						
							|  |  |  |                 cyclistWidthUsed + | 
					
						
							|  |  |  |                 parallelParkingCount * carWidthUsed; | 
					
						
							|  |  |  |             properties["_width:needed:no_pedestrians"] =Utils.Round(targetWidthIgnoringPedestrians); | 
					
						
							|  |  |  |            | 
					
						
							|  |  |  |             const pedestriansNeed = Math.max(0, pedestrianFlowNeeded) * pedestrianWidth; | 
					
						
							|  |  |  |             const targetWidth = targetWidthIgnoringPedestrians + pedestriansNeed ; | 
					
						
							|  |  |  |             properties["_width:needed"] = Utils.Round(targetWidth); | 
					
						
							|  |  |  |             properties["_width:needed:pedestrians"] = Utils.Round(pedestriansNeed) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             properties["_width:difference"] = Utils.Round(targetWidth - width ); | 
					
						
							|  |  |  |             properties["_width:difference:no_pedestrians"] = Utils.Round(targetWidthIgnoringPedestrians - width) ; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public static metatags = [ | 
					
						
							|  |  |  |         MetaTagging.latlon, | 
					
						
							|  |  |  |         MetaTagging.surfaceArea, | 
					
						
							|  |  |  |         MetaTagging.country, | 
					
						
							|  |  |  |         MetaTagging.isOpen, | 
					
						
							|  |  |  |         MetaTagging.carriageWayWidth | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-19 12:08:42 +02:00
										 |  |  |     ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     static addMetatags(features: any[]) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (const metatag of MetaTagging.metatags) { | 
					
						
							| 
									
										
										
										
											2020-11-11 16:23:49 +01:00
										 |  |  |             try { | 
					
						
							|  |  |  |                 metatag.addMetaTags(features); | 
					
						
							|  |  |  |             } catch (e) { | 
					
						
							|  |  |  |                 console.error("Could not calculate metatag ", metatag.keys.join(","), ":", e) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-10-19 12:08:42 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } |