forked from MapComplete/MapComplete
		
	
		
			
	
	
		
			171 lines
		
	
	
	
		
			4.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			171 lines
		
	
	
	
		
			4.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
|  | export class GeoOperations { | ||
|  | 
 | ||
|  | 
 | ||
|  |     static featureIsContainedInAny(feature: any, shouldNotContain: any[], noTouching: boolean = false): boolean { | ||
|  | 
 | ||
|  |         if (feature.geometry.type === "Point") { | ||
|  |             const coor = feature.geometry.coordinates; | ||
|  |             for (const shouldNotContainElement of shouldNotContain) { | ||
|  | 
 | ||
|  |                 let shouldNotContainBBox = BBox.get(shouldNotContainElement); | ||
|  |                 let featureBBox = BBox.get(feature); | ||
|  |                 if (!featureBBox.overlapsWith(shouldNotContainBBox)) { | ||
|  |                     continue; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 if (this.inside(coor, shouldNotContainElement)) { | ||
|  |                     return true | ||
|  |                 } | ||
|  |             } | ||
|  |             return false; | ||
|  |         } | ||
|  | 
 | ||
|  | 
 | ||
|  |         if (feature.geometry.type === "Polygon") { | ||
|  | 
 | ||
|  |             const poly = feature; | ||
|  |             for (const shouldNotContainElement of shouldNotContain) { | ||
|  | 
 | ||
|  |                 let shouldNotContainBBox = BBox.get(shouldNotContainElement); | ||
|  |                 let featureBBox = BBox.get(feature); | ||
|  |                 if (!featureBBox.overlapsWith(shouldNotContainBBox)) { | ||
|  |                     continue; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 if (noTouching) { | ||
|  |                     if (GeoOperations.isPolygonTouching(poly, shouldNotContainElement)) { | ||
|  |                         return true; | ||
|  |                     } | ||
|  |                 } else { | ||
|  |                     if (GeoOperations.isPolygonInside(poly, shouldNotContainElement)) { | ||
|  |                         return true; | ||
|  |                     } | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         return false; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Simple check: that every point of the polygon is inside the container | ||
|  |      * @param polygon | ||
|  |      * @param container | ||
|  |      */ | ||
|  |     static isPolygonInside(polygon, container) { | ||
|  |         for (const coor of polygon.geometry.coordinates[0]) { | ||
|  |             if (!GeoOperations.inside(coor, container)) { | ||
|  |                 return false; | ||
|  |             } | ||
|  |         } | ||
|  |         return true; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Simple check: one point of the polygon is inside the container | ||
|  |      * @param polygon | ||
|  |      * @param container | ||
|  |      */ | ||
|  |     static isPolygonTouching(polygon, container) { | ||
|  |         for (const coor of polygon.geometry.coordinates[0]) { | ||
|  |             if (GeoOperations.inside(coor, container)) { | ||
|  |                 return true; | ||
|  |             } | ||
|  |         } | ||
|  |         return false; | ||
|  |     } | ||
|  | 
 | ||
|  | 
 | ||
|  |     static inside(pointCoordinate, feature): boolean { | ||
|  |         // ray-casting algorithm based on
 | ||
|  |         // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
 | ||
|  | 
 | ||
|  |         if (feature.geometry.type === "Point") { | ||
|  |             return false; | ||
|  |         } | ||
|  | 
 | ||
|  | 
 | ||
|  |         const x: number = pointCoordinate[0]; | ||
|  |         const y: number = pointCoordinate[1]; | ||
|  | 
 | ||
|  | 
 | ||
|  |         let poly = feature.geometry.coordinates[0]; | ||
|  | 
 | ||
|  |         var inside = false; | ||
|  |         for (var i = 0, j = poly.length - 1; i < poly.length; j = i++) { | ||
|  |             const coori = poly[i]; | ||
|  |             const coorj = poly[j]; | ||
|  | 
 | ||
|  |             const xi = coori[0]; | ||
|  |             const yi = coori[1]; | ||
|  |             const xj = coorj[0]; | ||
|  |             const yj = coorj[1]; | ||
|  | 
 | ||
|  |             var intersect = ((yi > y) != (yj > y)) | ||
|  |                 && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); | ||
|  |             if (intersect) { | ||
|  |                 inside = !inside; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         return inside; | ||
|  |     }; | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | class BBox { | ||
|  | 
 | ||
|  |     readonly maxLat: number; | ||
|  |     readonly maxLon: number; | ||
|  |     readonly minLat: number; | ||
|  |     readonly minLon: number; | ||
|  | 
 | ||
|  |     constructor(coordinates) { | ||
|  |         this.maxLat = Number.MIN_VALUE; | ||
|  |         this.maxLon = Number.MIN_VALUE; | ||
|  |         this.minLat = Number.MAX_VALUE; | ||
|  |         this.minLon = Number.MAX_VALUE; | ||
|  | 
 | ||
|  | 
 | ||
|  |         for (const coordinate of coordinates) { | ||
|  |             this.maxLon = Math.max(this.maxLon, coordinate[0]); | ||
|  |             this.maxLat = Math.max(this.maxLat, coordinate[1]); | ||
|  |             this.minLon = Math.min(this.minLon, coordinate[0]); | ||
|  |             this.minLat = Math.min(this.minLat, coordinate[1]); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     public overlapsWith(other: BBox) { | ||
|  | 
 | ||
|  |         if (this.maxLon < other.minLon) { | ||
|  |             return false; | ||
|  |         } | ||
|  |         if (this.maxLat < other.minLat) { | ||
|  |             return false; | ||
|  |         } | ||
|  |         if (this.minLon > other.maxLon) { | ||
|  |             return false; | ||
|  |         } | ||
|  |         if (this.minLat > other.maxLat) { | ||
|  |             return false; | ||
|  |         } | ||
|  |         return true; | ||
|  |     } | ||
|  | 
 | ||
|  |     static get(feature) { | ||
|  |         if (feature.bbox === undefined) { | ||
|  |             if (feature.geometry.type === "Polygon") { | ||
|  |                 feature.bbox = new BBox(feature.geometry.coordinates[0]); | ||
|  |             } else if (feature.geometry.type === "LineString") { | ||
|  |                 feature.bbox = new BBox(feature.geometry.coordinates); | ||
|  |             } else { | ||
|  |                 // Point
 | ||
|  |                 feature.bbox = new BBox([feature.geometry.coordinates]); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         return feature.bbox; | ||
|  |     } | ||
|  | 
 | ||
|  | } |