/*
* 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.Comparator;
import org.apache.lucene.index.PointValues.Relation;
import org.apache.lucene.util.ArrayUtil;
2D multi-component geometry implementation represented as an interval tree of components.
Construction takes O(n log n)
time for sorting and tree construction.
/**
* 2D multi-component geometry implementation represented as an interval tree of components.
* <p>
* Construction takes {@code O(n log n)} time for sorting and tree construction.
*/
final class ComponentTree implements Component2D {
minimum Y of this geometry's bounding box area /** minimum Y of this geometry's bounding box area */
private double minY;
maximum Y of this geometry's bounding box area /** maximum Y of this geometry's bounding box area */
private double maxY;
minimum X of this geometry's bounding box area /** minimum X of this geometry's bounding box area */
private double minX;
maximum X of this geometry's bounding box area /** maximum X of this geometry's bounding box area */
private double maxX;
// child components, or null. Note internal nodes might mot have
// a consistent bounding box. Internal nodes should not be accessed
// outside if this class.
private Component2D left;
private Component2D right;
which dimension was this node split on /** which dimension was this node split on */
// TODO: its implicit based on level, but boolean keeps code simple
final private boolean splitX;
root node of edge tree /** root node of edge tree */
final private Component2D component;
private ComponentTree(Component2D component, boolean splitX) {
this.minY = component.getMinY();
this.maxY = component.getMaxY();
this.minX = component.getMinX();
this.maxX = component.getMaxX();
this.component = component;
this.splitX = splitX;
}
@Override
public double getMinX() {
return minX;
}
@Override
public double getMaxX() {
return maxX;
}
@Override
public double getMinY() {
return minY;
}
@Override
public double getMaxY() {
return maxY;
}
@Override
public boolean contains(double x, double y) {
if (y <= this.maxY && x <= this.maxX) {
if (component.contains(x, y)) {
return true;
}
if (left != null) {
if (left.contains(x, y)) {
return true;
}
}
if (right != null && ((splitX == false && y >= this.component.getMinY()) || (splitX && x >= this.component.getMinX()))) {
if (right.contains(x, y)) {
return true;
}
}
}
return false;
}
@Override
public boolean intersectsLine(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY) {
if (minY <= this.maxY && minX <= this.maxX) {
if(component.intersectsLine(minX, maxX, minY, maxY, aX, aY, bX, bY)) {
return true;
}
if (left != null) {
if (left.intersectsLine(minX, maxX, minY, maxY, aX, aY, bX, bY)) {
return true;
}
}
if (right != null && ((splitX == false && maxY >= this.component.getMinY()) || (splitX && maxX >= this.component.getMinX()))) {
if (right.intersectsLine(minX, maxX, minY, maxY, aX, aY, bX, bY)) {
return true;
}
}
}
return false;
}
@Override
public boolean intersectsTriangle(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY, double cX, double cY) {
if (minY <= this.maxY && minX <= this.maxX) {
if(component.intersectsTriangle(minX, maxX, minY, maxY, aX, aY, bX, bY, cX, cY)) {
return true;
}
if (left != null) {
if (left.intersectsTriangle(minX, maxX, minY, maxY, aX, aY, bX, bY, cX, cY)) {
return true;
}
}
if (right != null && ((splitX == false && maxY >= this.component.getMinY()) || (splitX && maxX >= this.component.getMinX()))) {
if (right.intersectsTriangle(minX, maxX, minY, maxY, aX, aY, bX, bY, cX, cY)) {
return true;
}
}
}
return false;
}
@Override
public boolean containsLine(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY) {
if (minY <= this.maxY && minX <= this.maxX) {
if(component.containsLine(minX, maxX, minY, maxY, aX, aY, bX, bY)) {
return true;
}
if (left != null) {
if (left.containsLine(minX, maxX, minY, maxY, aX, aY, bX, bY)) {
return true;
}
}
if (right != null && ((splitX == false && maxY >= this.component.getMinY()) || (splitX && maxX >= this.component.getMinX()))) {
if (right.containsLine(minX, maxX, minY, maxY, aX, aY, bX, bY)) {
return true;
}
}
}
return false;
}
@Override
public boolean containsTriangle(double minX, double maxX, double minY, double maxY,
double aX, double aY, double bX, double bY, double cX, double cY) {
if (minY <= this.maxY && minX <= this.maxX) {
if(component.containsTriangle(minX, maxX, minY, maxY, aX, aY, bX, bY, cX, cY)) {
return true;
}
if (left != null) {
if (left.containsTriangle(minX, maxX, minY, maxY, aX, aY, bX, bY, cX, cY)) {
return true;
}
}
if (right != null && ((splitX == false && maxY >= this.component.getMinY()) || (splitX && maxX >= this.component.getMinX()))) {
if (right.containsTriangle(minX, maxX, minY, maxY, aX, aY, bX, bY, cX, cY)) {
return true;
}
}
}
return false;
}
@Override
public WithinRelation withinPoint(double x, double y) {
if (left != null || right != null) {
throw new IllegalArgumentException("withinPoint is not supported for shapes with more than one component");
}
return component.withinPoint(x, y);
}
@Override
public WithinRelation withinLine(double minX, double maxX, double minY, double maxY,
double aX, double aY, boolean ab, double bX, double bY) {
if (left != null || right != null) {
throw new IllegalArgumentException("withinLine is not supported for shapes with more than one component");
}
return component.withinLine(minX, maxX, minY, maxY, aX, aY, ab, bX, bY);
}
@Override
public WithinRelation withinTriangle(double minX, double maxX, double minY, double maxY,
double aX, double aY, boolean ab, double bX, double bY, boolean bc, double cX, double cY, boolean ca) {
if (left != null || right != null) {
throw new IllegalArgumentException("withinTriangle is not supported for shapes with more than one component");
}
return component.withinTriangle(minX, maxX, minY, maxY, aX, aY, ab, bX, bY, bc, cX, cY, ca);
}
@Override
public Relation relate(double minX, double maxX, double minY, double maxY) {
if (minY <= this.maxY && minX <= this.maxX) {
Relation relation = component.relate(minX, maxX, minY, maxY);
if (relation != Relation.CELL_OUTSIDE_QUERY) {
return relation;
}
if (left != null) {
relation = left.relate(minX, maxX, minY, maxY);
if (relation != Relation.CELL_OUTSIDE_QUERY) {
return relation;
}
}
if (right != null && ((splitX == false && maxY >= this.component.getMinY()) || (splitX && maxX >= this.component.getMinX()))) {
relation = right.relate(minX, maxX, minY, maxY);
if (relation != Relation.CELL_OUTSIDE_QUERY) {
return relation;
}
}
}
return Relation.CELL_OUTSIDE_QUERY;
}
Creates tree from provided components /** Creates tree from provided components */
static Component2D create(Component2D[] components) {
if (components.length == 1) {
return components[0];
}
ComponentTree root = createTree(components, 0, components.length - 1, false);
// pull up min values for the root node so it contains a consistent bounding box
for (Component2D component : components) {
root.minY = Math.min(root.minY, component.getMinY());
root.minX = Math.min(root.minX, component.getMinX());
}
return root;
}
Creates tree from sorted components (with range low and high inclusive) /** Creates tree from sorted components (with range low and high inclusive) */
private static ComponentTree createTree(Component2D[] components, int low, int high, boolean splitX) {
if (low > high) {
return null;
}
final int mid = (low + high) >>> 1;
if (low < high) {
Comparator<Component2D> comparator;
if (splitX) {
comparator = (left, right) -> {
int ret = Double.compare(left.getMinX(), right.getMinX());
if (ret == 0) {
ret = Double.compare(left.getMaxX(), right.getMaxX());
}
return ret;
};
} else {
comparator = (left, right) -> {
int ret = Double.compare(left.getMinY(), right.getMinY());
if (ret == 0) {
ret = Double.compare(left.getMaxY(), right.getMaxY());
}
return ret;
};
}
ArrayUtil.select(components, low, high + 1, mid, comparator);
}
ComponentTree newNode = new ComponentTree(components[mid], splitX);
// find 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.getMaxX());
newNode.maxY = Math.max(newNode.maxY, newNode.left.getMaxY());
}
if (newNode.right != null) {
newNode.maxX = Math.max(newNode.maxX, newNode.right.getMaxX());
newNode.maxY = Math.max(newNode.maxY, newNode.right.getMaxY());
}
return newNode;
}
}