/* * Copyright (c) 2016 Martin Davis. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Eclipse Distribution License v. 1.0 which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * * http://www.eclipse.org/org/documents/edl-v10.php. */ package org.locationtech.jts.index.strtree; import java.util.Iterator; import java.util.List; import org.locationtech.jts.geom.Envelope; import org.locationtech.jts.util.PriorityQueue; /** * A pair of {@link Boundable}s, whose leaf items * support a distance metric between them. * Used to compute the distance between the members, * and to expand a member relative to the other * in order to produce new branches of the * Branch-and-Bound evaluation tree. * Provides an ordering based on the distance between the members, * which allows building a priority queue by minimum distance. * * @author Martin Davis * */ class BoundablePair implements Comparable { private Boundable boundable1; private Boundable boundable2; private double distance; private ItemDistance itemDistance; //private double maxDistance = -1.0; public BoundablePair(Boundable boundable1, Boundable boundable2, ItemDistance itemDistance) { this.boundable1 = boundable1; this.boundable2 = boundable2; this.itemDistance = itemDistance; distance = distance(); } /** * Gets one of the member {@link Boundable}s in the pair * (indexed by [0, 1]). * * @param i the index of the member to return (0 or 1) * @return the chosen member */ public Boundable getBoundable(int i) { if (i == 0) return boundable1; return boundable2; } /** * Computes the distance between the {@link Boundable}s in this pair. * The boundables are either composites or leaves. * If either is composite, the distance is computed as the minimum distance * between the bounds. * If both are leaves, the distance is computed by {@link #itemDistance(ItemBoundable, ItemBoundable)}. * * @return */ private double distance() { // if items, compute exact distance if (isLeaves()) { return itemDistance.distance((ItemBoundable) boundable1, (ItemBoundable) boundable2); } // otherwise compute distance between bounds of boundables return ((Envelope) boundable1.getBounds()).distance( ((Envelope) boundable2.getBounds())); } /* public double getMaximumDistance() { if (maxDistance < 0.0) maxDistance = maxDistance(); return maxDistance; } */ /* private double maxDistance() { return maximumDistance( (Envelope) boundable1.getBounds(), (Envelope) boundable2.getBounds()); } private static double maximumDistance(Envelope env1, Envelope env2) { double minx = Math.min(env1.getMinX(), env2.getMinX()); double miny = Math.min(env1.getMinY(), env2.getMinY()); double maxx = Math.max(env1.getMaxX(), env2.getMaxX()); double maxy = Math.max(env1.getMaxY(), env2.getMaxY()); Coordinate min = new Coordinate(minx, miny); Coordinate max = new Coordinate(maxx, maxy); return min.distance(max); } */ /** * Gets the minimum possible distance between the Boundables in * this pair. * If the members are both items, this will be the * exact distance between them. * Otherwise, this distance will be a lower bound on * the distances between the items in the members. * * @return the exact or lower bound distance for this pair */ public double getDistance() { return distance; } /** * Compares two pairs based on their minimum distances */ public int compareTo(Object o) { BoundablePair nd = (BoundablePair) o; if (distance < nd.distance) return -1; if (distance > nd.distance) return 1; return 0; } /** * Tests if both elements of the pair are leaf nodes * * @return true if both pair elements are leaf nodes */ public boolean isLeaves() { return ! (isComposite(boundable1) || isComposite(boundable2)); } public static boolean isComposite(Object item) { return (item instanceof AbstractNode); } private static double area(Boundable b) { return ((Envelope) b.getBounds()).getArea(); } /** * For a pair which is not a leaf * (i.e. has at least one composite boundable) * computes a list of new pairs * from the expansion of the larger boundable. * */ public void expandToQueue(PriorityQueue priQ, double minDistance) { boolean isComp1 = isComposite(boundable1); boolean isComp2 = isComposite(boundable2); /** * HEURISTIC: If both boundable are composite, * choose the one with largest area to expand. * Otherwise, simply expand whichever is composite. */ if (isComp1 && isComp2) { if (area(boundable1) > area(boundable2)) { expand(boundable1, boundable2, priQ, minDistance); return; } else { expand(boundable2, boundable1, priQ, minDistance); return; } } else if (isComp1) { expand(boundable1, boundable2, priQ, minDistance); return; } else if (isComp2) { expand(boundable2, boundable1, priQ, minDistance); return; } throw new IllegalArgumentException("neither boundable is composite"); } private void expand(Boundable bndComposite, Boundable bndOther, PriorityQueue priQ, double minDistance) { List children = ((AbstractNode) bndComposite).getChildBoundables(); for (Iterator i = children.iterator(); i.hasNext(); ) { Boundable child = (Boundable) i.next(); BoundablePair bp = new BoundablePair(child, bndOther, itemDistance); // only add to queue if this pair might contain the closest points // MD - it's actually faster to construct the object rather than called distance(child, bndOther)! if (bp.getDistance() < minDistance) { priQ.add(bp); } } } }