2020-06-28 23:33:48 +02:00
import * as turf from 'turf'
2020-06-24 00:35:19 +02:00
2020-06-28 23:33:48 +02:00
export class GeoOperations {
2020-06-24 00:35:19 +02:00
2020-06-28 23:33:48 +02:00
static surfaceAreaInSqMeters ( feature : any ) {
return turf . area ( feature ) ;
}
2020-06-24 00:35:19 +02:00
2020-07-22 00:50:30 +02:00
static centerpoint ( feature : any )
{
const newFeature = turf . center ( feature ) ;
newFeature . properties = feature . properties ;
newFeature . id = feature . id ;
return newFeature ;
}
2020-06-28 23:33:48 +02:00
static featureIsContainedInAny ( feature : any ,
shouldNotContain : any [ ] ,
maxOverlapPercentage : number ) : boolean {
// Returns 'false' if no problematic intersection is found
2020-06-24 00:35:19 +02:00
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 ;
}
2020-06-28 23:33:48 +02:00
if ( feature . geometry . type === "Polygon" || feature . geometry . type === "MultiPolygon" ) {
2020-06-24 00:35:19 +02:00
const poly = feature ;
2020-06-28 23:33:48 +02:00
let featureBBox = BBox . get ( feature ) ;
const featureSurface = GeoOperations . surfaceAreaInSqMeters ( poly ) ;
2020-06-24 00:35:19 +02:00
for ( const shouldNotContainElement of shouldNotContain ) {
2020-06-28 23:33:48 +02:00
const shouldNotContainBBox = BBox . get ( shouldNotContainElement ) ;
const overlaps = featureBBox . overlapsWith ( shouldNotContainBBox )
if ( ! overlaps ) {
2020-06-24 00:35:19 +02:00
continue ;
}
2020-06-28 23:33:48 +02:00
// Calculate the surface area of the intersection
// If it is too big, refuse
try {
const intersection = turf . intersect ( poly , shouldNotContainElement ) ;
if ( intersection == null ) {
continue ;
2020-06-24 00:35:19 +02:00
}
2020-06-28 23:33:48 +02:00
const intersectionSize = turf . area ( intersection ) ;
const ratio = intersectionSize / featureSurface ;
if ( ratio * 100 >= maxOverlapPercentage ) {
console . log ( "Refused" , poly . id , " due to " , shouldNotContainElement . id , "intersection ratio is " , ratio , "which is bigger then the target ratio of " , ( maxOverlapPercentage / 100 ) )
2020-06-24 00:35:19 +02:00
return true ;
}
2020-06-28 23:33:48 +02:00
} catch ( exception ) {
console . log ( "EXCEPTION CAUGHT WHILE INTERSECTING: " , exception ) ;
// We assume that this failed due to an intersection
return true ;
2020-06-24 00:35:19 +02:00
}
2020-06-28 23:33:48 +02:00
2020-06-24 00:35:19 +02:00
}
2020-06-28 23:33:48 +02:00
return false ; // No problematic intersections found
2020-06-24 00:35:19 +02:00
}
return false ;
}
2020-06-28 23:33:48 +02:00
private static inside ( pointCoordinate , feature ) : boolean {
2020-06-24 00:35:19 +02:00
// ray-casting algorithm based on
// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
if ( feature . geometry . type === "Point" ) {
return false ;
}
const x : number = pointCoordinate [ 0 ] ;
const y : number = pointCoordinate [ 1 ] ;
let poly = feature . geometry . coordinates [ 0 ] ;
var inside = false ;
2020-08-30 01:13:18 +02:00
for ( let i = 0 , j = poly . length - 1 ; i < poly . length ; j = i ++ ) {
2020-06-24 00:35:19 +02:00
const coori = poly [ i ] ;
const coorj = poly [ j ] ;
const xi = coori [ 0 ] ;
const yi = coori [ 1 ] ;
const xj = coorj [ 0 ] ;
const yj = coorj [ 1 ] ;
2020-08-30 01:13:18 +02:00
const intersect = ( ( yi > y ) != ( yj > y ) )
2020-06-24 00:35:19 +02:00
&& ( x < ( xj - xi ) * ( y - yi ) / ( yj - yi ) + xi ) ;
if ( intersect ) {
inside = ! inside ;
}
}
return inside ;
} ;
}
2020-08-30 01:13:18 +02:00
class BBox {
2020-06-24 00:35:19 +02:00
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 ] ) ;
}
2020-06-28 23:33:48 +02:00
this . check ( ) ;
2020-06-24 00:35:19 +02:00
}
2020-06-28 23:33:48 +02:00
private check() {
if ( isNaN ( this . maxLon ) || isNaN ( this . maxLat ) || isNaN ( this . minLon ) || isNaN ( this . minLat ) ) {
console . log ( this ) ;
throw "BBOX has NAN" ;
}
}
2020-06-24 00:35:19 +02:00
2020-06-28 23:33:48 +02:00
public overlapsWith ( other : BBox ) {
this . check ( ) ;
other . check ( ) ;
2020-06-24 00:35:19 +02:00
if ( this . maxLon < other . minLon ) {
return false ;
}
if ( this . maxLat < other . minLat ) {
return false ;
}
if ( this . minLon > other . maxLon ) {
return false ;
}
2020-08-30 01:13:18 +02:00
return this . minLat <= other . maxLat ;
2020-06-24 00:35:19 +02:00
}
static get ( feature ) {
if ( feature . bbox === undefined ) {
2020-06-28 23:33:48 +02:00
if ( feature . geometry . type === "MultiPolygon" ) {
let coordinates = [ ] ;
for ( const coorlist of feature . geometry . coordinates ) {
coordinates = coordinates . concat ( coorlist [ 0 ] ) ;
}
feature . bbox = new BBox ( coordinates ) ;
} else if ( feature . geometry . type === "Polygon" ) {
2020-06-24 00:35:19 +02:00
feature . bbox = new BBox ( feature . geometry . coordinates [ 0 ] ) ;
} else if ( feature . geometry . type === "LineString" ) {
feature . bbox = new BBox ( feature . geometry . coordinates ) ;
2020-06-28 23:33:48 +02:00
} else if ( feature . geometry . type === "Point" ) {
2020-06-24 00:35:19 +02:00
// Point
feature . bbox = new BBox ( [ feature . geometry . coordinates ] ) ;
2020-06-28 23:33:48 +02:00
} else {
throw "Cannot calculate bbox, unknown type " + feature . geometry . type ;
2020-06-24 00:35:19 +02:00
}
}
return feature . bbox ;
}
}