1 
2 
3 /*
4  * Copyright (c) 2016 Vivid Solutions.
5  * Copyright (c) 2020 Martin Davis.
6  *
7  * All rights reserved. This program and the accompanying materials
8  * are made available under the terms of the Eclipse Public License 2.0
9  * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
10  * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
11  * and the Eclipse Distribution License is available at
12  *
13  * http://www.eclipse.org/org/documents/edl-v10.php.
14  */
15 package org.locationtech.jts.index.chain;
16 
17 import org.locationtech.jts.geom.Coordinate;
18 import org.locationtech.jts.geom.Envelope;
19 import org.locationtech.jts.geom.LineSegment;
20 
21 
22 /**
23  * Monotone Chains are a way of partitioning the segments of a linestring to
24  * allow for fast searching of intersections.
25  * They have the following properties:
26  * <ol>
27  * <li>the segments within a monotone chain never intersect each other
28  * <li>the envelope of any contiguous subset of the segments in a monotone chain
29  * is equal to the envelope of the endpoints of the subset.
30  * </ol>
31  * Property 1 means that there is no need to test pairs of segments from within
32  * the same monotone chain for intersection.
33  * <p>
34  * Property 2 allows
35  * an efficient binary search to be used to find the intersection points of two monotone chains.
36  * For many types of real-world data, these properties eliminate a large number of
37  * segment comparisons, producing substantial speed gains.
38  * <p>
39  * One of the goals of this implementation of MonotoneChains is to be
40  * as space and time efficient as possible. One design choice that aids this
41  * is that a MonotoneChain is based on a subarray of a list of points.
42  * This means that new arrays of points (potentially very large) do not
43  * have to be allocated.
44  * <p>
45  *
46  * MonotoneChains support the following kinds of queries:
47  * <ul>
48  * <li>Envelope select: determine all the segments in the chain which
49  * intersect a given envelope
50  * <li>Overlap: determine all the pairs of segments in two chains whose
51  * envelopes overlap
52  * </ul>
53  *
54  * This implementation of MonotoneChains uses the concept of internal iterators
55  * ({@link MonotoneChainSelectAction} and {@link MonotoneChainOverlapAction})
56  * to return the results for queries.
57  * This has time and space advantages, since it
58  * is not necessary to build lists of instantiated objects to represent the segments
59  * returned by the query.
60  * Queries made in this manner are thread-safe.
61  * <p>
62  * MonotoneChains support being assigned an integer id value
63  * to provide a total ordering for a set of chains.
64  * This can be used during some kinds of processing to
65  * avoid redundant comparisons
66  * (i.e. by comparing only chains where the first id is less than the second).
67  * <p>
68  * MonotoneChains support using an tolerance distance for overlap tests.
69  * This allows reporting overlap in situations where
70  * intersection snapping is being used.
71  * If this is used the chain envelope must be computed
72  * providing an expansion distance using {@link #getEnvelope(double)}.
73  *
74  * @version 1.7
75  */
76 public class MonotoneChain {
77 
78   private Coordinate[] pts;
79   private int start, end;
80   private Envelope env = null;
81   private Object context = null;// user-defined information
82   private int id;// useful for optimizing chain comparisons
83   //private double overlapDistance;
84 
85   /**
86    * Creates a new MonotoneChain based on the given array of points.
87    * @param pts the points containing the chain
88    * @param start the index of the first coordinate in the chain
89    * @param end the index of the last coordinate in the chain
90    * @param context a user-defined data object
91    */
MonotoneChain(Coordinate[] pts, int start, int end, Object context)92   public MonotoneChain(Coordinate[] pts, int start, int end, Object context)
93   {
94     this.pts    = pts;
95     this.start  = start;
96     this.end    = end;
97     this.context = context;
98   }
99 
100   /**
101    * Sets the id of this chain.
102    * Useful for assigning an ordering to a set of
103    * chains, which can be used to avoid redundant processing.
104    *
105    * @param id an id value
106    */
setId(int id)107   public void setId(int id) { this.id = id; }
108 
109   /**
110    * Sets the overlap distance used in overlap tests
111    * with other chains.
112    *
113    * @param distance the distance to buffer overlap tests by
114    */
setOverlapDistance(double distance)115   public void setOverlapDistance(double distance) {
116     //this.overlapDistance = distance;
117   }
118 
119   /**
120    * Gets the id of this chain.
121    *
122    * @return the id value
123    */
getId()124   public int getId() { return id; }
125 
126   /**
127    * Gets the user-defined context data value.
128    *
129    * @return a data value
130    */
getContext()131   public Object getContext() { return context; }
132 
133   /**
134    * Gets the envelope of the chain.
135    *
136    * @return the envelope of the chain
137    */
getEnvelope()138   public Envelope getEnvelope()
139   {
140     return getEnvelope(0.0);
141   }
142 
143   /**
144    * Gets the envelope for this chain,
145    * expanded by a given distance.
146    *
147    * @param expansionDistance distance to expand the envelope by
148    * @return the expanded envelope of the chain
149    */
getEnvelope(double expansionDistance)150   public Envelope getEnvelope(double expansionDistance)
151   {
152     if (env == null) {
153       /**
154        * The monotonicity property allows fast envelope determination
155        */
156       Coordinate p0 = pts[start];
157       Coordinate p1 = pts[end];
158       env = new Envelope(p0, p1);
159       if (expansionDistance > 0.0)
160         env.expandBy(expansionDistance);
161     }
162     return env;
163   }
164 
165   /**
166    * Gets the index of the start of the monotone chain
167    * in the underlying array of points.
168    *
169    * @return the start index of the chain
170    */
getStartIndex()171   public int getStartIndex()  { return start; }
172 
173   /**
174    * Gets the index of the end of the monotone chain
175    * in the underlying array of points.
176    *
177    * @return the end index of the chain
178    */
getEndIndex()179   public int getEndIndex()    { return end; }
180 
181   /**
182    * Gets the line segment starting at <code>index</code>
183    *
184    * @param index index of segment
185    * @param ls line segment to extract into
186    */
getLineSegment(int index, LineSegment ls)187   public void getLineSegment(int index, LineSegment ls)
188   {
189     ls.p0 = pts[index];
190     ls.p1 = pts[index + 1];
191   }
192   /**
193    * Return the subsequence of coordinates forming this chain.
194    * Allocates a new array to hold the Coordinates
195    */
getCoordinates()196   public Coordinate[] getCoordinates()
197   {
198     Coordinate coord[] = new Coordinate[end - start + 1];
199     int index = 0;
200     for (int i = start; i <= end; i++) {
201       coord[index++] = pts[i];
202     }
203     return coord;
204   }
205 
206   /**
207    * Determine all the line segments in the chain whose envelopes overlap
208    * the searchEnvelope, and process them.
209    * <p>
210    * The monotone chain search algorithm attempts to optimize
211    * performance by not calling the select action on chain segments
212    * which it can determine are not in the search envelope.
213    * However, it *may* call the select action on segments
214    * which do not intersect the search envelope.
215    * This saves on the overhead of checking envelope intersection
216    * each time, since clients may be able to do this more efficiently.
217    *
218    * @param searchEnv the search envelope
219    * @param mcs the select action to execute on selected segments
220    */
select(Envelope searchEnv, MonotoneChainSelectAction mcs)221   public void select(Envelope searchEnv, MonotoneChainSelectAction mcs)
222   {
223     computeSelect(searchEnv, start, end, mcs);
224   }
225 
computeSelect( Envelope searchEnv, int start0, int end0, MonotoneChainSelectAction mcs )226   private void computeSelect(
227     Envelope searchEnv,
228     int start0, int end0,
229     MonotoneChainSelectAction mcs )
230   {
231     Coordinate p0 = pts[start0];
232     Coordinate p1 = pts[end0];
233 
234 //Debug.println("trying:" + p0 + p1 + " [ " + start0 + ", " + end0 + " ]");
235     // terminating condition for the recursion
236     if (end0 - start0 == 1) {
237       //Debug.println("computeSelect:" + p0 + p1);
238       mcs.select(this, start0);
239       return;
240     }
241     // nothing to do if the envelopes don't overlap
242     if (! searchEnv.intersects(p0, p1))
243       return;
244 
245     // the chains overlap, so split each in half and iterate  (binary search)
246     int mid = (start0 + end0) / 2;
247 
248     // Assert: mid != start or end (since we checked above for end - start <= 1)
249     // check terminating conditions before recursing
250     if (start0 < mid) {
251       computeSelect(searchEnv, start0, mid, mcs);
252     }
253     if (mid < end0) {
254       computeSelect(searchEnv, mid, end0, mcs);
255     }
256   }
257 
258   /**
259    * Determines the line segments in two chains which may overlap,
260    * and passes them to an overlap action.
261    * <p>
262    * The monotone chain search algorithm attempts to optimize
263    * performance by not calling the overlap action on chain segments
264    * which it can determine do not overlap.
265    * However, it *may* call the overlap action on segments
266    * which do not actually interact.
267    * This saves on the overhead of checking intersection
268    * each time, since clients may be able to do this more efficiently.
269    *
270    * @param mc the chain to compare to
271    * @param mco the overlap action to execute on overlapping segments
272    */
computeOverlaps(MonotoneChain mc, MonotoneChainOverlapAction mco)273   public void computeOverlaps(MonotoneChain mc, MonotoneChainOverlapAction mco)
274   {
275     computeOverlaps(start, end, mc, mc.start, mc.end, 0.0, mco);
276   }
277 
278   /**
279    * Determines the line segments in two chains which may overlap,
280    * using an overlap distance tolerance,
281    * and passes them to an overlap action.
282    *
283    * @param mc the chain to compare to
284    * @param overlapTolerance the distance tolerance for the overlap test
285    * @param mco the overlap action to execute on selected segments
286    */
computeOverlaps(MonotoneChain mc, double overlapTolerance, MonotoneChainOverlapAction mco)287   public void computeOverlaps(MonotoneChain mc, double overlapTolerance, MonotoneChainOverlapAction mco)
288   {
289     computeOverlaps(start, end, mc, mc.start, mc.end, overlapTolerance, mco);
290   }
291 
292   /**
293    * Uses an efficient mutual binary search strategy
294    * to determine which pairs of chain segments
295    * may overlap, and calls the given overlap action on them.
296    *
297    * @param start0 the start index of this chain section
298    * @param end0 the end index of this chain section
299    * @param mc the target monotone chain
300    * @param start1 the start index of the target chain section
301    * @param end1 the end index of the target chain section
302    * @param overlapTolerance the overlap tolerance distance (may be 0)
303    * @param mco the overlap action to execute on selected segments
304    */
computeOverlaps( int start0, int end0, MonotoneChain mc, int start1, int end1, double overlapTolerance, MonotoneChainOverlapAction mco)305   private void computeOverlaps(
306     int start0, int end0,
307     MonotoneChain mc,
308     int start1, int end1,
309     double overlapTolerance,
310     MonotoneChainOverlapAction mco)
311   {
312 //Debug.println("computeIntersectsForChain:" + p00 + p01 + p10 + p11);
313     // terminating condition for the recursion
314     if (end0 - start0 == 1 && end1 - start1 == 1) {
315       mco.overlap(this, start0, mc, start1);
316       return;
317     }
318     // nothing to do if the envelopes of these subchains don't overlap
319     if (! overlaps(start0, end0, mc, start1, end1, overlapTolerance)) return;
320 
321     // the chains overlap, so split each in half and iterate  (binary search)
322     int mid0 = (start0 + end0) / 2;
323     int mid1 = (start1 + end1) / 2;
324 
325     // Assert: mid != start or end (since we checked above for end - start <= 1)
326     // check terminating conditions before recursing
327     if (start0 < mid0) {
328       if (start1 < mid1) computeOverlaps(start0, mid0, mc, start1,  mid1, overlapTolerance, mco);
329       if (mid1 < end1)   computeOverlaps(start0, mid0, mc, mid1,    end1, overlapTolerance, mco);
330     }
331     if (mid0 < end0) {
332       if (start1 < mid1) computeOverlaps(mid0,   end0, mc, start1,  mid1, overlapTolerance, mco);
333       if (mid1 < end1)   computeOverlaps(mid0,   end0, mc, mid1,    end1, overlapTolerance, mco);
334     }
335   }
336 
337   /**
338    * Tests whether the envelope of a section of the chain
339    * overlaps (intersects) the envelope of a section of another target chain.
340    * This test is efficient due to the monotonicity property
341    * of the sections (i.e. the envelopes can be are determined
342    * from the section endpoints
343    * rather than a full scan).
344    *
345    * @param start0 the start index of this chain section
346    * @param end0 the end index of this chain section
347    * @param mc the target monotone chain
348    * @param start1 the start index of the target chain section
349    * @param end1 the end index of the target chain section
350    * @param overlapTolerance
351    * @return true if the section envelopes overlap
352    */
overlaps( int start0, int end0, MonotoneChain mc, int start1, int end1, double overlapTolerance)353   private boolean overlaps(
354       int start0, int end0,
355       MonotoneChain mc,
356       int start1, int end1,
357       double overlapTolerance)
358   {
359     if (overlapTolerance > 0.0) {
360       return overlaps(pts[start0], pts[end0], mc.pts[start1], mc.pts[end1], overlapTolerance);
361     }
362     return Envelope.intersects(pts[start0], pts[end0], mc.pts[start1], mc.pts[end1]);
363   }
364 
overlaps(Coordinate p1, Coordinate p2, Coordinate q1, Coordinate q2, double overlapTolerance)365   private boolean overlaps(Coordinate p1, Coordinate p2, Coordinate q1, Coordinate q2, double overlapTolerance)
366   {
367     double minq = Math.min(q1.x, q2.x);
368     double maxq = Math.max(q1.x, q2.x);
369     double minp = Math.min(p1.x, p2.x);
370     double maxp = Math.max(p1.x, p2.x);
371 
372     if( minp > maxq + overlapTolerance )
373         return false;
374     if( maxp < minq - overlapTolerance )
375         return false;
376 
377     minq = Math.min(q1.y, q2.y);
378     maxq = Math.max(q1.y, q2.y);
379     minp = Math.min(p1.y, p2.y);
380     maxp = Math.max(p1.y, p2.y);
381 
382     if( minp > maxq + overlapTolerance )
383         return false;
384     if( maxp < minq - overlapTolerance )
385         return false;
386     return true;
387   }
388 
389 }
390