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;
|
||
|
}
|
||
|
|
||
|
}
|