/* * The JTS Topology Suite is a collection of Java classes that * implement the fundamental operations required to validate a given * geo-spatial data set to a known topological specification. * * Copyright (C) 2001 Vivid Solutions * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * For more information, contact: * * Vivid Solutions * Suite #1A * 2328 Government Street * Victoria BC V8T 5G5 * Canada * * (250)385-6040 * www.vividsolutions.com */ package com.revolsys.geometry.index.chain; import com.revolsys.geometry.model.BoundingBox; import com.revolsys.geometry.model.LineString; import com.revolsys.geometry.model.Point; import com.revolsys.geometry.model.impl.BoundingBoxDoubleXY; import com.revolsys.geometry.model.segment.LineSegment; import com.revolsys.geometry.model.segment.LineSegmentDouble; import com.revolsys.geometry.util.BoundingBoxUtil; /** * Monotone Chains are a way of partitioning the segments of a linestring to * allow for fast searching of intersections. * They have the following properties: * <ol> * <li>the segments within a monotone chain never intersect each other * <li>the envelope of any contiguous subset of the segments in a monotone chain * is equal to the envelope of the endpoints of the subset. * </ol> * Property 1 means that there is no need to test pairs of segments from within * the same monotone chain for intersection. * <p> * Property 2 allows * an efficient binary search to be used to find the intersection points of two monotone chains. * For many types of real-world data, these properties eliminate a large number of * segment comparisons, producing substantial speed gains. * <p> * One of the goals of this implementation of MonotoneChains is to be * as space and time efficient as possible. One design choice that aids this * is that a MonotoneChain is based on a subarray of a list of points. * This means that new arrays of points (potentially very large) do not * have to be allocated. * <p> * * MonotoneChains support the following kinds of queries: * <ul> * <li>BoundingBox select: determine all the segments in the chain which * intersect a given envelope * <li>Overlap: determine all the pairs of segments in two chains whose * envelopes overlap * </ul> * * This implementation of MonotoneChains uses the concept of internal iterators * ({@link MonotoneChainSelectAction} and {@link MonotoneChainOverlapAction}) * to return the results for queries. * This has time and space advantages, since it * is not necessary to build lists of instantiated objects to represent the segments * returned by the query. * Queries made in this manner are thread-safe. * * @version 1.7 */ public class MonotoneChain { private Object context = null;// user-defined information private BoundingBox env = null; private int id;// useful for optimizing chain comparisons private final LineString points; private final int start; private final int end; public MonotoneChain(final LineString pts, final int start, final int end, final Object context) { this.points = pts; this.start = start; this.end = end; this.context = context; } private void computeOverlaps(final int start0, final int end0, final MonotoneChain mc, final int start1, final int end1, final MonotoneChainOverlapAction mco) { final double x1 = this.points.getX(start0); final double y1 = this.points.getY(start0); final double x2 = this.points.getX(end0); final double y2 = this.points.getY(end0); final double x3 = mc.points.getX(start1); final double y3 = mc.points.getY(start1); final double x4 = mc.points.getX(end1); final double y4 = mc.points.getY(end1); // terminating condition for the recursion if (end0 - start0 == 1 && end1 - start1 == 1) { mco.overlap(this, start0, mc, start1); return; } // nothing to do if the envelopes of these chains don't overlap if (BoundingBoxUtil.intersectsMinMax(x1, y1, x2, y2, x3, y3, x4, y4)) { // the chains overlap, so split each in half and iterate (binary search) final int mid0 = (start0 + end0) / 2; final int mid1 = (start1 + end1) / 2; // mid != start or end (since we checked above for end - start <= 1) // check terminating conditions before recursing if (start0 < mid0) { if (start1 < mid1) { computeOverlaps(start0, mid0, mc, start1, mid1, mco); } if (mid1 < end1) { computeOverlaps(start0, mid0, mc, mid1, end1, mco); } } if (mid0 < end0) { if (start1 < mid1) { computeOverlaps(mid0, end0, mc, start1, mid1, mco); } if (mid1 < end1) { computeOverlaps(mid0, end0, mc, mid1, end1, mco); } } } } /** * Determine all the line segments in two chains which may overlap, and process them. * <p> * The monotone chain search algorithm attempts to optimize * performance by not calling the overlap action on chain segments * which it can determine do not overlap. * However, it *may* call the overlap action on segments * which do not actually interact. * This saves on the overhead of checking intersection * each time, since clients may be able to do this more efficiently. * * @param searchEnv the search envelope * @param mco the overlap action to execute on selected segments */ public void computeOverlaps(final MonotoneChain mc, final MonotoneChainOverlapAction mco) { computeOverlaps(this.start, this.end, mc, mc.start, mc.end, mco); } private void computeSelect(final BoundingBox searchEnv, final int start0, final int end0, final MonotoneChainSelectAction mcs) { final double x1 = this.points.getX(start0); final double y1 = this.points.getY(start0); final double x2 = this.points.getX(end0); final double y2 = this.points.getY(end0); // terminating condition for the recursion if (end0 - start0 == 1) { mcs.select(this, start0); } else if (searchEnv.intersects(x1, y1, x2, y2)) { // the chains overlap, so split each in half and iterate (binary search) final int mid = (start0 + end0) / 2; // Assert: mid != start or end (since we checked above for end - start <= 1) // check terminating conditions before recursing if (start0 < mid) { computeSelect(searchEnv, start0, mid, mcs); } if (mid < end0) { computeSelect(searchEnv, mid, end0, mcs); } } } public Object getContext() { return this.context; } /** * Return the subsequence of coordinates forming this chain. * Allocates a new array to hold the Coordinates */ public Point[] getCoordinates() { final Point coord[] = new Point[this.end - this.start + 1]; int index = 0; for (int i = this.start; i <= this.end; i++) { coord[index++] = this.points.getPoint(i); } return coord; } public int getEndIndex() { return this.end; } public BoundingBox getEnvelope() { if (this.env == null) { final double x1 = this.points.getX(this.start); final double y1 = this.points.getY(this.start); final double x2 = this.points.getX(this.end); final double y2 = this.points.getY(this.end); this.env = new BoundingBoxDoubleXY(x1, y1, x2, y2); } return this.env; } public int getId() { return this.id; } /** * Gets the line segment starting at <code>index</code> * * @param index index of segment * @param ls line segment to extract into */ public LineSegment getLineSegment(final int index) { return new LineSegmentDouble(this.points.getPoint(index), this.points.getPoint(index + 1)); } public int getStartIndex() { return this.start; } /** * Determine all the line segments in the chain whose envelopes overlap * the searchEnvelope, and process them. * <p> * The monotone chain search algorithm attempts to optimize * performance by not calling the select action on chain segments * which it can determine are not in the search envelope. * However, it *may* call the select action on segments * which do not intersect the search envelope. * This saves on the overhead of checking envelope intersection * each time, since clients may be able to do this more efficiently. * * @param searchEnv the search envelope * @param mcs the select action to execute on selected segments */ public void select(final BoundingBox searchEnv, final MonotoneChainSelectAction mcs) { computeSelect(searchEnv, this.start, this.end, mcs); } public void setId(final int id) { this.id = id; } }