| 
									
										
										
										
											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"; | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-28 23:33:48 +02:00
										 |  |  | export class GeoOperations { | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											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 | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-05-14 02:25:30 +02:00
										 |  |  |     static centerpoint(feature: any) { | 
					
						
							|  |  |  |         const newFeature = 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 | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-05-14 02:25:30 +02:00
										 |  |  |     static centerpointCoordinates(feature: any): [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) { | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											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) { | 
					
						
							| 
									
										
										
										
											2021-06-20 03:07:58 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											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") { | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-26 03:24:58 +01:00
										 |  |  |             for (const otherFeature of otherFeatures) { | 
					
						
							| 
									
										
										
										
											2021-06-21 03:12:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											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-24 00:35:19 +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-28 23:33:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											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-02-11 15:27:20 +01:00
										 |  |  |     public static pointInPolygonCoordinates(x: number, y: number, coordinates: [number, number][][]) { | 
					
						
							|  |  |  |         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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											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
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +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 | 
					
						
							| 
									
										
										
										
											2021-06-24 13:50:35 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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
										 |  |  |     /** | 
					
						
							|  |  |  |      * Generates the closest point on a way from a given point | 
					
						
							| 
									
										
										
										
											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] | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public static nearestPoint(way, 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" | 
					
						
							|  |  |  |             way.geometry.coordinates = way.geometry.coordinates[0] | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-09 00:05:51 +02:00
										 |  |  |         return turf.nearestPointOnLine(way, point, {units: "kilometers"}); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     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) | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // 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) { | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |             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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											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); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             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
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 |