| 
									
										
										
										
											2021-04-18 14:24:30 +02:00
										 |  |  | import * as turf from "@turf/turf" | 
					
						
							| 
									
										
										
										
											2021-09-28 17:30:48 +02:00
										 |  |  | import { BBox } from "./BBox" | 
					
						
							| 
									
										
										
										
											2021-11-08 20:49:51 +01:00
										 |  |  | import togpx from "togpx" | 
					
						
							|  |  |  | import Constants from "../Models/Constants" | 
					
						
							|  |  |  | import LayerConfig from "../Models/ThemeConfig/LayerConfig" | 
					
						
							| 
									
										
										
										
											2022-07-13 17:56:10 +02:00
										 |  |  | import { | 
					
						
							|  |  |  |     AllGeoJSON, | 
					
						
							|  |  |  |     booleanWithin, | 
					
						
							|  |  |  |     Coord, | 
					
						
							|  |  |  |     Feature, | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |     Geometry, | 
					
						
							|  |  |  |     Lines, | 
					
						
							| 
									
										
										
										
											2022-07-13 17:56:10 +02:00
										 |  |  |     MultiPolygon, | 
					
						
							|  |  |  |     Polygon, | 
					
						
							|  |  |  |     Properties, | 
					
						
							|  |  |  | } from "@turf/turf" | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  | import { GeoJSON, LineString, Point } from "geojson" | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-28 23:33:48 +02:00
										 |  |  | export class GeoOperations { | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  |     private static readonly _earthRadius = 6378137 | 
					
						
							|  |  |  |     private static readonly _originShift = (2 * Math.PI * GeoOperations._earthRadius) / 2 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-28 23:33:48 +02:00
										 |  |  |     static surfaceAreaInSqMeters(feature: any) { | 
					
						
							|  |  |  |         return turf.area(feature) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-23 17:18:30 +02:00
										 |  |  |     /** | 
					
						
							| 
									
										
										
										
											2021-09-26 17:36:39 +02:00
										 |  |  |      * Converts a GeoJson feature to a point GeoJson feature | 
					
						
							| 
									
										
										
										
											2021-07-23 17:18:30 +02:00
										 |  |  |      * @param feature | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-09-27 18:52:13 +02:00
										 |  |  |     static centerpoint(feature: any): Feature<Point> { | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |         const newFeature: Feature<Point> = turf.center(feature) | 
					
						
							| 
									
										
										
										
											2020-07-22 00:50:30 +02:00
										 |  |  |         newFeature.properties = feature.properties | 
					
						
							|  |  |  |         newFeature.id = feature.id | 
					
						
							|  |  |  |         return newFeature | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-05-14 02:25:30 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-28 00:27:21 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Returns [lon,lat] coordinates | 
					
						
							|  |  |  |      * @param feature | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-07-13 17:56:10 +02:00
										 |  |  |     static centerpointCoordinates(feature: AllGeoJSON): [number, number] { | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |         return <[number, number]>turf.center(feature).geometry.coordinates | 
					
						
							| 
									
										
										
										
											2021-02-14 20:36:20 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-07-22 00:50:30 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-24 01:25:57 +01:00
										 |  |  |     /** | 
					
						
							| 
									
										
										
										
											2021-11-12 18:39:38 +01:00
										 |  |  |      * Returns the distance between the two points in meters | 
					
						
							| 
									
										
										
										
											2021-03-24 01:25:57 +01:00
										 |  |  |      * @param lonlat0 | 
					
						
							|  |  |  |      * @param lonlat1 | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-05-14 02:25:30 +02:00
										 |  |  |     static distanceBetween(lonlat0: [number, number], lonlat1: [number, number]) { | 
					
						
							| 
									
										
										
										
											2021-12-07 02:22:56 +01:00
										 |  |  |         return turf.distance(lonlat0, lonlat1, { units: "meters" }) | 
					
						
							| 
									
										
										
										
											2021-03-24 01:25:57 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |     static convexHull(featureCollection, options: { concavity?: number }) { | 
					
						
							| 
									
										
										
										
											2021-12-09 17:56:55 +01:00
										 |  |  |         return turf.convex(featureCollection, options) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-26 03:24:58 +01:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Calculates the overlap of 'feature' with every other specified feature. | 
					
						
							|  |  |  |      * The features with which 'feature' overlaps, are returned together with their overlap area in m² | 
					
						
							| 
									
										
										
										
											2021-05-14 02:25:30 +02:00
										 |  |  |      * | 
					
						
							|  |  |  |      * If 'feature' is a LineString, the features in which this feature is (partly) embedded is returned, the overlap length in meter is given | 
					
						
							| 
									
										
										
										
											2021-11-05 01:19:27 +01:00
										 |  |  |      * If 'feature' is a Polygon, overlapping points and points within the polygon will be returned | 
					
						
							| 
									
										
										
										
											2021-05-14 02:25:30 +02:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2021-03-26 03:24:58 +01:00
										 |  |  |      * If 'feature' is a point, it will return every feature the point is embedded in. Overlap will be undefined | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2022-03-23 19:48:06 +01:00
										 |  |  |      * const polygon = {"type": "Feature","properties": {},"geometry": {"type": "Polygon","coordinates": [[[1.8017578124999998,50.401515322782366],[-3.1640625,46.255846818480315],[5.185546875,44.74673324024678],[1.8017578124999998,50.401515322782366]]]}}; | 
					
						
							|  |  |  |      * const point = {"type": "Feature", "properties": {}, "geometry": { "type": "Point", "coordinates": [2.274169921875, 46.76244305208004]}}; | 
					
						
							|  |  |  |      * const overlap = GeoOperations.calculateOverlap(point, [polygon]); | 
					
						
							|  |  |  |      * overlap.length // => 1
 | 
					
						
							|  |  |  |      * overlap[0].feat == polygon // => true
 | 
					
						
							|  |  |  |      * const line = {"type": "Feature","properties": {},"geometry": {"type": "LineString","coordinates": [[3.779296875,48.777912755501845],[1.23046875,47.60616304386874]]}}; | 
					
						
							|  |  |  |      * const lineOverlap = GeoOperations.calculateOverlap(line, [polygon]); | 
					
						
							|  |  |  |      * lineOverlap.length // => 1
 | 
					
						
							|  |  |  |      * lineOverlap[0].overlap // => 156745.3293320278
 | 
					
						
							|  |  |  |      * lineOverlap[0].feat == polygon // => true
 | 
					
						
							|  |  |  |      * const line0 = {"type": "Feature","properties": {},"geometry": {"type": "LineString","coordinates": [[0.0439453125,47.31648293428332],[0.6591796875,46.77749276376827]]}}; | 
					
						
							|  |  |  |      * const overlap0 = GeoOperations.calculateOverlap(line0, [polygon]); | 
					
						
							|  |  |  |      * overlap.length // => 1
 | 
					
						
							| 
									
										
										
										
											2021-03-26 03:24:58 +01:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-05-14 02:25:30 +02:00
										 |  |  |     static calculateOverlap(feature: any, otherFeatures: any[]): { feat: any; overlap: number }[] { | 
					
						
							| 
									
										
										
										
											2021-03-26 03:24:58 +01:00
										 |  |  |         const featureBBox = BBox.get(feature) | 
					
						
							| 
									
										
										
										
											2021-05-14 02:25:30 +02:00
										 |  |  |         const result: { feat: any; overlap: number }[] = [] | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |         if (feature.geometry.type === "Point") { | 
					
						
							|  |  |  |             const coor = feature.geometry.coordinates | 
					
						
							| 
									
										
										
										
											2021-03-26 03:24:58 +01:00
										 |  |  |             for (const otherFeature of otherFeatures) { | 
					
						
							| 
									
										
										
										
											2022-01-26 20:47:08 +01:00
										 |  |  |                 if ( | 
					
						
							|  |  |  |                     feature.properties.id !== undefined && | 
					
						
							|  |  |  |                     feature.properties.id === otherFeature.properties.id | 
					
						
							|  |  |  |                 ) { | 
					
						
							| 
									
										
										
										
											2021-06-20 03:07:58 +02:00
										 |  |  |                     continue | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-14 02:25:30 +02:00
										 |  |  |                 if (otherFeature.geometry === undefined) { | 
					
						
							|  |  |  |                     console.error("No geometry for feature ", feature) | 
					
						
							|  |  |  |                     throw "List of other features contains a feature without geometry an undefined" | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-20 03:07:58 +02:00
										 |  |  |                 if (GeoOperations.inside(coor, otherFeature)) { | 
					
						
							| 
									
										
										
										
											2021-05-14 02:25:30 +02:00
										 |  |  |                     result.push({ feat: otherFeature, overlap: undefined }) | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-03-26 03:24:58 +01:00
										 |  |  |             return result | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-14 02:25:30 +02:00
										 |  |  |         if (feature.geometry.type === "LineString") { | 
					
						
							|  |  |  |             for (const otherFeature of otherFeatures) { | 
					
						
							| 
									
										
										
										
											2022-01-26 20:47:08 +01:00
										 |  |  |                 if ( | 
					
						
							|  |  |  |                     feature.properties.id !== undefined && | 
					
						
							|  |  |  |                     feature.properties.id === otherFeature.properties.id | 
					
						
							|  |  |  |                 ) { | 
					
						
							| 
									
										
										
										
											2021-06-20 03:07:58 +02:00
										 |  |  |                     continue | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2021-05-14 02:25:30 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-06 03:24:33 +01:00
										 |  |  |                 const intersection = GeoOperations.calculateInstersection( | 
					
						
							|  |  |  |                     feature, | 
					
						
							|  |  |  |                     otherFeature, | 
					
						
							|  |  |  |                     featureBBox | 
					
						
							|  |  |  |                 ) | 
					
						
							| 
									
										
										
										
											2021-06-21 03:12:18 +02:00
										 |  |  |                 if (intersection === null) { | 
					
						
							|  |  |  |                     continue | 
					
						
							| 
									
										
										
										
											2021-05-14 02:25:30 +02:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2021-06-21 03:12:18 +02:00
										 |  |  |                 result.push({ feat: otherFeature, overlap: intersection }) | 
					
						
							| 
									
										
										
										
											2021-05-14 02:25:30 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |             return result | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-28 23:33:48 +02:00
										 |  |  |         if (feature.geometry.type === "Polygon" || feature.geometry.type === "MultiPolygon") { | 
					
						
							| 
									
										
										
										
											2021-03-26 03:24:58 +01:00
										 |  |  |             for (const otherFeature of otherFeatures) { | 
					
						
							| 
									
										
										
										
											2022-01-26 20:47:08 +01:00
										 |  |  |                 if ( | 
					
						
							|  |  |  |                     feature.properties.id !== undefined && | 
					
						
							|  |  |  |                     feature.properties.id === otherFeature.properties.id | 
					
						
							|  |  |  |                 ) { | 
					
						
							| 
									
										
										
										
											2021-06-20 03:07:58 +02:00
										 |  |  |                     continue | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2021-06-21 03:12:18 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 if (otherFeature.geometry.type === "Point") { | 
					
						
							| 
									
										
										
										
											2021-06-20 03:07:58 +02:00
										 |  |  |                     if (this.inside(otherFeature, feature)) { | 
					
						
							|  |  |  |                         result.push({ feat: otherFeature, overlap: undefined }) | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2021-06-21 03:12:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-28 23:33:48 +02:00
										 |  |  |                 // Calculate the surface area of the intersection
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-21 03:12:18 +02:00
										 |  |  |                 const intersection = this.calculateInstersection(feature, otherFeature, featureBBox) | 
					
						
							|  |  |  |                 if (intersection === null) { | 
					
						
							|  |  |  |                     continue | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2021-06-21 03:12:18 +02:00
										 |  |  |                 result.push({ feat: otherFeature, overlap: intersection }) | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-03-26 03:24:58 +01:00
										 |  |  |             return result | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-03-26 03:24:58 +01:00
										 |  |  |         console.error( | 
					
						
							|  |  |  |             "Could not correctly calculate the overlap of ", | 
					
						
							|  |  |  |             feature, | 
					
						
							|  |  |  |             ": unsupported type" | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         return result | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-05-14 02:25:30 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-07 19:48:09 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Helper function which does the heavy lifting for 'inside' | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private static pointInPolygonCoordinates( | 
					
						
							|  |  |  |         x: number, | 
					
						
							|  |  |  |         y: number, | 
					
						
							|  |  |  |         coordinates: [number, number][][] | 
					
						
							|  |  |  |     ) { | 
					
						
							| 
									
										
										
										
											2022-02-11 15:27:20 +01:00
										 |  |  |         const inside = GeoOperations.pointWithinRing( | 
					
						
							|  |  |  |             x, | 
					
						
							|  |  |  |             y, | 
					
						
							|  |  |  |             /*This is the outer ring of the polygon */ coordinates[0] | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         if (!inside) { | 
					
						
							|  |  |  |             return false | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         for (let i = 1; i < coordinates.length; i++) { | 
					
						
							|  |  |  |             const inHole = GeoOperations.pointWithinRing( | 
					
						
							|  |  |  |                 x, | 
					
						
							|  |  |  |                 y, | 
					
						
							|  |  |  |                 coordinates[i] /* These are inner rings, aka holes*/ | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |             if (inHole) { | 
					
						
							|  |  |  |                 return false | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return true | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-23 19:48:06 +01:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Detect wether or not the given point is located in the feature | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * // Should work with a normal polygon
 | 
					
						
							|  |  |  |      * const polygon = {"type": "Feature","properties": {},"geometry": {"type": "Polygon","coordinates": [[[1.8017578124999998,50.401515322782366],[-3.1640625,46.255846818480315],[5.185546875,44.74673324024678],[1.8017578124999998,50.401515322782366]]]}}; | 
					
						
							|  |  |  |      * GeoOperations.inside([3.779296875, 48.777912755501845], polygon) // => false
 | 
					
						
							|  |  |  |      * GeoOperations.inside([1.23046875, 47.60616304386874], polygon) // => true
 | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * // should work with a multipolygon and detect holes
 | 
					
						
							|  |  |  |      * const multiPolygon = {"type": "Feature", "properties": {}, | 
					
						
							|  |  |  |      *         "geometry": { | 
					
						
							|  |  |  |      *             "type": "MultiPolygon", | 
					
						
							|  |  |  |      *             "coordinates": [[ | 
					
						
							|  |  |  |      *                 [[1.8017578124999998,50.401515322782366],[-3.1640625,46.255846818480315],[5.185546875,44.74673324024678],[1.8017578124999998,50.401515322782366]], | 
					
						
							|  |  |  |      *                 [[1.0107421875,48.821332549646634],[1.329345703125,48.25394114463431],[1.988525390625,48.71271258145237],[0.999755859375,48.86471476180277],[1.0107421875,48.821332549646634]] | 
					
						
							|  |  |  |      *             ]] | 
					
						
							|  |  |  |      *         } | 
					
						
							|  |  |  |      *     }; | 
					
						
							|  |  |  |      * GeoOperations.inside([2.515869140625, 47.37603463349758], multiPolygon) // => true
 | 
					
						
							|  |  |  |      * GeoOperations.inside([1.42822265625, 48.61838518688487], multiPolygon) // => false
 | 
					
						
							|  |  |  |      * GeoOperations.inside([4.02099609375, 47.81315451752768], multiPolygon) // => false
 | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2020-09-25 21:58:29 +02:00
										 |  |  |     public static inside(pointCoordinate, feature): boolean { | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |         // ray-casting algorithm based on
 | 
					
						
							|  |  |  |         // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (feature.geometry.type === "Point") { | 
					
						
							|  |  |  |             return false | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-06-21 03:12:18 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if (pointCoordinate.geometry !== undefined) { | 
					
						
							| 
									
										
										
										
											2021-06-20 03:07:58 +02:00
										 |  |  |             pointCoordinate = pointCoordinate.geometry.coordinates | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-11 15:27:20 +01:00
										 |  |  |         const x: number = pointCoordinate[0] | 
					
						
							|  |  |  |         const y: number = pointCoordinate[1] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-14 02:25:30 +02:00
										 |  |  |         if (feature.geometry.type === "MultiPolygon") { | 
					
						
							| 
									
										
										
										
											2022-02-11 15:27:20 +01:00
										 |  |  |             const coordinatess = feature.geometry.coordinates | 
					
						
							|  |  |  |             for (const coordinates of coordinatess) { | 
					
						
							|  |  |  |                 const inThisPolygon = GeoOperations.pointInPolygonCoordinates(x, y, coordinates) | 
					
						
							|  |  |  |                 if (inThisPolygon) { | 
					
						
							|  |  |  |                     return true | 
					
						
							| 
									
										
										
										
											2021-05-14 02:25:30 +02:00
										 |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-02-11 15:27:20 +01:00
										 |  |  |             return false | 
					
						
							| 
									
										
										
										
											2021-05-14 02:25:30 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-11 15:27:20 +01:00
										 |  |  |         if (feature.geometry.type === "Polygon") { | 
					
						
							|  |  |  |             return GeoOperations.pointInPolygonCoordinates(x, y, feature.geometry.coordinates) | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-06-24 13:50:35 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-14 01:15:20 +01:00
										 |  |  |         throw "GeoOperations.inside: unsupported geometry type " + feature.geometry.type | 
					
						
							| 
									
										
										
										
											2022-02-11 15:27:20 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-18 14:24:30 +02:00
										 |  |  |     static lengthInMeters(feature: any) { | 
					
						
							|  |  |  |         return turf.length(feature) * 1000 | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-09-22 05:02:09 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     static buffer(feature: any, bufferSizeInMeter: number) { | 
					
						
							|  |  |  |         return turf.buffer(feature, bufferSizeInMeter / 1000, { | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |             units: "kilometers", | 
					
						
							|  |  |  |         }) | 
					
						
							| 
									
										
										
										
											2021-09-21 02:10:42 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-09-22 05:02:09 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     static bbox(feature: any) { | 
					
						
							| 
									
										
										
										
											2021-09-21 02:10:42 +02:00
										 |  |  |         const [lon, lat, lon0, lat0] = turf.bbox(feature) | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |             type: "Feature", | 
					
						
							|  |  |  |             geometry: { | 
					
						
							|  |  |  |                 type: "LineString", | 
					
						
							|  |  |  |                 coordinates: [ | 
					
						
							|  |  |  |                     [lon, lat], | 
					
						
							|  |  |  |                     [lon0, lat], | 
					
						
							|  |  |  |                     [lon0, lat0], | 
					
						
							|  |  |  |                     [lon, lat0], | 
					
						
							|  |  |  |                     [lon, lat], | 
					
						
							|  |  |  |                 ], | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-06-21 03:12:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-09 00:05:51 +02:00
										 |  |  |     /** | 
					
						
							| 
									
										
										
										
											2022-09-21 02:09:41 +02:00
										 |  |  |      * Generates the closest point on a way from a given point. | 
					
						
							|  |  |  |      * If the passed-in geojson object is a polygon, the outer ring will be used as linestring | 
					
						
							| 
									
										
										
										
											2021-10-27 03:52:19 +02:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2021-09-22 05:02:09 +02:00
										 |  |  |      *  The properties object will contain three values: | 
					
						
							|  |  |  |      // - `index`: closest point was found on nth line part,
 | 
					
						
							|  |  |  |      // - `dist`: distance between pt and the closest point (in kilometer),
 | 
					
						
							| 
									
										
										
										
											2021-10-16 02:54:22 +02:00
										 |  |  |      // `location`: distance along the line between start (of the line) and the closest point.
 | 
					
						
							| 
									
										
										
										
											2021-09-09 00:05:51 +02:00
										 |  |  |      * @param way The road on which you want to find a point | 
					
						
							|  |  |  |      * @param point Point defined as [lon, lat] | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-09-21 02:09:41 +02:00
										 |  |  |     public static nearestPoint(way: Feature<LineString | Polygon>, point: [number, number]) { | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  |         if (way.geometry.type === "Polygon") { | 
					
						
							| 
									
										
										
										
											2021-10-31 02:08:39 +01:00
										 |  |  |             way = { ...way } | 
					
						
							|  |  |  |             way.geometry = { ...way.geometry } | 
					
						
							|  |  |  |             way.geometry.type = "LineString" | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |             way.geometry.coordinates = (<Polygon>way.geometry).coordinates[0] | 
					
						
							| 
									
										
										
										
											2021-10-31 02:08:39 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-27 01:50:01 +02:00
										 |  |  |         return turf.nearestPointOnLine(<Feature<LineString>>way, point, { units: "kilometers" }) | 
					
						
							| 
									
										
										
										
											2021-09-09 00:05:51 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public static toCSV(features: any[]): string { | 
					
						
							|  |  |  |         const headerValuesSeen = new Set<string>() | 
					
						
							|  |  |  |         const headerValuesOrdered: string[] = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         function addH(key) { | 
					
						
							|  |  |  |             if (!headerValuesSeen.has(key)) { | 
					
						
							|  |  |  |                 headerValuesSeen.add(key) | 
					
						
							|  |  |  |                 headerValuesOrdered.push(key) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         addH("_lat") | 
					
						
							|  |  |  |         addH("_lon") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const lines: string[] = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (const feature of features) { | 
					
						
							|  |  |  |             const properties = feature.properties | 
					
						
							|  |  |  |             for (const key in properties) { | 
					
						
							|  |  |  |                 if (!properties.hasOwnProperty(key)) { | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 addH(key) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         headerValuesOrdered.sort() | 
					
						
							|  |  |  |         for (const feature of features) { | 
					
						
							|  |  |  |             const properties = feature.properties | 
					
						
							|  |  |  |             let line = "" | 
					
						
							|  |  |  |             for (const key of headerValuesOrdered) { | 
					
						
							|  |  |  |                 const value = properties[key] | 
					
						
							|  |  |  |                 if (value === undefined) { | 
					
						
							|  |  |  |                     line += "," | 
					
						
							|  |  |  |                 } else { | 
					
						
							|  |  |  |                     line += JSON.stringify(value) + "," | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             lines.push(line) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return headerValuesOrdered.map((v) => JSON.stringify(v)).join(",") + "\n" + lines.join("\n") | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-27 03:52:19 +02:00
										 |  |  |     //Converts given lat/lon in WGS84 Datum to XY in Spherical Mercator EPSG:900913
 | 
					
						
							|  |  |  |     public static ConvertWgs84To900913(lonLat: [number, number]): [number, number] { | 
					
						
							|  |  |  |         const lon = lonLat[0] | 
					
						
							|  |  |  |         const lat = lonLat[1] | 
					
						
							|  |  |  |         const x = (lon * GeoOperations._originShift) / 180 | 
					
						
							|  |  |  |         let y = Math.log(Math.tan(((90 + lat) * Math.PI) / 360)) / (Math.PI / 180) | 
					
						
							|  |  |  |         y = (y * GeoOperations._originShift) / 180 | 
					
						
							|  |  |  |         return [x, y] | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-04 02:16:07 +01:00
										 |  |  |     //Converts XY point from (Spherical) Web Mercator EPSG:3785 (unofficially EPSG:900913) to lat/lon in WGS84 Datum
 | 
					
						
							| 
									
										
										
										
											2021-10-27 03:52:19 +02:00
										 |  |  |     public static Convert900913ToWgs84(lonLat: [number, number]): [number, number] { | 
					
						
							|  |  |  |         const lon = lonLat[0] | 
					
						
							|  |  |  |         const lat = lonLat[1] | 
					
						
							|  |  |  |         const x = (180 * lon) / GeoOperations._originShift | 
					
						
							|  |  |  |         let y = (180 * lat) / GeoOperations._originShift | 
					
						
							|  |  |  |         y = (180 / Math.PI) * (2 * Math.atan(Math.exp((y * Math.PI) / 180)) - Math.PI / 2) | 
					
						
							|  |  |  |         return [x, y] | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     public static GeoJsonToWGS84(geojson) { | 
					
						
							| 
									
										
										
										
											2021-10-27 03:52:19 +02:00
										 |  |  |         return turf.toWgs84(geojson) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Tries to remove points which do not contribute much to the general outline. | 
					
						
							|  |  |  |      * Points for which the angle is ~ 180° are removed | 
					
						
							|  |  |  |      * @param coordinates | 
					
						
							|  |  |  |      * @constructor | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public static SimplifyCoordinates(coordinates: [number, number][]) { | 
					
						
							|  |  |  |         const newCoordinates = [] | 
					
						
							|  |  |  |         for (let i = 1; i < coordinates.length - 1; i++) { | 
					
						
							|  |  |  |             const coordinate = coordinates[i] | 
					
						
							|  |  |  |             const prev = coordinates[i - 1] | 
					
						
							|  |  |  |             const next = coordinates[i + 1] | 
					
						
							|  |  |  |             const b0 = turf.bearing(prev, coordinate, { final: true }) | 
					
						
							|  |  |  |             const b1 = turf.bearing(coordinate, next) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const diff = Math.abs(b1 - b0) | 
					
						
							|  |  |  |             if (diff < 2) { | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             newCoordinates.push(coordinate) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return newCoordinates | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-19 02:11:22 +01:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Calculates line intersection between two features. | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |     public static LineIntersections(feature, otherFeature): [number, number][] { | 
					
						
							|  |  |  |         return turf | 
					
						
							|  |  |  |             .lineIntersect(feature, otherFeature) | 
					
						
							|  |  |  |             .features.map((p) => <[number, number]>p.geometry.coordinates) | 
					
						
							| 
									
										
										
										
											2021-12-19 02:11:22 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     public static AsGpx(feature, generatedWithLayer?: LayerConfig) { | 
					
						
							| 
									
										
										
										
											2021-11-08 20:49:51 +01:00
										 |  |  |         const metadata = {} | 
					
						
							|  |  |  |         const tags = feature.properties | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if (generatedWithLayer !== undefined) { | 
					
						
							| 
									
										
										
										
											2021-11-08 20:49:51 +01:00
										 |  |  |             metadata["name"] = generatedWithLayer.title?.GetRenderValue(tags)?.Subs(tags)?.txt | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |             metadata["desc"] = "Generated with MapComplete layer " + generatedWithLayer.id | 
					
						
							|  |  |  |             if (tags._backend?.contains("openstreetmap")) { | 
					
						
							|  |  |  |                 metadata["copyright"] = | 
					
						
							|  |  |  |                     "Data copyrighted by OpenStreetMap-contributors, freely available under ODbL. See https://www.openstreetmap.org/copyright" | 
					
						
							| 
									
										
										
										
											2021-11-08 20:49:51 +01:00
										 |  |  |                 metadata["author"] = tags["_last_edit:contributor"] | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |                 metadata["link"] = "https://www.openstreetmap.org/" + tags.id | 
					
						
							| 
									
										
										
										
											2021-11-08 20:49:51 +01:00
										 |  |  |                 metadata["time"] = tags["_last_edit:timestamp"] | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |             } else { | 
					
						
							| 
									
										
										
										
											2021-11-08 20:49:51 +01:00
										 |  |  |                 metadata["time"] = new Date().toISOString() | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-08 20:49:51 +01:00
										 |  |  |         return togpx(feature, { | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |             creator: "MapComplete " + Constants.vNumber, | 
					
						
							| 
									
										
										
										
											2021-11-08 20:49:51 +01:00
										 |  |  |             metadata, | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     public static IdentifieCommonSegments(coordinatess: [number, number][][]): { | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |         originalIndex: number | 
					
						
							|  |  |  |         segmentShardWith: number[] | 
					
						
							|  |  |  |         coordinates: [] | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |     }[] { | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |         // An edge. Note that the edge might be reversed to fix the sorting condition:  start[0] < end[0] && (start[0] != end[0] || start[0] < end[1])
 | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |         type edge = { | 
					
						
							|  |  |  |             start: [number, number] | 
					
						
							|  |  |  |             end: [number, number] | 
					
						
							|  |  |  |             intermediate: [number, number][] | 
					
						
							|  |  |  |             members: { index: number; isReversed: boolean }[] | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         // The strategy:
 | 
					
						
							|  |  |  |         // 1. Index _all_ edges from _every_ linestring. Index them by starting key, gather which relations run over them
 | 
					
						
							|  |  |  |         // 2. Join these edges back together - as long as their membership groups are the same
 | 
					
						
							|  |  |  |         // 3. Convert to results
 | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |         const allEdgesByKey = new Map<string, edge>() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |         for (let index = 0; index < coordinatess.length; index++) { | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |             const coordinates = coordinatess[index] | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |             for (let i = 0; i < coordinates.length - 1; i++) { | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |                 const c0 = coordinates[i] | 
					
						
							|  |  |  |                 const c1 = coordinates[i + 1] | 
					
						
							|  |  |  |                 const isReversed = c0[0] > c1[0] || (c0[0] == c1[0] && c0[1] > c1[1]) | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 let key: string | 
					
						
							|  |  |  |                 if (isReversed) { | 
					
						
							|  |  |  |                     key = "" + c1 + ";" + c0 | 
					
						
							|  |  |  |                 } else { | 
					
						
							|  |  |  |                     key = "" + c0 + ";" + c1 | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |                 } | 
					
						
							|  |  |  |                 const member = { index, isReversed } | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |                 if (allEdgesByKey.has(key)) { | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |                     allEdgesByKey.get(key).members.push(member) | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 let edge: edge | 
					
						
							|  |  |  |                 if (!isReversed) { | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |                     edge = { | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |                         start: c0, | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |                         end: c1, | 
					
						
							|  |  |  |                         members: [member], | 
					
						
							|  |  |  |                         intermediate: [], | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |                 } else { | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |                     edge = { | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |                         start: c1, | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |                         end: c0, | 
					
						
							|  |  |  |                         members: [member], | 
					
						
							|  |  |  |                         intermediate: [], | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 allEdgesByKey.set(key, edge) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Lets merge them back together!
 | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |         let didMergeSomething = false | 
					
						
							|  |  |  |         let allMergedEdges = Array.from(allEdgesByKey.values()) | 
					
						
							|  |  |  |         const allEdgesByStartPoint = new Map<string, edge[]>() | 
					
						
							|  |  |  |         for (const edge of allMergedEdges) { | 
					
						
							|  |  |  |             edge.members.sort((m0, m1) => m0.index - m1.index) | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |             const kstart = edge.start + "" | 
					
						
							|  |  |  |             if (!allEdgesByStartPoint.has(kstart)) { | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |                 allEdgesByStartPoint.set(kstart, []) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             allEdgesByStartPoint.get(kstart).push(edge) | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         function membersAreCompatible(first: edge, second: edge): boolean { | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |             // There must be an exact match between the members
 | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |             if (first.members === second.members) { | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |                 return true | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |             if (first.members.length !== second.members.length) { | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |                 return false | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |             // Members are sorted and have the same length, so we can check quickly
 | 
					
						
							|  |  |  |             for (let i = 0; i < first.members.length; i++) { | 
					
						
							|  |  |  |                 const m0 = first.members[i] | 
					
						
							|  |  |  |                 const m1 = second.members[i] | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |                 if (m0.index !== m1.index || m0.isReversed !== m1.isReversed) { | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |                     return false | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |             // Allrigth, they are the same, lets mark this permanently
 | 
					
						
							|  |  |  |             second.members = first.members | 
					
						
							|  |  |  |             return true | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         do { | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |             didMergeSomething = false | 
					
						
							|  |  |  |             // We use 'allMergedEdges' as our running list
 | 
					
						
							|  |  |  |             const consumed = new Set<edge>() | 
					
						
							|  |  |  |             for (const edge of allMergedEdges) { | 
					
						
							|  |  |  |                 // Can we make this edge longer at the end?
 | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |                 if (consumed.has(edge)) { | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |                     continue | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |                 console.log("Considering edge", edge) | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |                 const matchingEndEdges = allEdgesByStartPoint.get(edge.end + "") | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |                 console.log("Matchign endpoints:", matchingEndEdges) | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |                 if (matchingEndEdges === undefined) { | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |                     continue | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 for (let i = 0; i < matchingEndEdges.length; i++) { | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |                     const endEdge = matchingEndEdges[i] | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |                     if (consumed.has(endEdge)) { | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |                         continue | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |                     if (!membersAreCompatible(edge, endEdge)) { | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |                         continue | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |                     // We can make the segment longer!
 | 
					
						
							|  |  |  |                     didMergeSomething = true | 
					
						
							|  |  |  |                     console.log("Merging ", edge, "with ", endEdge) | 
					
						
							|  |  |  |                     edge.intermediate.push(edge.end) | 
					
						
							|  |  |  |                     edge.end = endEdge.end | 
					
						
							|  |  |  |                     consumed.add(endEdge) | 
					
						
							|  |  |  |                     matchingEndEdges.splice(i, 1) | 
					
						
							|  |  |  |                     break | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |             allMergedEdges = allMergedEdges.filter((edge) => !consumed.has(edge)) | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |         } while (didMergeSomething) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |         return [] | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Removes points that do not contribute to the geometry from linestrings and the outer ring of polygons. | 
					
						
							|  |  |  |      * Returs a new copy of the feature | 
					
						
							| 
									
										
										
										
											2022-03-24 03:30:05 +01:00
										 |  |  |      * | 
					
						
							|  |  |  |      * const feature = {"geometry": {"type": "Polygon","coordinates": [[[4.477944199999975,51.02783550000022],[4.477987899999996,51.027818800000034],[4.478004500000021,51.02783399999988],[4.478025499999962,51.02782489999994],[4.478079099999993,51.027873899999896],[4.47801040000006,51.027903799999955],[4.477964799999972,51.02785709999982],[4.477964699999964,51.02785690000006],[4.477944199999975,51.02783550000022]]]}} | 
					
						
							|  |  |  |      * const copy = GeoOperations.removeOvernoding(feature) | 
					
						
							|  |  |  |      * expect(copy.geometry.coordinates[0]).deep.equal([[4.477944199999975,51.02783550000022],[4.477987899999996,51.027818800000034],[4.478004500000021,51.02783399999988],[4.478025499999962,51.02782489999994],[4.478079099999993,51.027873899999896],[4.47801040000006,51.027903799999955],[4.477944199999975,51.02783550000022]]) | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |      */ | 
					
						
							|  |  |  |     static removeOvernoding(feature: any) { | 
					
						
							|  |  |  |         if (feature.geometry.type !== "LineString" && feature.geometry.type !== "Polygon") { | 
					
						
							|  |  |  |             throw "Overnode removal is only supported on linestrings and polygons" | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const copy = { | 
					
						
							|  |  |  |             ...feature, | 
					
						
							|  |  |  |             geometry: { ...feature.geometry }, | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         let coordinates: [number, number][] | 
					
						
							|  |  |  |         if (feature.geometry.type === "LineString") { | 
					
						
							|  |  |  |             coordinates = [...feature.geometry.coordinates] | 
					
						
							|  |  |  |             copy.geometry.coordinates = coordinates | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             coordinates = [...feature.geometry.coordinates[0]] | 
					
						
							|  |  |  |             copy.geometry.coordinates[0] = coordinates | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // inline replacement in the coordinates list
 | 
					
						
							|  |  |  |         for (let i = coordinates.length - 2; i >= 1; i--) { | 
					
						
							|  |  |  |             const coordinate = coordinates[i] | 
					
						
							|  |  |  |             const nextCoordinate = coordinates[i + 1] | 
					
						
							|  |  |  |             const prevCoordinate = coordinates[i - 1] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const distP = GeoOperations.distanceBetween(coordinate, prevCoordinate) | 
					
						
							| 
									
										
										
										
											2022-01-26 21:40:38 +01:00
										 |  |  |             if (distP < 0.1) { | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |                 coordinates.splice(i, 1) | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-01-26 21:40:38 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |             if (i == coordinates.length - 2) { | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |                 const distN = GeoOperations.distanceBetween(coordinate, nextCoordinate) | 
					
						
							| 
									
										
										
										
											2022-01-26 21:40:38 +01:00
										 |  |  |                 if (distN < 0.1) { | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |                     coordinates.splice(i, 1) | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-01-26 21:40:38 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |             const bearingN = turf.bearing(coordinate, nextCoordinate) | 
					
						
							|  |  |  |             const bearingP = turf.bearing(prevCoordinate, coordinate) | 
					
						
							|  |  |  |             const diff = Math.abs(bearingN - bearingP) | 
					
						
							|  |  |  |             if (diff < 4) { | 
					
						
							|  |  |  |                 // If the diff is low, this point is hardly relevant
 | 
					
						
							|  |  |  |                 coordinates.splice(i, 1) | 
					
						
							|  |  |  |             } else if (360 - diff < 4) { | 
					
						
							|  |  |  |                 // In case that the line is going south, e.g. bearingN = 179, bearingP = -179
 | 
					
						
							|  |  |  |                 coordinates.splice(i, 1) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return copy | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-11 15:27:20 +01:00
										 |  |  |     private static pointWithinRing(x: number, y: number, ring: [number, number][]) { | 
					
						
							|  |  |  |         let inside = false | 
					
						
							|  |  |  |         for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) { | 
					
						
							|  |  |  |             const coori = ring[i] | 
					
						
							|  |  |  |             const coorj = ring[j] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const xi = coori[0] | 
					
						
							|  |  |  |             const yi = coori[1] | 
					
						
							|  |  |  |             const xj = coorj[0] | 
					
						
							|  |  |  |             const yj = coorj[1] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const intersect = yi > y != yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi | 
					
						
							|  |  |  |             if (intersect) { | 
					
						
							|  |  |  |                 inside = !inside | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return inside | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Calculates the intersection between two features. | 
					
						
							|  |  |  |      * Returns the length if intersecting a linestring and a (multi)polygon (in meters), returns a surface area (in m²) if intersecting two (multi)polygons | 
					
						
							|  |  |  |      * Returns 0 if both are linestrings | 
					
						
							|  |  |  |      * Returns null if the features are not intersecting | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private static calculateInstersection( | 
					
						
							|  |  |  |         feature, | 
					
						
							|  |  |  |         otherFeature, | 
					
						
							|  |  |  |         featureBBox: BBox, | 
					
						
							|  |  |  |         otherFeatureBBox?: BBox | 
					
						
							|  |  |  |     ): number { | 
					
						
							|  |  |  |         if (feature.geometry.type === "LineString") { | 
					
						
							|  |  |  |             otherFeatureBBox = otherFeatureBBox ?? BBox.get(otherFeature) | 
					
						
							|  |  |  |             const overlaps = featureBBox.overlapsWith(otherFeatureBBox) | 
					
						
							|  |  |  |             if (!overlaps) { | 
					
						
							|  |  |  |                 return null | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Calculate the length of the intersection
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             let intersectionPoints = turf.lineIntersect(feature, otherFeature) | 
					
						
							|  |  |  |             if (intersectionPoints.features.length == 0) { | 
					
						
							|  |  |  |                 // No intersections.
 | 
					
						
							|  |  |  |                 // If one point is inside of the polygon, all points are
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 const coors = feature.geometry.coordinates | 
					
						
							|  |  |  |                 const startCoor = coors[0] | 
					
						
							|  |  |  |                 if (this.inside(startCoor, otherFeature)) { | 
					
						
							|  |  |  |                     return this.lengthInMeters(feature) | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 return null | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             let intersectionPointsArray = intersectionPoints.features.map((d) => { | 
					
						
							|  |  |  |                 return d.geometry.coordinates | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (otherFeature.geometry.type === "LineString") { | 
					
						
							|  |  |  |                 if (intersectionPointsArray.length > 0) { | 
					
						
							|  |  |  |                     return 0 | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 return null | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (intersectionPointsArray.length == 1) { | 
					
						
							|  |  |  |                 // We need to add the start- or endpoint of the current feature, depending on which one is embedded
 | 
					
						
							|  |  |  |                 const coors = feature.geometry.coordinates | 
					
						
							|  |  |  |                 const startCoor = coors[0] | 
					
						
							|  |  |  |                 if (this.inside(startCoor, otherFeature)) { | 
					
						
							|  |  |  |                     // The startpoint is embedded
 | 
					
						
							|  |  |  |                     intersectionPointsArray.push(startCoor) | 
					
						
							|  |  |  |                 } else { | 
					
						
							|  |  |  |                     intersectionPointsArray.push(coors[coors.length - 1]) | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             let intersection = turf.lineSlice( | 
					
						
							|  |  |  |                 turf.point(intersectionPointsArray[0]), | 
					
						
							|  |  |  |                 turf.point(intersectionPointsArray[1]), | 
					
						
							|  |  |  |                 feature | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             ) | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |             if (intersection == null) { | 
					
						
							|  |  |  |                 return null | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             const intersectionSize = turf.length(intersection) // in km
 | 
					
						
							|  |  |  |             return intersectionSize * 1000 | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (feature.geometry.type === "Polygon" || feature.geometry.type === "MultiPolygon") { | 
					
						
							|  |  |  |             const otherFeatureBBox = BBox.get(otherFeature) | 
					
						
							|  |  |  |             const overlaps = featureBBox.overlapsWith(otherFeatureBBox) | 
					
						
							|  |  |  |             if (!overlaps) { | 
					
						
							|  |  |  |                 return null | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (otherFeature.geometry.type === "LineString") { | 
					
						
							|  |  |  |                 return this.calculateInstersection( | 
					
						
							|  |  |  |                     otherFeature, | 
					
						
							|  |  |  |                     feature, | 
					
						
							|  |  |  |                     otherFeatureBBox, | 
					
						
							|  |  |  |                     featureBBox | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             try { | 
					
						
							|  |  |  |                 const intersection = turf.intersect(feature, otherFeature) | 
					
						
							|  |  |  |                 if (intersection == null) { | 
					
						
							|  |  |  |                     return null | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 return turf.area(intersection) // in m²
 | 
					
						
							|  |  |  |             } catch (e) { | 
					
						
							|  |  |  |                 if (e.message === "Each LinearRing of a Polygon must have 4 or more Positions.") { | 
					
						
							|  |  |  |                     // WORKAROUND TIME!
 | 
					
						
							|  |  |  |                     // See https://github.com/Turfjs/turf/pull/2238
 | 
					
						
							|  |  |  |                     return null | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 throw e | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         throw "CalculateIntersection fallthrough: can not calculate an intersection between features" | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-01-26 21:40:38 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-03 01:33:41 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Takes two points and finds the geographic bearing between them, i.e. the angle measured in degrees from the north line (0 degrees) | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public static bearing(a: Coord, b: Coord): number { | 
					
						
							|  |  |  |         return turf.bearing(a, b) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-01-26 21:40:38 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-07 19:48:09 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Returns 'true' if one feature contains the other feature | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * const pond: Feature<Polygon, any> = { | 
					
						
							|  |  |  |      *       "type": "Feature", | 
					
						
							|  |  |  |      *       "properties": {"natural":"water","water":"pond"}, | 
					
						
							|  |  |  |      *       "geometry": { | 
					
						
							|  |  |  |      *         "type": "Polygon", | 
					
						
							|  |  |  |      *         "coordinates": [[ | 
					
						
							|  |  |  |      *             [4.362924098968506,50.8435422298544 ], | 
					
						
							|  |  |  |      *             [4.363272786140442,50.8435219059949 ], | 
					
						
							|  |  |  |      *             [4.363213777542114,50.8437420806679 ], | 
					
						
							|  |  |  |      *             [4.362924098968506,50.8435422298544 ] | 
					
						
							|  |  |  |      *           ]]}} | 
					
						
							|  |  |  |      * const park: Feature<Polygon, any> =   { | 
					
						
							|  |  |  |      *       "type": "Feature", | 
					
						
							|  |  |  |      *       "properties": {"leisure":"park"}, | 
					
						
							|  |  |  |      *       "geometry": { | 
					
						
							|  |  |  |      *         "type": "Polygon", | 
					
						
							|  |  |  |      *         "coordinates": [[ | 
					
						
							|  |  |  |      *            [ 4.36073541641235,50.84323737103244 ], | 
					
						
							|  |  |  |      *            [ 4.36469435691833, 50.8423905305197 ], | 
					
						
							|  |  |  |      *            [ 4.36659336090087, 50.8458997374786 ], | 
					
						
							|  |  |  |      *            [ 4.36254858970642, 50.8468007074916 ], | 
					
						
							|  |  |  |      *            [ 4.36073541641235, 50.8432373710324 ] | 
					
						
							|  |  |  |      *           ]]}} | 
					
						
							|  |  |  |      * GeoOperations.completelyWithin(pond, park) // => true
 | 
					
						
							|  |  |  |      * GeoOperations.completelyWithin(park, pond) // => false
 | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     static completelyWithin( | 
					
						
							|  |  |  |         feature: Feature<Geometry, any>, | 
					
						
							|  |  |  |         possiblyEncloingFeature: Feature<Polygon | MultiPolygon, any> | 
					
						
							|  |  |  |     ): boolean { | 
					
						
							|  |  |  |         return booleanWithin(feature, possiblyEncloingFeature) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | } |