blob: 5e99506315312f1e1f7118c7291f650895cfd6b2 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.geo;
import java.util.Arrays;
import static org.apache.lucene.geo.GeoUtils.lineCrossesLine;
import static org.apache.lucene.geo.GeoUtils.lineCrossesLineWithBoundary;
import static org.apache.lucene.geo.GeoUtils.orient;
/**
* Internal tree node: represents geometry edge from [x1, y1] to [x2, y2].
* The sort value is {@code low}, which is the minimum y of the edge.
* {@code max} stores the maximum y of this edge or any children.
* <p>
* Construction takes {@code O(n log n)} time for sorting and tree construction.
* Methods are {@code O(n)}, but for most
* practical lines and polygons are much faster than brute force.
*/
final class EdgeTree {
// X-Y pair (in original order) of the two vertices
final double y1, y2;
final double x1, x2;
/** min Y of this edge */
final double low;
/** max Y of this edge or any children */
double max;
/** left child edge, or null */
EdgeTree left;
/** right child edge, or null */
EdgeTree right;
/** helper bytes to signal if a point is on an edge, it is within the edge tree or disjoint */
final private static byte FALSE = 0x00;
final private static byte TRUE = 0x01;
final private static byte ON_EDGE = 0x02;
private EdgeTree(double x1, double y1, double x2, double y2, double low, double max) {
this.y1 = y1;
this.x1 = x1;
this.y2 = y2;
this.x2 = x2;
this.low = low;
this.max = max;
}
/**
* Returns true if the point is on an edge or crosses the edge subtree an odd number
* of times.
*/
protected boolean contains(double x, double y) {
return containsPnPoly(x, y) > FALSE;
}
/**
* Returns byte 0x00 if the point crosses this edge subtree an even number of times.
* Returns byte 0x01 if the point crosses this edge subtree an odd number of times.
* Returns byte 0x02 if the point is on one of the edges.
* <p>
* See <a href="https://www.ecse.rpi.edu/~wrf/Research/Short_Notes/pnpoly.html">
* https://www.ecse.rpi.edu/~wrf/Research/Short_Notes/pnpoly.html</a> for more information.
*/
// ported to java from https://www.ecse.rpi.edu/~wrf/Research/Short_Notes/pnpoly.html
// original code under the BSD license (https://www.ecse.rpi.edu/~wrf/Research/Short_Notes/pnpoly.html#License%20to%20Use)
//
// Copyright (c) 1970-2003, Wm. Randolph Franklin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
// to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimers.
// 2. Redistributions in binary form must reproduce the above copyright
// notice in the documentation and/or other materials provided with
// the distribution.
// 3. The name of W. Randolph Franklin may not be used to endorse or
// promote products derived from this Software without specific
// prior written permission.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
private byte containsPnPoly(double x, double y) {
byte res = FALSE;
if (y <= this.max) {
if (y == this.y1 && y == this.y2 ||
(y <= this.y1 && y >= this.y2) != (y >= this.y1 && y <= this.y2)) {
if ((x == this.x1 && x == this.x2) ||
((x <= this.x1 && x >= this.x2) != (x >= this.x1 && x <= this.x2) &&
GeoUtils.orient(this.x1, this.y1, this.x2, this.y2, x, y) == 0)) {
return ON_EDGE;
} else if (this.y1 > y != this.y2 > y) {
res = x < (this.x2 - this.x1) * (y - this.y1) / (this.y2 - this.y1) + this.x1 ? TRUE : FALSE;
}
}
if (this.left != null) {
res ^= left.containsPnPoly(x, y);
if ((res & 0x02) == 0x02) {
return ON_EDGE;
}
}
if (this.right != null && y >= this.low) {
res ^= right.containsPnPoly(x, y);
if ((res & 0x02) == 0x02) {
return ON_EDGE;
}
}
}
assert res >= FALSE && res <= ON_EDGE;
return res;
}
/** returns true if the provided x, y point lies on the line */
boolean isPointOnLine(double x, double y) {
if (y <= max) {
double a1x = x1;
double a1y = y1;
double b1x = x2;
double b1y = y2;
boolean outside = (a1y < y && b1y < y) ||
(a1y > y && b1y > y) ||
(a1x < x && b1x < x) ||
(a1x > x && b1x > x);
if (outside == false && orient(a1x, a1y, b1x, b1y, x, y) == 0) {
return true;
}
if (left != null && left.isPointOnLine(x, y)) {
return true;
}
if (right != null && y >= this.low && right.isPointOnLine(x, y)) {
return true;
}
}
return false;
}
/** Returns true if the triangle crosses any edge in this edge subtree */
boolean crossesTriangle(double minX, double maxX, double minY, double maxY,
double ax, double ay, double bx, double by, double cx, double cy, boolean includeBoundary) {
if (minY <= max) {
double dy = y1;
double ey = y2;
double dx = x1;
double ex = x2;
// optimization: see if the rectangle is outside of the "bounding box" of the polyline at all
// if not, don't waste our time trying more complicated stuff
boolean outside = (dy < minY && ey < minY) ||
(dy > maxY && ey > maxY) ||
(dx < minX && ex < minX) ||
(dx > maxX && ex > maxX);
if (outside == false) {
if (includeBoundary == true) {
if (lineCrossesLineWithBoundary(dx, dy, ex, ey, ax, ay, bx, by) ||
lineCrossesLineWithBoundary(dx, dy, ex, ey, bx, by, cx, cy) ||
lineCrossesLineWithBoundary(dx, dy, ex, ey, cx, cy, ax, ay)) {
return true;
}
} else {
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(minX, maxX, minY, maxY, ax, ay, bx, by, cx, cy, includeBoundary)) {
return true;
}
if (right != null && maxY >= low && right.crossesTriangle(minX, maxX, minY, maxY, ax, ay, bx, by, cx, cy, includeBoundary)) {
return true;
}
}
return false;
}
/** Returns true if the box crosses any edge in this edge subtree */
boolean crossesBox(double minX, double maxX, double minY, double maxY, boolean includeBoundary) {
// we just have to cross one edge to answer the question, so we descend the tree and return when we do.
if (minY <= max) {
// we compute line intersections of every polygon edge with every box line.
// if we find one, return true.
// for each box line (AB):
// for each poly line (CD):
// intersects = orient(C,D,A) * orient(C,D,B) <= 0 && orient(A,B,C) * orient(A,B,D) <= 0
double cy = y1;
double dy = y2;
double cx = x1;
double dx = x2;
// optimization: see if either end of the line segment is contained by the rectangle
if (Rectangle.containsPoint(cy, cx, minY, maxY, minX, maxX) ||
Rectangle.containsPoint(dy, dx, minY, maxY, minX, maxX)) {
return true;
}
// optimization: see if the rectangle is outside of the "bounding box" of the polyline at all
// if not, don't waste our time trying more complicated stuff
boolean outside = (cy < minY && dy < minY) ||
(cy > maxY && dy > maxY) ||
(cx < minX && dx < minX) ||
(cx > maxX && dx > maxX);
if (outside == false) {
if (includeBoundary == true) {
if (lineCrossesLineWithBoundary(cx, cy, dx, dy, minX, minY, maxX, minY) ||
lineCrossesLineWithBoundary(cx, cy, dx, dy, maxX, minY, maxX, maxY) ||
lineCrossesLineWithBoundary(cx, cy, dx, dy, maxX, maxY, minX, maxY) ||
lineCrossesLineWithBoundary(cx, cy, dx, dy, minX, maxY, minX, minY)) {
// include boundaries: ensures box edges that terminate on the polygon are included
return true;
}
} else {
if (lineCrossesLine(cx, cy, dx, dy, minX, minY, maxX, minY) ||
lineCrossesLine(cx, cy, dx, dy, maxX, minY, maxX, maxY) ||
lineCrossesLine(cx, cy, dx, dy, maxX, maxY, minX, maxY) ||
lineCrossesLine(cx, cy, dx, dy, minX, maxY, minX, minY)) {
return true;
}
}
}
if (left != null && left.crossesBox(minX, maxX, minY, maxY, includeBoundary)) {
return true;
}
if (right != null && maxY >= low && right.crossesBox(minX, maxX, minY, maxY, includeBoundary)) {
return true;
}
}
return false;
}
/** Returns true if the line crosses any edge in this edge subtree */
boolean crossesLine(double minX, double maxX, double minY, double maxY, double a2x, double a2y, double b2x, double b2y, boolean includeBoundary) {
if (minY <= max) {
double a1x = x1;
double a1y = y1;
double b1x = x2;
double b1y = y2;
boolean outside = (a1y < minY && b1y < minY) ||
(a1y > maxY && b1y > maxY) ||
(a1x < minX && b1x < minX) ||
(a1x > maxX && b1x > maxX);
if (outside == false) {
if (includeBoundary) {
if (lineCrossesLineWithBoundary(a1x, a1y, b1x, b1y, a2x, a2y, b2x, b2y)) {
return true;
}
} else {
if (lineCrossesLine(a1x, a1y, b1x, b1y, a2x, a2y, b2x, b2y)) {
return true;
}
}
}
if (left != null && left.crossesLine(minX, maxX, minY, maxY, a2x, a2y, b2x, b2y, includeBoundary)) {
return true;
}
if (right != null && maxY >= low && right.crossesLine(minX, maxX, minY, maxY, a2x, a2y, b2x, b2y, includeBoundary)) {
return true;
}
}
return false;
}
/**
* Creates an edge interval tree from a set of geometry vertices.
* @return root node of the tree.
*/
static EdgeTree createTree(double[] x, double[] y) {
EdgeTree edges[] = new EdgeTree[x.length - 1];
for (int i = 1; i < x.length; i++) {
double x1 = x[i-1];
double y1 = y[i-1];
double x2 = x[i];
double y2 = y[i];
edges[i - 1] = new EdgeTree(x1, y1, x2, y2, Math.min(y1, y2), Math.max(y1, y2));
}
// sort the edges then build a balanced tree from them
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);
}
/** Creates tree from sorted edges (with range low and high inclusive) */
private static EdgeTree createTree(EdgeTree edges[], int low, int high) {
if (low > high) {
return null;
}
// add midpoint
int mid = (low + high) >>> 1;
EdgeTree newNode = edges[mid];
// add children
newNode.left = createTree(edges, low, mid - 1);
newNode.right = createTree(edges, mid + 1, high);
// pull up max values to this node
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;
}
}