| /* |
| * 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 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; |
| |
| /** |
| * 2D line/polygon geometry implementation represented as a balanced interval tree of edges. |
| * <p> |
| * Construction takes {@code O(n log n)} time for sorting and tree construction. |
| * {@link #relate relate()} are {@code O(n)}, but for most |
| * practical lines and polygons are much faster than brute force. |
| * @lucene.internal |
| */ |
| public abstract class EdgeTree { |
| /** minimum latitude of this geometry's bounding box area */ |
| public final double minLat; |
| /** maximum latitude of this geometry's bounding box area */ |
| public final double maxLat; |
| /** minimum longitude of this geometry's bounding box area */ |
| public final double minLon; |
| /** maximum longitude of this geometry's bounding box area */ |
| public final double maxLon; |
| |
| // each component is a node in an augmented 2d kd-tree: we alternate splitting between latitude/longitude, |
| // and pull up max values for both dimensions to each parent node (regardless of split). |
| |
| /** maximum latitude of this component or any of its children */ |
| protected double maxY; |
| /** maximum longitude of this component or any of its children */ |
| protected double maxX; |
| /** which dimension was this node split on */ |
| // TODO: its implicit based on level, but boolean keeps code simple |
| protected boolean splitX; |
| |
| // child components, or null |
| protected EdgeTree left; |
| protected EdgeTree right; |
| |
| /** root node of edge tree */ |
| 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; |
| |
| // create interval tree of edges |
| this.tree = createTree(lats, lons); |
| } |
| |
| /** Returns relation to the provided triangle */ |
| public Relation relateTriangle(double ax, double ay, double bx, double by, double cx, double cy) { |
| // compute bounding box of triangle |
| 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; |
| } |
| |
| /** Returns relation to the provided rectangle */ |
| 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; |
| } |
| |
| /** Returns relation to the provided rectangle for this component */ |
| protected abstract Relation componentRelate(double minLat, double maxLat, double minLon, double maxLon); |
| |
| /** Returns relation to the provided triangle for this component */ |
| 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) { |
| // compute bounding box of triangle |
| 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); |
| } |
| |
| |
| /** Returns relation to the provided rectangle for this component */ |
| protected Relation internalComponentRelate(double minLat, double maxLat, double minLon, double maxLon) { |
| // if the bounding boxes are disjoint then the shape does not cross |
| if (maxLon < this.minLon || minLon > this.maxLon || maxLat < this.minLat || minLat > this.maxLat) { |
| return Relation.CELL_OUTSIDE_QUERY; |
| } |
| // if the rectangle fully encloses us, we cross. |
| if (minLat <= this.minLat && maxLat >= this.maxLat && minLon <= this.minLon && maxLon >= this.maxLon) { |
| return Relation.CELL_CROSSES_QUERY; |
| } |
| return componentRelate(minLat, maxLat, minLon, maxLon); |
| } |
| |
| /** Creates tree from sorted components (with range low and high inclusive) */ |
| 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); |
| } |
| // add midpoint |
| EdgeTree newNode = components[mid]; |
| newNode.splitX = splitX; |
| // add children |
| newNode.left = createTree(components, low, mid - 1, !splitX); |
| newNode.right = createTree(components, mid + 1, high, !splitX); |
| // pull up max values to this node |
| 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; |
| } |
| |
| /** |
| * Internal tree node: represents geometry edge from lat1,lon1 to lat2,lon2. |
| * The sort value is {@code low}, which is the minimum latitude of the edge. |
| * {@code max} stores the maximum latitude of this edge or any children. |
| */ |
| static class Edge { |
| // lat-lon pair (in original order) of the two vertices |
| final double lat1, lat2; |
| final double lon1, lon2; |
| //edge belongs to the dateline |
| final boolean dateline; |
| /** min of this edge */ |
| final double low; |
| /** max latitude of this edge or any children */ |
| double max; |
| |
| /** left child edge, or null */ |
| Edge left; |
| /** right child edge, or null */ |
| 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); |
| } |
| |
| /** Returns true if the triangle crosses any edge in this edge subtree */ |
| boolean crossesTriangle(double ax, double ay, double bx, double by, double cx, double cy) { |
| // compute min lat of triangle bounding box |
| double triMinLat = StrictMath.min(StrictMath.min(ay, by), cy); |
| if (triMinLat <= max) { |
| double dy = lat1; |
| double ey = lat2; |
| double dx = lon1; |
| double ex = lon2; |
| |
| // compute remaining bounding box of triangle |
| 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); |
| |
| // 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 < 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; |
| } |
| |
| /** Returns true if the box crosses any edge in this edge subtree */ |
| boolean crossesBox(double minLat, double maxLat, double minLon, double maxLon, boolean includeBoundary) { |
| // we just have to cross one edge to answer the question, so we descend the tree and return when we do. |
| if (minLat <= 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 = lat1; |
| double dy = lat2; |
| double cx = lon1; |
| double dx = lon2; |
| |
| // optimization: see if either end of the line segment is contained by the rectangle |
| if (Rectangle.containsPoint(cy, cx, minLat, maxLat, minLon, maxLon) || |
| Rectangle.containsPoint(dy, dx, minLat, maxLat, minLon, maxLon)) { |
| 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 < 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)) { |
| // include boundaries: ensures box edges that terminate on the polygon are included |
| 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; |
| } |
| |
| /** Returns true if the line crosses any edge in this edge subtree */ |
| 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; |
| } |
| } |
| |
| //This should be moved when LatLonShape is moved from sandbox! |
| /** |
| * Compute whether the given x, y point is in a triangle; uses the winding order method */ |
| 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)); |
| //check the bounding box because if the triangle is degenerated, e.g points and lines, we need to filter out |
| //coplanar points that are not part of the triangle. |
| 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; |
| } |
| } |
| |
| /** |
| * Creates an edge interval tree from a set of geometry vertices. |
| * @return root node of the tree. |
| */ |
| 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)); |
| } |
| // 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 Edge createTree(Edge edges[], int low, int high) { |
| if (low > high) { |
| return null; |
| } |
| // add midpoint |
| int mid = (low + high) >>> 1; |
| Edge 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; |
| } |
| } |