package org.apache.lucene.geo;
import java.util.Arrays;
import java.util.Comparator;
import org.apache.lucene.index.PointValues.Relation;
import org.apache.lucene.util.ArrayUtil;
import static org.apache.lucene.geo.GeoUtils.lineCrossesLine;
import static org.apache.lucene.geo.GeoUtils.lineCrossesLineWithBoundary;
import static org.apache.lucene.geo.GeoUtils.orient;
public abstract class EdgeTree {
public final double minLat;
public final double maxLat;
public final double minLon;
public final double maxLon;
protected double maxY;
protected double maxX;
protected boolean splitX;
protected EdgeTree left;
protected EdgeTree right;
protected final Edge tree;
protected EdgeTree(final double minLat, final double maxLat, final double minLon, final double maxLon, double[] lats, double[] lons) {
this.minLat = minLat;
this.maxLat = maxLat;
this.minLon = minLon;
this.maxLon = maxLon;
this.maxY = maxLat;
this.maxX = maxLon;
this.tree = createTree(lats, lons);
}
public Relation relateTriangle(double ax, double ay, double bx, double by, double cx, double cy) {
double triMinLat = StrictMath.min(StrictMath.min(ay, by), cy);
double triMinLon = StrictMath.min(StrictMath.min(ax, bx), cx);
if (triMinLat <= maxY && triMinLon <= maxX) {
Relation relation = internalComponentRelateTriangle(ax, ay, bx, by, cx, cy);
if (relation != Relation.CELL_OUTSIDE_QUERY) {
return relation;
}
if (left != null) {
relation = left.relateTriangle(ax, ay, bx, by, cx, cy);
if (relation != Relation.CELL_OUTSIDE_QUERY) {
return relation;
}
}
double triMaxLat = StrictMath.max(StrictMath.max(ay, by), cy);
double triMaxLon = StrictMath.max(StrictMath.max(ax, bx), cx);
if (right != null && ((splitX == false && triMaxLat >= this.minLat) || (splitX && triMaxLon >= this.minLon))) {
relation = right.relateTriangle(ax, ay, bx, by, cx, cy);
if (relation != Relation.CELL_OUTSIDE_QUERY) {
return relation;
}
}
}
return Relation.CELL_OUTSIDE_QUERY;
}
public Relation relate(double minLat, double maxLat, double minLon, double maxLon) {
if (minLat <= maxY && minLon <= maxX) {
Relation relation = internalComponentRelate(minLat, maxLat, minLon, maxLon);
if (relation != Relation.CELL_OUTSIDE_QUERY) {
return relation;
}
if (left != null) {
relation = left.relate(minLat, maxLat, minLon, maxLon);
if (relation != Relation.CELL_OUTSIDE_QUERY) {
return relation;
}
}
if (right != null && ((splitX == false && maxLat >= this.minLat) || (splitX && maxLon >= this.minLon))) {
relation = right.relate(minLat, maxLat, minLon, maxLon);
if (relation != Relation.CELL_OUTSIDE_QUERY) {
return relation;
}
}
}
return Relation.CELL_OUTSIDE_QUERY;
}
protected abstract Relation componentRelate(double minLat, double maxLat, double minLon, double maxLon);
protected abstract Relation componentRelateTriangle(double ax, double ay, double bx, double by, double cx, double cy);
private Relation internalComponentRelateTriangle(double ax, double ay, double bx, double by, double cx, double cy) {
double minLat = StrictMath.min(StrictMath.min(ay, by), cy);
double minLon = StrictMath.min(StrictMath.min(ax, bx), cx);
double maxLat = StrictMath.max(StrictMath.max(ay, by), cy);
double maxLon = StrictMath.max(StrictMath.max(ax, bx), cx);
if (maxLon < this.minLon || minLon > this.maxLon || maxLat < this.minLat || minLat > this.maxLat) {
return Relation.CELL_OUTSIDE_QUERY;
} else if (bx == cx && by == cy) {
return componentRelateTriangle(bx, by, ax, ay, cx, cy);
} else if (ax == bx && ay == by) {
return componentRelateTriangle(bx, by, cx, cy, ax, ay);
}
return componentRelateTriangle(ax, ay, bx, by, cx, cy);
}
protected Relation internalComponentRelate(double minLat, double maxLat, double minLon, double maxLon) {
if (maxLon < this.minLon || minLon > this.maxLon || maxLat < this.minLat || minLat > this.maxLat) {
return Relation.CELL_OUTSIDE_QUERY;
}
if (minLat <= this.minLat && maxLat >= this.maxLat && minLon <= this.minLon && maxLon >= this.maxLon) {
return Relation.CELL_CROSSES_QUERY;
}
return componentRelate(minLat, maxLat, minLon, maxLon);
}
protected static EdgeTree createTree(EdgeTree components[], int low, int high, boolean splitX) {
if (low > high) {
return null;
}
final int mid = (low + high) >>> 1;
if (low < high) {
Comparator<EdgeTree> comparator;
if (splitX) {
comparator = (left, right) -> {
int ret = Double.compare(left.minLon, right.minLon);
if (ret == 0) {
ret = Double.compare(left.maxX, right.maxX);
}
return ret;
};
} else {
comparator = (left, right) -> {
int ret = Double.compare(left.minLat, right.minLat);
if (ret == 0) {
ret = Double.compare(left.maxY, right.maxY);
}
return ret;
};
}
ArrayUtil.select(components, low, high + 1, mid, comparator);
}
EdgeTree newNode = components[mid];
newNode.splitX = splitX;
newNode.left = createTree(components, low, mid - 1, !splitX);
newNode.right = createTree(components, mid + 1, high, !splitX);
if (newNode.left != null) {
newNode.maxX = Math.max(newNode.maxX, newNode.left.maxX);
newNode.maxY = Math.max(newNode.maxY, newNode.left.maxY);
}
if (newNode.right != null) {
newNode.maxX = Math.max(newNode.maxX, newNode.right.maxX);
newNode.maxY = Math.max(newNode.maxY, newNode.right.maxY);
}
return newNode;
}
static class Edge {
final double lat1, lat2;
final double lon1, lon2;
final boolean dateline;
final double low;
double max;
Edge left;
Edge right;
Edge(double lat1, double lon1, double lat2, double lon2, double low, double max) {
this.lat1 = lat1;
this.lon1 = lon1;
this.lat2 = lat2;
this.lon2 = lon2;
this.low = low;
this.max = max;
dateline = (lon1 == GeoUtils.MIN_LON_INCL && lon2 == GeoUtils.MIN_LON_INCL)
|| (lon1 == GeoUtils.MAX_LON_INCL && lon2 == GeoUtils.MAX_LON_INCL);
}
boolean crossesTriangle(double ax, double ay, double bx, double by, double cx, double cy) {
double triMinLat = StrictMath.min(StrictMath.min(ay, by), cy);
if (triMinLat <= max) {
double dy = lat1;
double ey = lat2;
double dx = lon1;
double ex = lon2;
double triMinLon = StrictMath.min(StrictMath.min(ax, bx), cx);
double triMaxLat = StrictMath.max(StrictMath.max(ay, by), cy);
double triMaxLon = StrictMath.max(StrictMath.max(ax, bx), cx);
boolean outside = (dy < triMinLat && ey < triMinLat) ||
(dy > triMaxLat && ey > triMaxLat) ||
(dx < triMinLon && ex < triMinLon) ||
(dx > triMaxLon && ex > triMaxLon);
if (outside == false) {
if (lineCrossesLine(dx, dy, ex, ey, ax, ay, bx, by) ||
lineCrossesLine(dx, dy, ex, ey, bx, by, cx, cy) ||
lineCrossesLine(dx, dy, ex, ey, cx, cy, ax, ay)) {
return true;
}
}
if (left != null && left.crossesTriangle(ax, ay, bx, by, cx, cy)) {
return true;
}
if (right != null && triMaxLat >= low && right.crossesTriangle(ax, ay, bx, by, cx, cy)) {
return true;
}
}
return false;
}
boolean crossesBox(double minLat, double maxLat, double minLon, double maxLon, boolean includeBoundary) {
if (minLat <= max) {
double cy = lat1;
double dy = lat2;
double cx = lon1;
double dx = lon2;
if (Rectangle.containsPoint(cy, cx, minLat, maxLat, minLon, maxLon) ||
Rectangle.containsPoint(dy, dx, minLat, maxLat, minLon, maxLon)) {
return true;
}
boolean outside = (cy < minLat && dy < minLat) ||
(cy > maxLat && dy > maxLat) ||
(cx < minLon && dx < minLon) ||
(cx > maxLon && dx > maxLon);
if (outside == false) {
if (includeBoundary == true &&
lineCrossesLineWithBoundary(cx, cy, dx, dy, minLon, minLat, maxLon, minLat) ||
lineCrossesLineWithBoundary(cx, cy, dx, dy, maxLon, minLat, maxLon, maxLat) ||
lineCrossesLineWithBoundary(cx, cy, dx, dy, maxLon, maxLat, maxLon, minLat) ||
lineCrossesLineWithBoundary(cx, cy, dx, dy, minLon, maxLat, minLon, minLat)) {
return true;
} else if (lineCrossesLine(cx, cy, dx, dy, minLon, minLat, maxLon, minLat) ||
lineCrossesLine(cx, cy, dx, dy, maxLon, minLat, maxLon, maxLat) ||
lineCrossesLine(cx, cy, dx, dy, maxLon, maxLat, maxLon, minLat) ||
lineCrossesLine(cx, cy, dx, dy, minLon, maxLat, minLon, minLat)) {
return true;
}
}
if (left != null && left.crossesBox(minLat, maxLat, minLon, maxLon, includeBoundary)) {
return true;
}
if (right != null && maxLat >= low && right.crossesBox(minLat, maxLat, minLon, maxLon, includeBoundary)) {
return true;
}
}
return false;
}
boolean crossesLine(double a2x, double a2y, double b2x, double b2y) {
double minY = StrictMath.min(a2y, b2y);
double maxY = StrictMath.max(a2y, b2y);
if (minY <= max) {
double a1x = lon1;
double a1y = lat1;
double b1x = lon2;
double b1y = lat2;
double minX = StrictMath.min(a2x, b2x);
double maxX = StrictMath.max(a2x, b2x);
boolean outside = (a1y < minY && b1y < minY) ||
(a1y > maxY && b1y > maxY) ||
(a1x < minX && b1x < minX) ||
(a1x > maxX && b1x > maxX);
if (outside == false && lineCrossesLineWithBoundary(a1x, a1y, b1x, b1y, a2x, a2y, b2x, b2y)) {
return true;
}
if (left != null && left.crossesLine(a2x, a2y, b2x, b2y)) {
return true;
}
if (right != null && maxY >= low && right.crossesLine(a2x, a2y, b2x, b2y)) {
return true;
}
}
return false;
}
}
protected static boolean pointInTriangle (double x, double y, double ax, double ay, double bx, double by, double cx, double cy) {
double minX = StrictMath.min(ax, StrictMath.min(bx, cx));
double minY = StrictMath.min(ay, StrictMath.min(by, cy));
double maxX = StrictMath.max(ax, StrictMath.max(bx, cx));
double maxY = StrictMath.max(ay, StrictMath.max(by, cy));
if (x >= minX && x <= maxX && y >= minY && y <= maxY ) {
int a = orient(x, y, ax, ay, bx, by);
int b = orient(x, y, bx, by, cx, cy);
if (a == 0 || b == 0 || a < 0 == b < 0) {
int c = orient(x, y, cx, cy, ax, ay);
return c == 0 || (c < 0 == (b < 0 || a < 0));
}
return false;
} else {
return false;
}
}
private static Edge createTree(double[] lats, double[] lons) {
Edge edges[] = new Edge[lats.length - 1];
for (int i = 1; i < lats.length; i++) {
double lat1 = lats[i-1];
double lon1 = lons[i-1];
double lat2 = lats[i];
double lon2 = lons[i];
edges[i - 1] = new Edge(lat1, lon1, lat2, lon2, Math.min(lat1, lat2), Math.max(lat1, lat2));
}
Arrays.sort(edges, (left, right) -> {
int ret = Double.compare(left.low, right.low);
if (ret == 0) {
ret = Double.compare(left.max, right.max);
}
return ret;
});
return createTree(edges, 0, edges.length - 1);
}
private static Edge createTree(Edge edges[], int low, int high) {
if (low > high) {
return null;
}
int mid = (low + high) >>> 1;
Edge newNode = edges[mid];
newNode.left = createTree(edges, low, mid - 1);
newNode.right = createTree(edges, mid + 1, high);
if (newNode.left != null) {
newNode.max = Math.max(newNode.max, newNode.left.max);
}
if (newNode.right != null) {
newNode.max = Math.max(newNode.max, newNode.right.max);
}
return newNode;
}
}