| 
									
										
										
										
											2022-11-02 13:47:34 +01:00
										 |  |  | import { BBox } from "./BBox" | 
					
						
							| 
									
										
										
										
											2021-04-18 14:24:30 +02:00
										 |  |  | import * as turf from "@turf/turf" | 
					
						
							| 
									
										
										
										
											2025-02-06 19:11:30 +01:00
										 |  |  | import { AllGeoJSON, booleanWithin, Coord } from "@turf/turf" | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  | import { | 
					
						
							|  |  |  |     Feature, | 
					
						
							| 
									
										
										
										
											2023-04-16 04:13:09 +02:00
										 |  |  |     FeatureCollection, | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  |     GeoJSON, | 
					
						
							|  |  |  |     LineString, | 
					
						
							| 
									
										
										
										
											2023-03-29 17:21:20 +02:00
										 |  |  |     MultiLineString, | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  |     MultiPolygon, | 
					
						
							|  |  |  |     Point, | 
					
						
							| 
									
										
										
										
											2025-02-06 19:11:30 +01:00
										 |  |  |     Polygon, | 
					
						
							| 
									
										
										
										
											2025-06-04 00:21:28 +02:00
										 |  |  |     Position, | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  | } from "geojson" | 
					
						
							|  |  |  | import { Tiles } from "../Models/TileRange" | 
					
						
							| 
									
										
										
										
											2023-04-20 18:58:31 +02:00
										 |  |  | import { Utils } from "../Utils" | 
					
						
							| 
									
										
										
										
											2025-06-04 00:21:28 +02:00
										 |  |  | ;("use strict") | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-28 23:33:48 +02:00
										 |  |  | export class GeoOperations { | 
					
						
							| 
									
										
										
										
											2025-04-03 02:12:59 +02:00
										 |  |  |     private static readonly _earthRadius: number = 6378137 | 
					
						
							|  |  |  |     private static readonly _originShift: number = (2 * Math.PI * GeoOperations._earthRadius) / 2 | 
					
						
							| 
									
										
										
										
											2023-12-31 14:09:25 +01:00
										 |  |  |     private static readonly directions = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"] as const | 
					
						
							|  |  |  |     private static readonly directionsRelative = [ | 
					
						
							|  |  |  |         "straight", | 
					
						
							|  |  |  |         "slight_right", | 
					
						
							|  |  |  |         "right", | 
					
						
							|  |  |  |         "sharp_right", | 
					
						
							|  |  |  |         "behind", | 
					
						
							|  |  |  |         "sharp_left", | 
					
						
							|  |  |  |         "left", | 
					
						
							| 
									
										
										
										
											2025-03-07 21:53:42 +01:00
										 |  |  |         "slight_left", | 
					
						
							| 
									
										
										
										
											2023-12-31 14:09:25 +01:00
										 |  |  |     ] as const | 
					
						
							|  |  |  |     private static reverseBearing = { | 
					
						
							|  |  |  |         N: 0, | 
					
						
							|  |  |  |         NNE: 22.5, | 
					
						
							|  |  |  |         NE: 45, | 
					
						
							|  |  |  |         ENE: 67.5, | 
					
						
							|  |  |  |         E: 90, | 
					
						
							|  |  |  |         ESE: 112.5, | 
					
						
							|  |  |  |         SE: 135, | 
					
						
							|  |  |  |         SSE: 157.5, | 
					
						
							|  |  |  |         S: 180, | 
					
						
							|  |  |  |         SSW: 202.5, | 
					
						
							|  |  |  |         SW: 225, | 
					
						
							|  |  |  |         WSW: 247.5, | 
					
						
							|  |  |  |         W: 270, | 
					
						
							|  |  |  |         WNW: 292.5, | 
					
						
							|  |  |  |         NW: 315, | 
					
						
							| 
									
										
										
										
											2025-03-07 21:53:42 +01:00
										 |  |  |         NNW: 337.5, | 
					
						
							| 
									
										
										
										
											2023-12-31 14:09:25 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-14 00:08:21 +01:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Create a union between two features | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2024-12-11 02:45:44 +01:00
										 |  |  |     public static union( | 
					
						
							|  |  |  |         f0: Feature<Polygon | MultiPolygon>, | 
					
						
							|  |  |  |         f1: Feature<Polygon | MultiPolygon> | 
					
						
							|  |  |  |     ): Feature<Polygon | MultiPolygon> | null { | 
					
						
							| 
									
										
										
										
											2025-02-06 19:11:30 +01:00
										 |  |  |         return turf.union(turf.featureCollection([f0, f1])) | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-11 02:45:44 +01:00
										 |  |  |     public static intersect( | 
					
						
							| 
									
										
										
										
											2025-01-27 02:32:19 +01:00
										 |  |  |         f0: Readonly<Feature<Polygon | MultiPolygon>>, | 
					
						
							|  |  |  |         f1: Readonly<Feature<Polygon | MultiPolygon>> | 
					
						
							| 
									
										
										
										
											2024-12-11 02:45:44 +01:00
										 |  |  |     ): Feature<Polygon | MultiPolygon> | null { | 
					
						
							| 
									
										
										
										
											2025-02-06 19:11:30 +01:00
										 |  |  |         return turf.intersect(turf.featureCollection([f0, f1])) | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-11 02:12:23 +01:00
										 |  |  |     static surfaceAreaInSqMeters(feature: Feature<Polygon | MultiPolygon>): number { | 
					
						
							| 
									
										
										
										
											2020-06-28 23:33:48 +02:00
										 |  |  |         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 | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2024-12-11 02:12:23 +01:00
										 |  |  |     static centerpoint(feature: Feature): Feature<Point> { | 
					
						
							| 
									
										
										
										
											2025-02-07 22:48:02 +01: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
										 |  |  |     /** | 
					
						
							| 
									
										
										
										
											2024-12-06 01:20:20 +01:00
										 |  |  |      * Returns [lon,lat] coordinates. | 
					
						
							| 
									
										
										
										
											2022-04-28 00:27:21 +02:00
										 |  |  |      * @param feature | 
					
						
							| 
									
										
										
										
											2024-12-06 01:20:20 +01:00
										 |  |  |      * | 
					
						
							|  |  |  |      * GeoOperations.centerpointCoordinates(undefined) // => undefined
 | 
					
						
							| 
									
										
										
										
											2022-04-28 00:27:21 +02:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2024-12-11 02:45:44 +01:00
										 |  |  |     static centerpointCoordinates(feature: undefined | null): undefined | 
					
						
							|  |  |  |     static centerpointCoordinates( | 
					
						
							|  |  |  |         feature: AllGeoJSON | GeoJSON | undefined | 
					
						
							|  |  |  |     ): [number, number] | undefined | 
					
						
							|  |  |  |     static centerpointCoordinates( | 
					
						
							|  |  |  |         feature: NonNullable<AllGeoJSON> | NonNullable<GeoJSON> | 
					
						
							|  |  |  |     ): NonNullable<[number, number]> | 
					
						
							| 
									
										
										
										
											2022-11-07 23:35:17 +01:00
										 |  |  |     static centerpointCoordinates(feature: AllGeoJSON | GeoJSON): [number, number] { | 
					
						
							| 
									
										
										
										
											2024-12-11 02:12:23 +01:00
										 |  |  |         if (feature === undefined || feature === null) { | 
					
						
							| 
									
										
										
										
											2024-12-06 01:20:20 +01:00
										 |  |  |             return undefined | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-02-07 01:09:00 +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 | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2025-06-04 00:21:28 +02:00
										 |  |  |     static distanceBetween( | 
					
						
							|  |  |  |         lonlat0: [number, number] | Coord | Position, | 
					
						
							|  |  |  |         lonlat1: [number, number] | Position | Coord | 
					
						
							|  |  |  |     ) { | 
					
						
							| 
									
										
										
										
											2021-12-07 02:22:56 +01:00
										 |  |  |         return turf.distance(lonlat0, lonlat1, { units: "meters" }) | 
					
						
							| 
									
										
										
										
											2021-03-24 01:25:57 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-30 01:08:07 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Starting on `from`, travels `distance` meters in the direction of the `bearing` (default: 90) | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |     static destination( | 
					
						
							|  |  |  |         from: Coord | [number, number], | 
					
						
							|  |  |  |         distance: number, | 
					
						
							|  |  |  |         bearing: number = 90 | 
					
						
							|  |  |  |     ): [number, number] { | 
					
						
							|  |  |  |         return <[number, number]>( | 
					
						
							|  |  |  |             turf.destination(from, distance, bearing, { units: "meters" }).geometry.coordinates | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2024-09-30 01:08:07 +02: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
 | 
					
						
							| 
									
										
										
										
											2025-02-06 19:11:30 +01:00
										 |  |  |      * lineOverlap[0].overlap // => 158835.70531134616
 | 
					
						
							| 
									
										
										
										
											2022-03-23 19:48:06 +01:00
										 |  |  |      * 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
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2024-12-11 02:45:44 +01:00
										 |  |  |     static calculateOverlap( | 
					
						
							|  |  |  |         feature: Feature, | 
					
						
							|  |  |  |         otherFeatures: Feature[] | 
					
						
							|  |  |  |     ): { feat: Feature; overlap: number }[] { | 
					
						
							| 
									
										
										
										
											2021-03-26 03:24:58 +01:00
										 |  |  |         const featureBBox = BBox.get(feature) | 
					
						
							| 
									
										
										
										
											2024-12-11 02:12:23 +01:00
										 |  |  |         const result: { feat: Feature; overlap: number }[] = [] | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |         if (feature.geometry.type === "Point") { | 
					
						
							| 
									
										
										
										
											2025-02-06 19:11:30 +01:00
										 |  |  |             const coor = <[number, number]>(<Feature<Point>>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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-04 23:42:47 +01:00
										 |  |  |                 const intersection = GeoOperations.calculateIntersection( | 
					
						
							| 
									
										
										
										
											2021-12-06 03:24:33 +01:00
										 |  |  |                     feature, | 
					
						
							|  |  |  |                     otherFeature, | 
					
						
							| 
									
										
										
										
											2024-02-15 17:48:26 +01:00
										 |  |  |                     featureBBox | 
					
						
							| 
									
										
										
										
											2021-12-06 03:24:33 +01:00
										 |  |  |                 ) | 
					
						
							| 
									
										
										
										
											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") { | 
					
						
							| 
									
										
										
										
											2024-12-11 02:45:44 +01:00
										 |  |  |                     if (this.inside(<Feature<Point>>otherFeature, feature)) { | 
					
						
							| 
									
										
										
										
											2021-06-20 03:07:58 +02:00
										 |  |  |                         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
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-04 23:42:47 +01:00
										 |  |  |                 const intersection = this.calculateIntersection(feature, otherFeature, featureBBox) | 
					
						
							| 
									
										
										
										
											2021-06-21 03:12:18 +02:00
										 |  |  |                 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, | 
					
						
							| 
									
										
										
										
											2024-02-15 17:48:26 +01:00
										 |  |  |             ": unsupported type" | 
					
						
							| 
									
										
										
										
											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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-23 19:48:06 +01:00
										 |  |  |     /** | 
					
						
							| 
									
										
										
										
											2023-12-21 17:36:43 +01:00
										 |  |  |      * Detect whether or not the given point is located in the feature | 
					
						
							| 
									
										
										
										
											2022-03-23 19:48:06 +01:00
										 |  |  |      * | 
					
						
							|  |  |  |      * // 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
 | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2023-01-31 14:45:32 +01:00
										 |  |  |     public static inside( | 
					
						
							|  |  |  |         pointCoordinate: [number, number] | Feature<Point>, | 
					
						
							| 
									
										
										
										
											2024-02-15 17:48:26 +01:00
										 |  |  |         feature: Feature | 
					
						
							| 
									
										
										
										
											2023-01-31 14:45:32 +01:00
										 |  |  |     ): 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") { | 
					
						
							| 
									
										
										
										
											2023-01-31 14:45:32 +01:00
										 |  |  |             // The feature that should 'contain' pointCoordinate is a point itself, so it cannot contain anything
 | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  |             return false | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-06-21 03:12:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-29 13:10:57 +01:00
										 |  |  |         if (pointCoordinate["geometry"] !== undefined) { | 
					
						
							|  |  |  |             pointCoordinate = pointCoordinate["geometry"].coordinates | 
					
						
							| 
									
										
										
										
											2021-06-20 03:07:58 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											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") { | 
					
						
							| 
									
										
										
										
											2024-12-11 02:45:44 +01:00
										 |  |  |             const coordinatess: [number, number][][][] = <[number, number][][][]>( | 
					
						
							| 
									
										
										
										
											2025-02-06 19:11:30 +01:00
										 |  |  |                 (<Feature<MultiPolygon>>feature).geometry.coordinates | 
					
						
							| 
									
										
										
										
											2024-12-11 02:45:44 +01:00
										 |  |  |             ) | 
					
						
							| 
									
										
										
										
											2022-02-11 15:27:20 +01:00
										 |  |  |             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") { | 
					
						
							| 
									
										
										
										
											2024-12-11 02:45:44 +01:00
										 |  |  |             return GeoOperations.pointInPolygonCoordinates( | 
					
						
							|  |  |  |                 x, | 
					
						
							|  |  |  |                 y, | 
					
						
							| 
									
										
										
										
											2025-02-06 19:11:30 +01:00
										 |  |  |                 <[number, number][][]>(<Feature<Polygon>>feature).geometry.coordinates | 
					
						
							| 
									
										
										
										
											2024-12-11 02:45:44 +01:00
										 |  |  |             ) | 
					
						
							| 
									
										
										
										
											2022-02-11 15:27:20 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-11 02:12:23 +01:00
										 |  |  |     static lengthInMeters(feature: Feature): number { | 
					
						
							| 
									
										
										
										
											2021-04-18 14:24:30 +02:00
										 |  |  |         return turf.length(feature) * 1000 | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-09-22 05:02:09 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-11 02:45:44 +01:00
										 |  |  |     static buffer( | 
					
						
							|  |  |  |         feature: Feature, | 
					
						
							|  |  |  |         bufferSizeInMeter: number | 
					
						
							|  |  |  |     ): Feature<Polygon | MultiPolygon> | FeatureCollection<Polygon | MultiPolygon> { | 
					
						
							| 
									
										
										
										
											2021-09-22 05:02:09 +02:00
										 |  |  |         return turf.buffer(feature, bufferSizeInMeter / 1000, { | 
					
						
							| 
									
										
										
										
											2025-03-07 21:53:42 +01:00
										 |  |  |             units: "kilometers", | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |         }) | 
					
						
							| 
									
										
										
										
											2021-09-21 02:10:42 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-09-22 05:02:09 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-06 19:11:30 +01:00
										 |  |  |     static bbox(feature: AllGeoJSON): Feature<LineString> { | 
					
						
							| 
									
										
										
										
											2021-09-21 02:10:42 +02:00
										 |  |  |         const [lon, lat, lon0, lat0] = turf.bbox(feature) | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |             type: "Feature", | 
					
						
							| 
									
										
										
										
											2023-03-11 02:35:11 +01:00
										 |  |  |             properties: {}, | 
					
						
							| 
									
										
										
										
											2021-09-21 02:10:42 +02:00
										 |  |  |             geometry: { | 
					
						
							|  |  |  |                 type: "LineString", | 
					
						
							|  |  |  |                 coordinates: [ | 
					
						
							|  |  |  |                     [lon, lat], | 
					
						
							|  |  |  |                     [lon0, lat], | 
					
						
							|  |  |  |                     [lon0, lat0], | 
					
						
							|  |  |  |                     [lon, lat0], | 
					
						
							| 
									
										
										
										
											2025-03-07 21:53:42 +01:00
										 |  |  |                     [lon, lat], | 
					
						
							|  |  |  |                 ], | 
					
						
							|  |  |  |             }, | 
					
						
							| 
									
										
										
										
											2021-09-21 02:10:42 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-06-21 03:12:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-09 00:05:51 +02:00
										 |  |  |     /** | 
					
						
							| 
									
										
										
										
											2023-12-31 14:09:25 +01: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 | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      *  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),
 | 
					
						
							|  |  |  |      // `location`: distance along the line between start (of the line) and the closest point.
 | 
					
						
							|  |  |  |      * @param way The road on which you want to find a point | 
					
						
							|  |  |  |      * @param point Point defined as [lon, lat] | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2023-03-24 19:21:15 +01:00
										 |  |  |     public static nearestPoint( | 
					
						
							| 
									
										
										
										
											2023-04-06 01:33:08 +02:00
										 |  |  |         way: Feature<LineString>, | 
					
						
							| 
									
										
										
										
											2024-02-15 17:48:26 +01:00
										 |  |  |         point: [number, number] | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |     ): Feature< | 
					
						
							|  |  |  |         Point, | 
					
						
							|  |  |  |         { dist: number; index: number; multiFeatureIndex: number; location: number } | 
					
						
							|  |  |  |     > { | 
					
						
							| 
									
										
										
										
											2024-12-11 02:12:23 +01:00
										 |  |  |         return turf.nearestPointOnLine(<Feature<LineString>>way, point, { units: "kilometers" }) | 
					
						
							| 
									
										
										
										
											2023-03-29 17:21:20 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Helper method to reuse the coordinates of the way as LineString. | 
					
						
							|  |  |  |      * Mostly used as helper for 'nearestPoint' | 
					
						
							|  |  |  |      * @param way | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2025-03-07 00:32:20 +01:00
										 |  |  |     public static forceLineString(way: Feature<LineString | Polygon>): Feature<LineString>[] | 
					
						
							| 
									
										
										
										
											2023-12-31 14:09:25 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-20 01:52:23 +02:00
										 |  |  |     public static forceLineString( | 
					
						
							| 
									
										
										
										
											2024-02-15 17:48:26 +01:00
										 |  |  |         way: Feature<MultiLineString | MultiPolygon> | 
					
						
							| 
									
										
										
										
											2025-03-07 00:32:20 +01:00
										 |  |  |     ): Feature<MultiLineString>[] | 
					
						
							| 
									
										
										
										
											2023-12-31 14:09:25 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-29 17:21:20 +02:00
										 |  |  |     public static forceLineString( | 
					
						
							| 
									
										
										
										
											2024-02-15 17:48:26 +01:00
										 |  |  |         way: Feature<LineString | MultiLineString | Polygon | MultiPolygon> | 
					
						
							| 
									
										
										
										
											2025-03-07 00:32:20 +01:00
										 |  |  |     ): Feature<LineString | MultiLineString>[] { | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  |         if (way.geometry.type === "Polygon") { | 
					
						
							| 
									
										
										
										
											2025-03-07 00:32:20 +01:00
										 |  |  |             const poly: Feature<Polygon> = <Feature<Polygon>>way | 
					
						
							| 
									
										
										
										
											2025-06-18 22:31:50 +02:00
										 |  |  |             return poly.geometry.coordinates.map(linestringCoors => { | 
					
						
							| 
									
										
										
										
											2025-03-07 00:32:20 +01:00
										 |  |  |                 return <Feature<LineString>>{ | 
					
						
							|  |  |  |                     type: "Feature", | 
					
						
							|  |  |  |                     geometry: { | 
					
						
							|  |  |  |                         type: "LineString", | 
					
						
							| 
									
										
										
										
											2025-03-07 21:53:42 +01:00
										 |  |  |                         coordinates: linestringCoors, | 
					
						
							| 
									
										
										
										
											2025-03-07 00:32:20 +01:00
										 |  |  |                     }, | 
					
						
							| 
									
										
										
										
											2025-03-07 21:53:42 +01:00
										 |  |  |                     properties: way.properties, | 
					
						
							| 
									
										
										
										
											2025-03-07 00:32:20 +01:00
										 |  |  |                 } | 
					
						
							|  |  |  |             }) | 
					
						
							| 
									
										
										
										
											2021-10-31 02:08:39 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-12-11 02:12:23 +01:00
										 |  |  |         if (way.geometry.type === "MultiPolygon") { | 
					
						
							| 
									
										
										
										
											2025-03-07 00:32:20 +01:00
										 |  |  |             const mpoly: Feature<MultiPolygon> = <Feature<MultiPolygon>>way | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-07 21:53:42 +01:00
										 |  |  |             return [].concat( | 
					
						
							|  |  |  |                 ...mpoly.geometry.coordinates.map((linestrings) => | 
					
						
							|  |  |  |                     [].concat( | 
					
						
							|  |  |  |                         ...linestrings.map( | 
					
						
							|  |  |  |                             (linestring) => | 
					
						
							|  |  |  |                                 <Feature<LineString>>{ | 
					
						
							|  |  |  |                                     type: "Feature", | 
					
						
							|  |  |  |                                     geometry: { | 
					
						
							|  |  |  |                                         type: "LineString", | 
					
						
							|  |  |  |                                         coordinates: linestring, | 
					
						
							|  |  |  |                                     }, | 
					
						
							|  |  |  |                                     properties: way.properties, | 
					
						
							|  |  |  |                                 } | 
					
						
							|  |  |  |                         ) | 
					
						
							|  |  |  |                     ) | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2024-12-11 02:12:23 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |         if (way.geometry.type === "LineString") { | 
					
						
							| 
									
										
										
										
											2025-03-07 00:32:20 +01:00
										 |  |  |             return [<Feature<LineString>>way] | 
					
						
							| 
									
										
										
										
											2024-12-11 02:12:23 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |         if (way.geometry.type === "MultiLineString") { | 
					
						
							| 
									
										
										
										
											2025-03-07 00:32:20 +01:00
										 |  |  |             return [<Feature<MultiLineString>>way] | 
					
						
							| 
									
										
										
										
											2024-12-11 02:12:23 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |         throw "Invalid geometry to create a way from this" | 
					
						
							| 
									
										
										
										
											2021-09-09 00:05:51 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-15 17:48:26 +01:00
										 |  |  |     public static toCSV( | 
					
						
							|  |  |  |         features: Feature[] | FeatureCollection, | 
					
						
							|  |  |  |         options?: { | 
					
						
							|  |  |  |             ignoreTags?: RegExp | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     ): string { | 
					
						
							| 
									
										
										
										
											2021-09-09 00:05:51 +02:00
										 |  |  |         const headerValuesSeen = new Set<string>() | 
					
						
							|  |  |  |         const headerValuesOrdered: string[] = [] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-09 23:05:05 +01:00
										 |  |  |         function addH(key: string) { | 
					
						
							| 
									
										
										
										
											2024-02-14 12:20:29 +01:00
										 |  |  |             if (options?.ignoreTags) { | 
					
						
							|  |  |  |                 if (key.match(options.ignoreTags)) { | 
					
						
							| 
									
										
										
										
											2024-02-09 23:05:05 +01:00
										 |  |  |                     return | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-09-09 00:05:51 +02:00
										 |  |  |             if (!headerValuesSeen.has(key)) { | 
					
						
							|  |  |  |                 headerValuesSeen.add(key) | 
					
						
							|  |  |  |                 headerValuesOrdered.push(key) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         addH("_lat") | 
					
						
							|  |  |  |         addH("_lon") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const lines: string[] = [] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-19 12:16:44 +02:00
										 |  |  |         let _features | 
					
						
							| 
									
										
										
										
											2023-10-30 13:44:27 +01:00
										 |  |  |         if (Array.isArray(features)) { | 
					
						
							| 
									
										
										
										
											2023-10-19 12:16:44 +02:00
										 |  |  |             _features = features | 
					
						
							| 
									
										
										
										
											2023-10-30 13:44:27 +01:00
										 |  |  |         } else { | 
					
						
							| 
									
										
										
										
											2023-10-19 12:16:44 +02:00
										 |  |  |             _features = features.features | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (const feature of _features) { | 
					
						
							| 
									
										
										
										
											2021-09-09 00:05:51 +02:00
										 |  |  |             const properties = feature.properties | 
					
						
							|  |  |  |             for (const key in properties) { | 
					
						
							|  |  |  |                 addH(key) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         headerValuesOrdered.sort() | 
					
						
							| 
									
										
										
										
											2023-10-19 12:16:44 +02:00
										 |  |  |         for (const feature of _features) { | 
					
						
							| 
									
										
										
										
											2021-09-09 00:05:51 +02:00
										 |  |  |             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 | 
					
						
							| 
									
										
										
										
											2025-04-03 02:12:59 +02:00
										 |  |  |         let y = (180 * lat) / GeoOperations._originShift | 
					
						
							| 
									
										
										
										
											2021-10-27 03:52:19 +02:00
										 |  |  |         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. | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |     public static LineIntersections( | 
					
						
							|  |  |  |         feature: Feature<LineString | MultiLineString | Polygon | MultiPolygon>, | 
					
						
							| 
									
										
										
										
											2024-02-15 17:48:26 +01:00
										 |  |  |         otherFeature: Feature<LineString | MultiLineString | Polygon | MultiPolygon> | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |     ): [number, number][] { | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |         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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-17 13:18:00 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Given a list of features, will construct a map of slippy map tile-indices. | 
					
						
							|  |  |  |      * Features of which the BBOX overlaps with the corresponding slippy map tile are added to the corresponding array | 
					
						
							| 
									
										
										
										
											2025-01-27 02:32:19 +01:00
										 |  |  |      * | 
					
						
							|  |  |  |      * Also @see clipAllInBox | 
					
						
							| 
									
										
										
										
											2023-05-17 13:18:00 +02:00
										 |  |  |      * @param features | 
					
						
							|  |  |  |      * @param zoomlevel | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |     public static spreadIntoBboxes(features: Feature[], zoomlevel: number): Map<number, Feature[]> { | 
					
						
							| 
									
										
										
										
											2023-05-17 13:18:00 +02:00
										 |  |  |         const perBbox = new Map<number, Feature[]>() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (const feature of features) { | 
					
						
							|  |  |  |             const bbox = BBox.get(feature) | 
					
						
							|  |  |  |             const tilerange = bbox.expandToTileBounds(zoomlevel).containingTileRange(zoomlevel) | 
					
						
							|  |  |  |             Tiles.MapRange(tilerange, (x, y) => { | 
					
						
							|  |  |  |                 const tileNumber = Tiles.tile_index(zoomlevel, x, y) | 
					
						
							|  |  |  |                 let newFeatureList = perBbox.get(tileNumber) | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |                 if (newFeatureList === undefined) { | 
					
						
							| 
									
										
										
										
											2023-05-17 13:18:00 +02:00
										 |  |  |                     newFeatureList = [] | 
					
						
							|  |  |  |                     perBbox.set(tileNumber, newFeatureList) | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 newFeatureList.push(feature) | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return perBbox | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-11-06 12:44:58 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-27 02:32:19 +01:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Given a list of features, returns a new list of features so that the features are clipped into the given tile-index. | 
					
						
							|  |  |  |      * Note: IDs are rewritten | 
					
						
							|  |  |  |      * Also @see spreadIntoBBoxes | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |     public static clipAllInBox( | 
					
						
							|  |  |  |         features: ReadonlyArray<Readonly<Feature>>, | 
					
						
							|  |  |  |         tileIndex: number | 
					
						
							|  |  |  |     ): Feature[] { | 
					
						
							| 
									
										
										
										
											2025-01-27 02:32:19 +01:00
										 |  |  |         const bbox = Tiles.asGeojson(tileIndex) | 
					
						
							|  |  |  |         const newFeatures: Feature[] = [] | 
					
						
							|  |  |  |         for (const f of features) { | 
					
						
							|  |  |  |             const intersectionParts = GeoOperations.clipWith(f, bbox) | 
					
						
							|  |  |  |             for (let i = 0; i < intersectionParts.length; i++) { | 
					
						
							|  |  |  |                 const intersectionPart = intersectionParts[i] | 
					
						
							|  |  |  |                 let id = (f.properties?.id ?? "") + "_" + tileIndex | 
					
						
							|  |  |  |                 if (i > 0) { | 
					
						
							|  |  |  |                     id += "_part_" + i | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 const properties = { | 
					
						
							|  |  |  |                     ...f.properties, | 
					
						
							| 
									
										
										
										
											2025-03-07 21:53:42 +01:00
										 |  |  |                     id, | 
					
						
							| 
									
										
										
										
											2025-01-27 02:32:19 +01:00
										 |  |  |                 } | 
					
						
							|  |  |  |                 intersectionPart.properties = properties | 
					
						
							|  |  |  |                 newFeatures.push(intersectionPart) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return Utils.NoNull(newFeatures) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-20 18:58:31 +02:00
										 |  |  |     public static toGpx( | 
					
						
							|  |  |  |         locations: | 
					
						
							|  |  |  |             | Feature<LineString> | 
					
						
							|  |  |  |             | Feature<Point, { date?: string; altitude?: number | string }>[], | 
					
						
							| 
									
										
										
										
											2024-02-15 17:48:26 +01:00
										 |  |  |         title?: string | 
					
						
							| 
									
										
										
										
											2023-04-20 18:58:31 +02:00
										 |  |  |     ) { | 
					
						
							|  |  |  |         title = title?.trim() | 
					
						
							|  |  |  |         if (title === undefined || title === "") { | 
					
						
							|  |  |  |             title = "Uploaded with MapComplete" | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         title = Utils.EncodeXmlValue(title) | 
					
						
							|  |  |  |         const trackPoints: string[] = [] | 
					
						
							| 
									
										
										
										
											2025-02-06 19:11:30 +01:00
										 |  |  |         let locationsWithMeta: Feature<Point>[] | 
					
						
							| 
									
										
										
										
											2023-04-20 18:58:31 +02:00
										 |  |  |         if (Array.isArray(locations)) { | 
					
						
							|  |  |  |             locationsWithMeta = locations | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             locationsWithMeta = locations.geometry.coordinates.map( | 
					
						
							|  |  |  |                 (p) => | 
					
						
							|  |  |  |                     <Feature<Point>>{ | 
					
						
							|  |  |  |                         type: "Feature", | 
					
						
							|  |  |  |                         properties: {}, | 
					
						
							|  |  |  |                         geometry: { | 
					
						
							|  |  |  |                             type: "Point", | 
					
						
							| 
									
										
										
										
											2025-03-07 21:53:42 +01:00
										 |  |  |                             coordinates: p, | 
					
						
							|  |  |  |                         }, | 
					
						
							| 
									
										
										
										
											2024-02-15 17:48:26 +01:00
										 |  |  |                     } | 
					
						
							| 
									
										
										
										
											2023-04-20 18:58:31 +02:00
										 |  |  |             ) | 
					
						
							| 
									
										
										
										
											2021-11-08 20:49:51 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-04-20 18:58:31 +02:00
										 |  |  |         for (const l of locationsWithMeta) { | 
					
						
							|  |  |  |             let trkpt = `    <trkpt lat="${l.geometry.coordinates[1]}" lon="${l.geometry.coordinates[0]}">` | 
					
						
							|  |  |  |             if (l.properties.date) { | 
					
						
							|  |  |  |                 trkpt += `        <time>${l.properties.date}</time>` | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (l.properties.altitude) { | 
					
						
							|  |  |  |                 trkpt += `        <ele>${l.properties.altitude}</ele>` | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             trkpt += "    </trkpt>" | 
					
						
							|  |  |  |             trackPoints.push(trkpt) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const header = | 
					
						
							| 
									
										
										
										
											2025-03-07 21:53:42 +01:00
										 |  |  |             '<gpx version="1.1" creator="mapcomplete.org" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">' | 
					
						
							| 
									
										
										
										
											2023-04-20 18:58:31 +02:00
										 |  |  |         return ( | 
					
						
							|  |  |  |             header + | 
					
						
							|  |  |  |             "\n<name>" + | 
					
						
							|  |  |  |             title + | 
					
						
							|  |  |  |             "</name>\n<trk><trkseg>\n" + | 
					
						
							|  |  |  |             trackPoints.join("\n") + | 
					
						
							|  |  |  |             "\n</trkseg></trk></gpx>" | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2021-11-08 20:49:51 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-03 20:03:47 +01:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Given a list of points, convert into a GPX-list, e.g. for favourites | 
					
						
							|  |  |  |      * @param locations | 
					
						
							|  |  |  |      * @param title | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public static toGpxPoints( | 
					
						
							|  |  |  |         locations: Feature<Point, { date?: string; altitude?: number | string }>[], | 
					
						
							| 
									
										
										
										
											2024-02-15 17:48:26 +01:00
										 |  |  |         title?: string | 
					
						
							| 
									
										
										
										
											2023-12-03 20:03:47 +01:00
										 |  |  |     ) { | 
					
						
							|  |  |  |         title = title?.trim() | 
					
						
							|  |  |  |         if (title === undefined || title === "") { | 
					
						
							|  |  |  |             title = "Created with MapComplete" | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-12-03 20:03:47 +01:00
										 |  |  |         title = Utils.EncodeXmlValue(title) | 
					
						
							|  |  |  |         const trackPoints: string[] = [] | 
					
						
							|  |  |  |         for (const l of locations) { | 
					
						
							|  |  |  |             let trkpt = `    <wpt lat="${l.geometry.coordinates[1]}" lon="${l.geometry.coordinates[0]}">` | 
					
						
							|  |  |  |             for (const key in l.properties) { | 
					
						
							|  |  |  |                 const keyCleaned = key.replaceAll(":", "__") | 
					
						
							|  |  |  |                 trkpt += `        <${keyCleaned}>${l.properties[key]}</${keyCleaned}>\n` | 
					
						
							|  |  |  |                 if (key === "website") { | 
					
						
							|  |  |  |                     trkpt += `        <link>${l.properties[key]}</link>\n` | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-12-03 20:03:47 +01:00
										 |  |  |             trkpt += "    </wpt>\n" | 
					
						
							|  |  |  |             trackPoints.push(trkpt) | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-12-03 20:03:47 +01:00
										 |  |  |         const header = | 
					
						
							| 
									
										
										
										
											2025-03-07 21:53:42 +01:00
										 |  |  |             '<gpx version="1.1" creator="mapcomplete.org" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">' | 
					
						
							| 
									
										
										
										
											2023-12-03 20:03:47 +01:00
										 |  |  |         return ( | 
					
						
							|  |  |  |             header + | 
					
						
							|  |  |  |             "\n<name>" + | 
					
						
							|  |  |  |             title + | 
					
						
							|  |  |  |             "</name>\n<trk><trkseg>\n" + | 
					
						
							|  |  |  |             trackPoints.join("\n") + | 
					
						
							|  |  |  |             "\n</trkseg></trk></gpx>" | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2021-11-09 18:41:20 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											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
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2024-12-11 02:12:23 +01:00
										 |  |  |     static removeOvernoding(feature: Feature<LineString | Polygon>) { | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |         if (feature.geometry.type !== "LineString" && feature.geometry.type !== "Polygon") { | 
					
						
							|  |  |  |             throw "Overnode removal is only supported on linestrings and polygons" | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const copy = { | 
					
						
							|  |  |  |             ...feature, | 
					
						
							| 
									
										
										
										
											2025-03-07 21:53:42 +01:00
										 |  |  |             geometry: { ...feature.geometry }, | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |         let coordinates: [number, number][] | 
					
						
							|  |  |  |         if (feature.geometry.type === "LineString") { | 
					
						
							| 
									
										
										
										
											2024-12-11 02:45:44 +01:00
										 |  |  |             coordinates = <[number, number][]>[...feature.geometry.coordinates] | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |             copy.geometry.coordinates = coordinates | 
					
						
							|  |  |  |         } else { | 
					
						
							| 
									
										
										
										
											2024-12-11 02:45:44 +01:00
										 |  |  |             coordinates = <[number, number][]>[...feature.geometry.coordinates[0]] | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |             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 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-14 00:08:21 +01: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) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-19 01:37:31 +02:00
										 |  |  |     public static along(a: Coord, b: Coord, distanceMeter: number): Coord { | 
					
						
							|  |  |  |         return turf.along( | 
					
						
							| 
									
										
										
										
											2024-12-11 02:12:23 +01:00
										 |  |  |             <Feature<LineString>>{ | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |                 type: "Feature", | 
					
						
							|  |  |  |                 geometry: { | 
					
						
							|  |  |  |                     type: "LineString", | 
					
						
							| 
									
										
										
										
											2025-03-07 21:53:42 +01:00
										 |  |  |                     coordinates: [a, b], | 
					
						
							|  |  |  |                 }, | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |             }, | 
					
						
							|  |  |  |             distanceMeter, | 
					
						
							| 
									
										
										
										
											2024-02-15 17:48:26 +01:00
										 |  |  |             { units: "meters" } | 
					
						
							| 
									
										
										
										
											2023-05-19 01:37:31 +02:00
										 |  |  |         ).geometry.coordinates | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-14 00:08:21 +01: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( | 
					
						
							| 
									
										
										
										
											2025-01-27 02:32:19 +01:00
										 |  |  |         feature: Readonly<Feature>, | 
					
						
							|  |  |  |         possiblyEnclosingFeature: Readonly<Feature<Polygon | MultiPolygon>> | 
					
						
							| 
									
										
										
										
											2023-02-14 00:08:21 +01:00
										 |  |  |     ): boolean { | 
					
						
							| 
									
										
										
										
											2025-01-27 02:32:19 +01:00
										 |  |  |         if (feature.geometry.type === "MultiPolygon") { | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |             const polygons = feature.geometry.coordinates.map( | 
					
						
							|  |  |  |                 (coordinates) => | 
					
						
							|  |  |  |                     <Feature<Polygon>>{ | 
					
						
							|  |  |  |                         type: "Feature", | 
					
						
							|  |  |  |                         geometry: { | 
					
						
							|  |  |  |                             type: "Polygon", | 
					
						
							| 
									
										
										
										
											2025-03-07 21:53:42 +01:00
										 |  |  |                             coordinates, | 
					
						
							|  |  |  |                         }, | 
					
						
							| 
									
										
										
										
											2025-01-27 02:32:19 +01:00
										 |  |  |                     } | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |             ) | 
					
						
							|  |  |  |             return !polygons.some((polygon) => !booleanWithin(polygon, possiblyEnclosingFeature)) | 
					
						
							| 
									
										
										
										
											2025-01-27 02:32:19 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  |         return booleanWithin(feature, possiblyEnclosingFeature) | 
					
						
							| 
									
										
										
										
											2023-02-14 00:08:21 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Create an intersection between two features. | 
					
						
							| 
									
										
										
										
											2025-01-27 02:32:19 +01:00
										 |  |  |      * One or multiple new feature are returned based on 'toSplit', which'll have a geometry that is completely withing boundary | 
					
						
							| 
									
										
										
										
											2023-02-14 00:08:21 +01:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |     public static clipWith( | 
					
						
							|  |  |  |         toSplit: Readonly<Feature>, | 
					
						
							|  |  |  |         boundary: Readonly<Feature<Polygon>> | 
					
						
							|  |  |  |     ): Feature[] { | 
					
						
							| 
									
										
										
										
											2023-02-14 00:08:21 +01:00
										 |  |  |         if (toSplit.geometry.type === "Point") { | 
					
						
							|  |  |  |             const p = <Feature<Point>>toSplit | 
					
						
							| 
									
										
										
										
											2023-03-11 02:35:11 +01:00
										 |  |  |             if (GeoOperations.inside(<[number, number]>p.geometry.coordinates, boundary)) { | 
					
						
							| 
									
										
										
										
											2023-02-14 00:08:21 +01:00
										 |  |  |                 return [p] | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 return [] | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (toSplit.geometry.type === "LineString") { | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |             const splitup: Feature<LineString>[] = turf.lineSplit( | 
					
						
							|  |  |  |                 <Feature<LineString>>toSplit, | 
					
						
							|  |  |  |                 boundary | 
					
						
							|  |  |  |             ).features | 
					
						
							| 
									
										
										
										
											2025-01-27 02:32:19 +01:00
										 |  |  |             const kept: Feature[] = [] | 
					
						
							|  |  |  |             for (const f of splitup) { | 
					
						
							| 
									
										
										
										
											2023-02-14 00:08:21 +01:00
										 |  |  |                 if (!GeoOperations.inside(GeoOperations.centerpointCoordinates(f), boundary)) { | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 f.properties = { ...toSplit.properties } | 
					
						
							|  |  |  |                 kept.push(f) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return kept | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-02-02 20:04:39 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if (toSplit.geometry.type === "MultiLineString") { | 
					
						
							| 
									
										
										
										
											2024-02-15 17:48:26 +01:00
										 |  |  |             const lines: Feature<LineString>[][] = toSplit.geometry.coordinates.map( | 
					
						
							|  |  |  |                 (coordinates) => | 
					
						
							|  |  |  |                     turf.lineSplit(<LineString>{ type: "LineString", coordinates }, boundary) | 
					
						
							|  |  |  |                         .features | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2024-02-02 20:04:39 +01:00
										 |  |  |             const splitted: Feature<LineString>[] = [].concat(...lines) | 
					
						
							|  |  |  |             const kept: Feature<LineString>[] = [] | 
					
						
							|  |  |  |             for (const f of splitted) { | 
					
						
							|  |  |  |                 if (!GeoOperations.inside(GeoOperations.centerpointCoordinates(f), boundary)) { | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 f.properties = { ...toSplit.properties } | 
					
						
							|  |  |  |                 kept.push(f) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return kept | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-02-14 00:08:21 +01:00
										 |  |  |         if (toSplit.geometry.type === "Polygon" || toSplit.geometry.type == "MultiPolygon") { | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |             const splitup = turf.intersect( | 
					
						
							|  |  |  |                 turf.featureCollection([<Feature<Polygon | MultiPolygon>>toSplit, boundary]) | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2025-01-27 02:32:19 +01:00
										 |  |  |             if (splitup === null) { | 
					
						
							|  |  |  |                 // No intersection found.
 | 
					
						
							|  |  |  |                 // Either: the boundary is contained fully in 'toSplit', 'toSplit' is contained fully in 'boundary' or they are unrelated at all
 | 
					
						
							|  |  |  |                 if (GeoOperations.completelyWithin(toSplit, boundary)) { | 
					
						
							|  |  |  |                     return [toSplit] | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |                 if ( | 
					
						
							|  |  |  |                     GeoOperations.completelyWithin( | 
					
						
							|  |  |  |                         boundary, | 
					
						
							|  |  |  |                         <Feature<Polygon | MultiPolygon>>toSplit | 
					
						
							|  |  |  |                     ) | 
					
						
							|  |  |  |                 ) { | 
					
						
							|  |  |  |                     return [ | 
					
						
							|  |  |  |                         { | 
					
						
							|  |  |  |                             type: "Feature", | 
					
						
							|  |  |  |                             properties: { ...toSplit.properties }, | 
					
						
							|  |  |  |                             geometry: boundary.geometry, | 
					
						
							| 
									
										
										
										
											2025-03-07 21:53:42 +01:00
										 |  |  |                             bbox: boundary.bbox, | 
					
						
							|  |  |  |                         }, | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |                     ] | 
					
						
							| 
									
										
										
										
											2025-01-27 02:32:19 +01:00
										 |  |  |                 } | 
					
						
							|  |  |  |                 return [] | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-02-14 00:08:21 +01:00
										 |  |  |             splitup.properties = { ...toSplit.properties } | 
					
						
							|  |  |  |             return [splitup] | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         throw "Invalid geometry type with GeoOperations.clipWith: " + toSplit.geometry.type | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-24 19:21:15 +01:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * const f = (type, feature: Feature) => GeoOperations.featureToCoordinateWithRenderingType(feature, type) | 
					
						
							|  |  |  |      * const g = geometry => (<Feature> {type: "Feature", properties: {}, geometry}) | 
					
						
							|  |  |  |      * f("point", g({type:"Point", coordinates:[1,2]})) // => [1,2]
 | 
					
						
							|  |  |  |      * f("centroid", g({type:"Point", coordinates:[1,2]})) // => undefined
 | 
					
						
							|  |  |  |      * f("start", g({type:"Point", coordinates:[1,2]})) // => undefined
 | 
					
						
							|  |  |  |      * f("centroid", g({type:"LineString", coordinates:[[1,2], [3,4]]})) // => [2,3]
 | 
					
						
							|  |  |  |      * f("centroid", g({type:"Polygon", coordinates:[[[1,2], [3,4], [1,2]]]})) // => [2,3]
 | 
					
						
							| 
									
										
										
										
											2025-02-06 19:11:30 +01:00
										 |  |  |      * f("projected_centerpoint", g({type:"LineString", coordinates:[[1,2], [3,4]]})) // => [ 1.9993134785863844, 3.000684536363483]
 | 
					
						
							| 
									
										
										
										
											2023-03-24 19:21:15 +01:00
										 |  |  |      * f("start", g({type:"LineString", coordinates:[[1,2], [3,4]]})) // => [1,2]
 | 
					
						
							|  |  |  |      * f("end", g({type:"LineString", coordinates:[[1,2], [3,4]]})) // => [3,4]
 | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public static featureToCoordinateWithRenderingType( | 
					
						
							|  |  |  |         feature: Feature, | 
					
						
							| 
									
										
										
										
											2024-02-15 17:48:26 +01:00
										 |  |  |         location: | 
					
						
							|  |  |  |             | "point" | 
					
						
							|  |  |  |             | "centroid" | 
					
						
							|  |  |  |             | "start" | 
					
						
							|  |  |  |             | "end" | 
					
						
							|  |  |  |             | "projected_centerpoint" | 
					
						
							|  |  |  |             | "polygon_centerpoint" | 
					
						
							|  |  |  |             | string | 
					
						
							| 
									
										
										
										
											2023-03-24 19:21:15 +01:00
										 |  |  |     ): [number, number] | undefined { | 
					
						
							|  |  |  |         switch (location) { | 
					
						
							|  |  |  |             case "point": | 
					
						
							|  |  |  |                 if (feature.geometry.type === "Point") { | 
					
						
							| 
									
										
										
										
											2025-02-06 19:11:30 +01:00
										 |  |  |                     return <[number, number]>(<Feature<Point>>feature).geometry.coordinates | 
					
						
							| 
									
										
										
										
											2023-03-24 19:21:15 +01:00
										 |  |  |                 } | 
					
						
							|  |  |  |                 return undefined | 
					
						
							|  |  |  |             case "centroid": | 
					
						
							|  |  |  |                 if (feature.geometry.type === "Point") { | 
					
						
							|  |  |  |                     return undefined | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 return GeoOperations.centerpointCoordinates(feature) | 
					
						
							| 
									
										
										
										
											2024-02-20 18:24:25 +01:00
										 |  |  |             case "polygon_centroid": | 
					
						
							| 
									
										
										
										
											2024-02-14 12:20:29 +01:00
										 |  |  |                 if (feature.geometry.type === "Polygon") { | 
					
						
							|  |  |  |                     return GeoOperations.centerpointCoordinates(feature) | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 return undefined | 
					
						
							| 
									
										
										
										
											2023-03-24 19:21:15 +01:00
										 |  |  |             case "projected_centerpoint": | 
					
						
							|  |  |  |                 if ( | 
					
						
							|  |  |  |                     feature.geometry.type === "LineString" || | 
					
						
							|  |  |  |                     feature.geometry.type === "MultiLineString" | 
					
						
							|  |  |  |                 ) { | 
					
						
							|  |  |  |                     const centerpoint = GeoOperations.centerpointCoordinates(feature) | 
					
						
							|  |  |  |                     const projected = GeoOperations.nearestPoint( | 
					
						
							|  |  |  |                         <Feature<LineString>>feature, | 
					
						
							| 
									
										
										
										
											2024-02-15 17:48:26 +01:00
										 |  |  |                         centerpoint | 
					
						
							| 
									
										
										
										
											2023-03-24 19:21:15 +01:00
										 |  |  |                     ) | 
					
						
							|  |  |  |                     return <[number, number]>projected.geometry.coordinates | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 return undefined | 
					
						
							|  |  |  |             case "start": | 
					
						
							|  |  |  |                 if (feature.geometry.type === "LineString") { | 
					
						
							| 
									
										
										
										
											2025-02-06 19:11:30 +01:00
										 |  |  |                     return <[number, number]>(<Feature<LineString>>feature).geometry.coordinates[0] | 
					
						
							| 
									
										
										
										
											2023-03-24 19:21:15 +01:00
										 |  |  |                 } | 
					
						
							|  |  |  |                 return undefined | 
					
						
							|  |  |  |             case "end": | 
					
						
							|  |  |  |                 if (feature.geometry.type === "LineString") { | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |                     return <[number, number]>( | 
					
						
							|  |  |  |                         (<Feature<LineString>>feature).geometry.coordinates.at(-1) | 
					
						
							|  |  |  |                     ) | 
					
						
							| 
									
										
										
										
											2023-03-24 19:21:15 +01:00
										 |  |  |                 } | 
					
						
							|  |  |  |                 return undefined | 
					
						
							|  |  |  |             default: | 
					
						
							| 
									
										
										
										
											2024-11-19 16:42:53 +01:00
										 |  |  |                 throw "Unknown location type: " + location + " for feature " + feature.properties.id | 
					
						
							| 
									
										
										
										
											2023-03-24 19:21:15 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-20 18:58:31 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Creates a linestring object based on the outer ring of the given polygon | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * Returns the argument if not a polygon | 
					
						
							|  |  |  |      * @param p | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public static outerRing<P>(p: Feature<Polygon | LineString, P>): Feature<LineString, P> { | 
					
						
							|  |  |  |         if (p.geometry.type !== "Polygon") { | 
					
						
							|  |  |  |             return <Feature<LineString, P>>p | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |             type: "Feature", | 
					
						
							|  |  |  |             properties: p.properties, | 
					
						
							|  |  |  |             geometry: { | 
					
						
							|  |  |  |                 type: "LineString", | 
					
						
							| 
									
										
										
										
											2025-03-07 21:53:42 +01:00
										 |  |  |                 coordinates: p.geometry.coordinates[0], | 
					
						
							|  |  |  |             }, | 
					
						
							| 
									
										
										
										
											2023-04-20 18:58:31 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     static centerpointCoordinatesObj(geojson: Feature) { | 
					
						
							|  |  |  |         const [lon, lat] = GeoOperations.centerpointCoordinates(geojson) | 
					
						
							|  |  |  |         return { lon, lat } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-31 14:09:25 +01:00
										 |  |  |     public static SplitSelfIntersectingWays(features: Feature[]): Feature[] { | 
					
						
							|  |  |  |         const result: Feature[] = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (const feature of features) { | 
					
						
							|  |  |  |             if (feature.geometry.type === "LineString") { | 
					
						
							| 
									
										
										
										
											2025-02-06 19:11:30 +01:00
										 |  |  |                 let coors = (<Feature<LineString>>feature).geometry.coordinates | 
					
						
							| 
									
										
										
										
											2023-12-31 14:09:25 +01:00
										 |  |  |                 for (let i = coors.length - 1; i >= 0; i--) { | 
					
						
							|  |  |  |                     // Go back, to nick of the back when needed
 | 
					
						
							|  |  |  |                     const ci = coors[i] | 
					
						
							|  |  |  |                     for (let j = i + 1; j < coors.length; j++) { | 
					
						
							|  |  |  |                         const cj = coors[j] | 
					
						
							|  |  |  |                         if ( | 
					
						
							|  |  |  |                             Math.abs(ci[0] - cj[0]) <= 0.000001 && | 
					
						
							|  |  |  |                             Math.abs(ci[1] - cj[1]) <= 0.0000001 | 
					
						
							|  |  |  |                         ) { | 
					
						
							|  |  |  |                             // Found a self-intersecting way!
 | 
					
						
							| 
									
										
										
										
											2025-02-06 19:11:30 +01:00
										 |  |  |                             console.debug("Splitting way", feature.properties.id) | 
					
						
							|  |  |  |                             result.push(<Feature>{ | 
					
						
							| 
									
										
										
										
											2023-12-31 14:09:25 +01:00
										 |  |  |                                 ...feature, | 
					
						
							| 
									
										
										
										
											2025-03-07 21:53:42 +01:00
										 |  |  |                                 geometry: { ...feature.geometry, coordinates: coors.slice(i + 1) }, | 
					
						
							| 
									
										
										
										
											2023-12-31 14:09:25 +01:00
										 |  |  |                             }) | 
					
						
							|  |  |  |                             coors = coors.slice(0, i + 1) | 
					
						
							|  |  |  |                             break | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2025-02-06 19:11:30 +01:00
										 |  |  |                 result.push(<Feature>{ | 
					
						
							| 
									
										
										
										
											2023-12-31 14:09:25 +01:00
										 |  |  |                     ...feature, | 
					
						
							| 
									
										
										
										
											2025-03-07 21:53:42 +01:00
										 |  |  |                     geometry: { ...feature.geometry, coordinates: coors }, | 
					
						
							| 
									
										
										
										
											2023-12-31 14:09:25 +01:00
										 |  |  |                 }) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return result | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							| 
									
										
										
										
											2024-08-21 17:52:22 +02:00
										 |  |  |      * GeoOperations.distanceToHuman(52.3) // => "50m"
 | 
					
						
							| 
									
										
										
										
											2024-08-22 22:50:37 +02:00
										 |  |  |      * GeoOperations.distanceToHuman(999) // => "1.0km"
 | 
					
						
							| 
									
										
										
										
											2023-12-31 14:09:25 +01:00
										 |  |  |      * GeoOperations.distanceToHuman(2800) // => "2.8km"
 | 
					
						
							|  |  |  |      * GeoOperations.distanceToHuman(12800) // => "13km"
 | 
					
						
							| 
									
										
										
										
											2024-08-21 17:52:22 +02:00
										 |  |  |      * GeoOperations.distanceToHuman(128000) // => "130km"
 | 
					
						
							|  |  |  |      * | 
					
						
							| 
									
										
										
										
											2023-12-31 14:09:25 +01:00
										 |  |  |      * | 
					
						
							|  |  |  |      * @param meters | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public static distanceToHuman(meters: number): string { | 
					
						
							|  |  |  |         if (meters === undefined) { | 
					
						
							|  |  |  |             return "" | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |         meters = Utils.roundHuman(Math.round(meters)) | 
					
						
							| 
									
										
										
										
											2023-12-31 14:09:25 +01:00
										 |  |  |         if (meters < 1000) { | 
					
						
							| 
									
										
										
										
											2024-08-21 14:06:42 +02:00
										 |  |  |             return Utils.roundHuman(meters) + "m" | 
					
						
							| 
									
										
										
										
											2023-12-31 14:09:25 +01:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (meters >= 10000) { | 
					
						
							| 
									
										
										
										
											2024-10-19 14:44:55 +02:00
										 |  |  |             const km = Utils.roundHuman(Math.round(meters / 1000)) | 
					
						
							| 
									
										
										
										
											2023-12-31 14:09:25 +01:00
										 |  |  |             return km + "km" | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         meters = Math.round(meters / 100) | 
					
						
							|  |  |  |         const kmStr = "" + meters | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return kmStr.substring(0, kmStr.length - 1) + "." + kmStr.substring(kmStr.length - 1) + "km" | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							| 
									
										
										
										
											2025-04-18 00:25:25 +02:00
										 |  |  |      * Converts various types of possible bearings into degrees, with 0 being north, 90 being east. | 
					
						
							|  |  |  |      * | 
					
						
							| 
									
										
										
										
											2023-12-31 14:09:25 +01:00
										 |  |  |      * GeoOperations.parseBearing("N") // => 0
 | 
					
						
							|  |  |  |      * GeoOperations.parseBearing("E") // => 90
 | 
					
						
							| 
									
										
										
										
											2023-12-31 15:22:39 +01:00
										 |  |  |      * GeoOperations.parseBearing("NE") // => 45
 | 
					
						
							|  |  |  |      * GeoOperations.parseBearing("NNE") // => 22.5
 | 
					
						
							| 
									
										
										
										
											2023-12-31 14:09:25 +01:00
										 |  |  |      * | 
					
						
							|  |  |  |      * GeoOperations.parseBearing("90") // => 90
 | 
					
						
							|  |  |  |      * GeoOperations.parseBearing("-90°") // => 270
 | 
					
						
							|  |  |  |      * GeoOperations.parseBearing("180 °") // => 180
 | 
					
						
							|  |  |  |      * | 
					
						
							| 
									
										
										
										
											2023-12-31 15:22:39 +01:00
										 |  |  |      * GeoOperations.parseBearing(180) // => 180
 | 
					
						
							|  |  |  |      * GeoOperations.parseBearing(-270) // => 90
 | 
					
						
							|  |  |  |      * | 
					
						
							| 
									
										
										
										
											2023-12-31 14:09:25 +01:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2025-04-18 00:25:25 +02:00
										 |  |  |     public static parseBearing(str: string | number): number { | 
					
						
							| 
									
										
										
										
											2023-12-31 14:09:25 +01:00
										 |  |  |         let n: number | 
					
						
							|  |  |  |         if (typeof str === "string") { | 
					
						
							|  |  |  |             str = str.trim() | 
					
						
							|  |  |  |             if (str.endsWith("°")) { | 
					
						
							|  |  |  |                 str = str.substring(0, str.length - 1).trim() | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             n = Number(str) | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             n = str | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (!isNaN(n)) { | 
					
						
							|  |  |  |             while (n < 0) { | 
					
						
							|  |  |  |                 n += 360 | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return n % 360 | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return GeoOperations.reverseBearing[str] | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * GeoOperations.bearingToHuman(0) // => "N"
 | 
					
						
							|  |  |  |      * GeoOperations.bearingToHuman(-9) // => "N"
 | 
					
						
							|  |  |  |      * GeoOperations.bearingToHuman(-10) // => "N"
 | 
					
						
							|  |  |  |      * GeoOperations.bearingToHuman(-180) // => "S"
 | 
					
						
							|  |  |  |      * GeoOperations.bearingToHuman(181) // => "S"
 | 
					
						
							|  |  |  |      * GeoOperations.bearingToHuman(46) // => "NE"
 | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public static bearingToHuman( | 
					
						
							| 
									
										
										
										
											2024-02-15 17:48:26 +01:00
										 |  |  |         bearing: number | 
					
						
							| 
									
										
										
										
											2023-12-31 14:09:25 +01:00
										 |  |  |     ): "N" | "NE" | "E" | "SE" | "S" | "SW" | "W" | "NW" { | 
					
						
							|  |  |  |         while (bearing < 0) { | 
					
						
							|  |  |  |             bearing += 360 | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         bearing %= 360 | 
					
						
							|  |  |  |         bearing += 22.5 | 
					
						
							|  |  |  |         const segment = Math.floor(bearing / 45) % GeoOperations.directions.length | 
					
						
							|  |  |  |         return GeoOperations.directions[segment] | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							| 
									
										
										
										
											2024-01-29 16:43:23 +01:00
										 |  |  |      * GeoOperations.bearingToHumanRelative(-207) // => "sharp_right"
 | 
					
						
							|  |  |  |      * GeoOperations.bearingToHumanRelative(-199) // => "behind"
 | 
					
						
							|  |  |  |      * GeoOperations.bearingToHumanRelative(-180) // => "behind"
 | 
					
						
							|  |  |  |      * GeoOperations.bearingToHumanRelative(-10) // => "straight"
 | 
					
						
							|  |  |  |      * GeoOperations.bearingToHumanRelative(0) // => "straight"
 | 
					
						
							|  |  |  |      * GeoOperations.bearingToHumanRelative(181) // => "behind"
 | 
					
						
							|  |  |  |      * GeoOperations.bearingToHumanRelative(40) // => "slight_right"
 | 
					
						
							|  |  |  |      * GeoOperations.bearingToHumanRelative(46) // => "slight_right"
 | 
					
						
							|  |  |  |      * GeoOperations.bearingToHumanRelative(95) // => "right"
 | 
					
						
							|  |  |  |      * GeoOperations.bearingToHumanRelative(140) // => "sharp_right"
 | 
					
						
							|  |  |  |      * GeoOperations.bearingToHumanRelative(158) // => "behind"
 | 
					
						
							|  |  |  |      * | 
					
						
							| 
									
										
										
										
											2023-12-31 14:09:25 +01:00
										 |  |  |      */ | 
					
						
							|  |  |  |     public static bearingToHumanRelative( | 
					
						
							| 
									
										
										
										
											2024-02-15 17:48:26 +01:00
										 |  |  |         bearing: number | 
					
						
							| 
									
										
										
										
											2023-12-31 14:09:25 +01:00
										 |  |  |     ): | 
					
						
							|  |  |  |         | "straight" | 
					
						
							|  |  |  |         | "slight_right" | 
					
						
							|  |  |  |         | "right" | 
					
						
							|  |  |  |         | "sharp_right" | 
					
						
							|  |  |  |         | "behind" | 
					
						
							|  |  |  |         | "sharp_left" | 
					
						
							|  |  |  |         | "left" | 
					
						
							|  |  |  |         | "slight_left" { | 
					
						
							|  |  |  |         while (bearing < 0) { | 
					
						
							|  |  |  |             bearing += 360 | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         bearing %= 360 | 
					
						
							|  |  |  |         bearing += 22.5 | 
					
						
							|  |  |  |         const segment = Math.floor(bearing / 45) % GeoOperations.directionsRelative.length | 
					
						
							|  |  |  |         return GeoOperations.directionsRelative[segment] | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-02 20:04:39 +01:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * const coors = [[[3.217198532946432,51.218067],[3.216807134449482,51.21849812105347],[3.2164304037883706,51.2189272]],[[3.2176208,51.21760169669458],[3.217198560167068,51.218067]]] | 
					
						
							|  |  |  |      * const f = <any> {geometry: {coordinates: coors}} | 
					
						
							|  |  |  |      * const merged = GeoOperations.attemptLinearize(f) | 
					
						
							|  |  |  |      * merged.geometry.coordinates // => [[3.2176208,51.21760169669458],[3.217198532946432,51.218067], [3.216807134449482,51.21849812105347],[3.2164304037883706,51.2189272]]
 | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2024-02-15 17:48:26 +01:00
										 |  |  |     static attemptLinearize( | 
					
						
							|  |  |  |         multiLineStringFeature: Feature<MultiLineString> | 
					
						
							|  |  |  |     ): Feature<LineString | MultiLineString> { | 
					
						
							| 
									
										
										
										
											2024-02-02 20:04:39 +01:00
										 |  |  |         const coors = multiLineStringFeature.geometry.coordinates | 
					
						
							| 
									
										
										
										
											2024-02-15 17:48:26 +01:00
										 |  |  |         if (coors.length === 0) { | 
					
						
							| 
									
										
										
										
											2024-02-02 20:04:39 +01:00
										 |  |  |             console.error(multiLineStringFeature.geometry) | 
					
						
							|  |  |  |             throw "Error: got degenerate multilinestring" | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         outer: for (let i = coors.length - 1; i >= 0; i--) { | 
					
						
							|  |  |  |             // We try to match the first element of 'i' with another, earlier list `j`
 | 
					
						
							|  |  |  |             // If a match is found with `j`, j is extended and `i` is scrapped
 | 
					
						
							|  |  |  |             const iFirst = coors[i][0] | 
					
						
							|  |  |  |             for (let j = 0; j < coors.length; j++) { | 
					
						
							|  |  |  |                 if (i == j) { | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 const jLast = coors[j].at(-1) | 
					
						
							| 
									
										
										
										
											2024-02-15 17:48:26 +01:00
										 |  |  |                 if ( | 
					
						
							|  |  |  |                     !( | 
					
						
							|  |  |  |                         Math.abs(iFirst[0] - jLast[0]) < 0.000001 && | 
					
						
							|  |  |  |                         Math.abs(iFirst[1] - jLast[1]) < 0.0000001 | 
					
						
							|  |  |  |                     ) | 
					
						
							|  |  |  |                 ) { | 
					
						
							| 
									
										
										
										
											2024-02-02 20:04:39 +01:00
										 |  |  |                     continue | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 coors[j].splice(coors.length - 1, 1) | 
					
						
							|  |  |  |                 coors[j].push(...coors[i]) | 
					
						
							|  |  |  |                 coors.splice(i, 1) | 
					
						
							|  |  |  |                 continue outer | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-02-15 17:48:26 +01:00
										 |  |  |         if (coors.length === 0) { | 
					
						
							| 
									
										
										
										
											2024-02-02 20:04:39 +01:00
										 |  |  |             throw "No more coordinates found" | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (coors.length === 1) { | 
					
						
							|  |  |  |             return { | 
					
						
							|  |  |  |                 type: "Feature", | 
					
						
							|  |  |  |                 properties: multiLineStringFeature.properties, | 
					
						
							|  |  |  |                 geometry: { | 
					
						
							|  |  |  |                     type: "LineString", | 
					
						
							| 
									
										
										
										
											2025-03-07 21:53:42 +01:00
										 |  |  |                     coordinates: coors[0], | 
					
						
							|  |  |  |                 }, | 
					
						
							| 
									
										
										
										
											2024-02-02 20:04:39 +01:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |             type: "Feature", | 
					
						
							|  |  |  |             properties: multiLineStringFeature.properties, | 
					
						
							|  |  |  |             geometry: { | 
					
						
							|  |  |  |                 type: "MultiLineString", | 
					
						
							| 
									
										
										
										
											2025-03-07 21:53:42 +01:00
										 |  |  |                 coordinates: coors, | 
					
						
							|  |  |  |             }, | 
					
						
							| 
									
										
										
										
											2024-02-02 20:04:39 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Helper function which does the heavy lifting for 'inside' | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private static pointInPolygonCoordinates( | 
					
						
							|  |  |  |         x: number, | 
					
						
							|  |  |  |         y: number, | 
					
						
							| 
									
										
										
										
											2024-02-15 17:48:26 +01:00
										 |  |  |         coordinates: [number, number][][] | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  |     ): boolean { | 
					
						
							|  |  |  |         const inside = GeoOperations.pointWithinRing( | 
					
						
							|  |  |  |             x, | 
					
						
							|  |  |  |             y, | 
					
						
							| 
									
										
										
										
											2024-02-15 17:48:26 +01:00
										 |  |  |             /*This is the outer ring of the polygon */ coordinates[0] | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  |         ) | 
					
						
							|  |  |  |         if (!inside) { | 
					
						
							|  |  |  |             return false | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         for (let i = 1; i < coordinates.length; i++) { | 
					
						
							|  |  |  |             const inHole = GeoOperations.pointWithinRing( | 
					
						
							|  |  |  |                 x, | 
					
						
							|  |  |  |                 y, | 
					
						
							| 
									
										
										
										
											2024-02-15 17:48:26 +01:00
										 |  |  |                 coordinates[i] /* These are inner rings, aka holes*/ | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  |             ) | 
					
						
							|  |  |  |             if (inHole) { | 
					
						
							|  |  |  |                 return false | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return true | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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 | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2024-01-04 23:42:47 +01:00
										 |  |  |     private static calculateIntersection( | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |         feature, | 
					
						
							|  |  |  |         otherFeature, | 
					
						
							|  |  |  |         featureBBox: BBox, | 
					
						
							| 
									
										
										
										
											2024-02-15 17:48:26 +01:00
										 |  |  |         otherFeatureBBox?: BBox | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |     ): 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
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-11 02:12:23 +01:00
										 |  |  |             const intersectionPoints = turf.lineIntersect(feature, otherFeature) | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |             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 | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2024-12-11 02:12:23 +01:00
										 |  |  |             const intersectionPointsArray = intersectionPoints.features.map((d) => { | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |                 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]) | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-11 02:12:23 +01:00
										 |  |  |             const intersection = turf.lineSlice( | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |                 turf.point(intersectionPointsArray[0]), | 
					
						
							|  |  |  |                 turf.point(intersectionPointsArray[1]), | 
					
						
							| 
									
										
										
										
											2024-02-15 17:48:26 +01:00
										 |  |  |                 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") { | 
					
						
							| 
									
										
										
										
											2024-01-04 23:42:47 +01:00
										 |  |  |                 return this.calculateIntersection( | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |                     otherFeature, | 
					
						
							|  |  |  |                     feature, | 
					
						
							|  |  |  |                     otherFeatureBBox, | 
					
						
							| 
									
										
										
										
											2024-02-15 17:48:26 +01:00
										 |  |  |                     featureBBox | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |                 ) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             try { | 
					
						
							| 
									
										
										
										
											2025-02-06 19:11:30 +01:00
										 |  |  |                 const intersection = turf.intersect(turf.featureCollection([feature, otherFeature])) | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |                 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 | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2024-01-04 23:42:47 +01:00
										 |  |  |                 if (e.message.indexOf("SweepLine tree") >= 0) { | 
					
						
							|  |  |  |                     console.log("Applying fallback intersection...") | 
					
						
							| 
									
										
										
										
											2025-02-10 02:04:58 +01:00
										 |  |  |                     const intersection = turf.intersect( | 
					
						
							|  |  |  |                         turf.featureCollection([ | 
					
						
							| 
									
										
										
										
											2025-02-06 19:11:30 +01:00
										 |  |  |                             turf.truncate(feature), | 
					
						
							| 
									
										
										
										
											2025-03-07 21:53:42 +01:00
										 |  |  |                             turf.truncate(otherFeature), | 
					
						
							| 
									
										
										
										
											2025-02-06 19:11:30 +01:00
										 |  |  |                         ]) | 
					
						
							| 
									
										
										
										
											2024-01-04 23:42:47 +01:00
										 |  |  |                     ) | 
					
						
							|  |  |  |                     if (intersection == null) { | 
					
						
							|  |  |  |                         return null | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     return turf.area(intersection) // in m²
 | 
					
						
							|  |  |  |                     // Another workaround: https://github.com/Turfjs/turf/issues/2258
 | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-06 14:39:42 +01:00
										 |  |  |                 throw e | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         throw "CalculateIntersection fallthrough: can not calculate an intersection between features" | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-06-24 00:35:19 +02:00
										 |  |  | } |