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