| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
|
| 13 |
package org.locationtech.jts.edgegraph; |
| 14 |
|
| 15 |
import org.locationtech.jts.algorithm.Orientation; |
| 16 |
import org.locationtech.jts.geom.Coordinate; |
| 17 |
import org.locationtech.jts.geomgraph.Quadrant; |
| 18 |
import org.locationtech.jts.io.WKTWriter; |
| 19 |
import org.locationtech.jts.util.Assert; |
| 20 |
|
| 21 |
/** |
| 22 |
* Represents a directed component of an edge in an {@link EdgeGraph}. |
| 23 |
* HalfEdges link vertices whose locations are defined by {@link Coordinate}s. |
| 24 |
* HalfEdges start at an <b>origin</b> vertex, |
| 25 |
* and terminate at a <b>destination</b> vertex. |
| 26 |
* HalfEdges always occur in symmetric pairs, with the {@link #sym()} method |
| 27 |
* giving access to the oppositely-oriented component. |
| 28 |
* HalfEdges and the methods on them form an edge algebra, |
| 29 |
* which can be used to traverse and query the topology |
| 30 |
* of the graph formed by the edges. |
| 31 |
* <p> |
| 32 |
* To support graphs where the edges are sequences of coordinates |
| 33 |
* each edge may also have a direction point supplied. |
| 34 |
* This is used to determine the ordering |
| 35 |
* of the edges around the origin. |
| 36 |
* HalfEdges with the same origin are ordered |
| 37 |
* so that the ring of edges formed by them is oriented CCW. |
| 38 |
* <p> |
| 39 |
* By design HalfEdges carry minimal information |
| 40 |
* about the actual usage of the graph they represent. |
| 41 |
* They can be subclassed to carry more information if required. |
| 42 |
* <p> |
| 43 |
* HalfEdges form a complete and consistent data structure by themselves, |
| 44 |
* but an {@link EdgeGraph} is useful to allow retrieving edges |
| 45 |
* by vertex and edge location, as well as ensuring |
| 46 |
* edges are created and linked appropriately. |
| 47 |
* |
| 48 |
* @author Martin Davis |
| 49 |
* |
| 50 |
*/ |
| 51 |
public class HalfEdge { |
| 52 |
|
| 53 |
/** |
| 54 |
* Creates a HalfEdge pair representing an edge |
| 55 |
* between two vertices located at coordinates p0 and p1. |
| 56 |
* |
| 57 |
* @param p0 a vertex coordinate |
| 58 |
* @param p1 a vertex coordinate |
| 59 |
* @return the HalfEdge with origin at p0 |
| 60 |
*/ |
| 61 |
public static HalfEdge create(Coordinate p0, Coordinate p1) { |
| 62 |
HalfEdge e0 = new HalfEdge(p0); |
| 63 |
HalfEdge e1 = new HalfEdge(p1); |
| 64 |
e0.link(e1); |
| 65 |
return e0; |
| 66 |
} |
| 67 |
|
| 68 |
private Coordinate orig; |
| 69 |
private HalfEdge sym; |
| 70 |
private HalfEdge next; |
| 71 |
|
| 72 |
/** |
| 73 |
* Creates a half-edge originating from a given coordinate. |
| 74 |
* |
| 75 |
* @param orig the origin coordinate |
| 76 |
*/ |
| 77 |
public HalfEdge(Coordinate orig) { |
| 78 |
this.orig = orig; |
| 79 |
} |
| 80 |
|
| 81 |
/** |
| 82 |
* Links this edge with its sym (opposite) edge. |
| 83 |
* This must be done for each pair of edges created. |
| 84 |
* |
| 85 |
* @param e the sym edge to link. |
| 86 |
*/ |
| 87 |
public void link(HalfEdge sym) |
| 88 |
{ |
| 89 |
setSym(sym); |
| 90 |
sym.setSym(this); |
| 91 |
|
| 92 |
setNext(sym); |
| 93 |
sym.setNext(this); |
| 94 |
} |
| 95 |
|
| 96 |
/** |
| 97 |
* Gets the origin coordinate of this edge. |
| 98 |
* |
| 99 |
* @return the origin coordinate |
| 100 |
*/ |
| 101 |
public Coordinate orig() { return orig; } |
| 102 |
|
| 103 |
/** |
| 104 |
* Gets the destination coordinate of this edge. |
| 105 |
* |
| 106 |
* @return the destination coordinate |
| 107 |
*/ |
| 108 |
public Coordinate dest() { return sym.orig; } |
| 109 |
|
| 110 |
/** |
| 111 |
* The X component of the direction vector. |
| 112 |
* |
| 113 |
* @return the X component of the direction vector |
| 114 |
*/ |
| 115 |
double directionX() { return directionPt().getX() - orig.getX(); } |
| 116 |
|
| 117 |
/** |
| 118 |
* The Y component of the direction vector. |
| 119 |
* |
| 120 |
* @return the Y component of the direction vector |
| 121 |
*/ |
| 122 |
double directionY() { return directionPt().getY() - orig.getY(); } |
| 123 |
|
| 124 |
/** |
| 125 |
* Gets the direction point of this edge. |
| 126 |
* In the base case this is the dest coordinate |
| 127 |
* of the edge. |
| 128 |
* Subclasses may override to |
| 129 |
* allow a HalfEdge to represent an edge with more than two coordinates. |
| 130 |
* |
| 131 |
* @return the direction point for the edge |
| 132 |
*/ |
| 133 |
protected Coordinate directionPt() { |
| 134 |
|
| 135 |
|
| 136 |
return dest(); |
| 137 |
} |
| 138 |
|
| 139 |
/** |
| 140 |
* Gets the symmetric pair edge of this edge. |
| 141 |
* |
| 142 |
* @return the symmetric pair edge |
| 143 |
*/ |
| 144 |
public HalfEdge sym() |
| 145 |
{ |
| 146 |
return sym; |
| 147 |
} |
| 148 |
|
| 149 |
/** |
| 150 |
* Sets the symmetric (opposite) edge to this edge. |
| 151 |
* |
| 152 |
* @param e the sym edge to set |
| 153 |
*/ |
| 154 |
private void setSym(HalfEdge e) { |
| 155 |
sym = e; |
| 156 |
} |
| 157 |
|
| 158 |
/** |
| 159 |
* Gets the next edge CCW around the |
| 160 |
* destination vertex of this edge, |
| 161 |
* with the dest vertex as its origin. |
| 162 |
* If the vertex has degree 1 then this is the <b>sym</b> edge. |
| 163 |
* |
| 164 |
* @return the next edge |
| 165 |
*/ |
| 166 |
public HalfEdge next() |
| 167 |
{ |
| 168 |
return next; |
| 169 |
} |
| 170 |
|
| 171 |
/** |
| 172 |
* Gets the edge previous to this one |
| 173 |
* (with dest being the same as this orig). |
| 174 |
* |
| 175 |
* @return the previous edge to this one |
| 176 |
*/ |
| 177 |
public HalfEdge prev() { |
| 178 |
return sym.next().sym; |
| 179 |
} |
| 180 |
|
| 181 |
/** |
| 182 |
* Gets the next edge CCW around the origin of this edge, |
| 183 |
* with the same origin. |
| 184 |
* |
| 185 |
* @return the next edge around the origin |
| 186 |
*/ |
| 187 |
public HalfEdge oNext() { |
| 188 |
return sym.next; |
| 189 |
} |
| 190 |
|
| 191 |
/** |
| 192 |
* Sets the next edge CCW around the destination vertex of this edge. |
| 193 |
* |
| 194 |
* @param e the next edge |
| 195 |
*/ |
| 196 |
public void setNext(HalfEdge e) |
| 197 |
{ |
| 198 |
next = e; |
| 199 |
} |
| 200 |
|
| 201 |
/** |
| 202 |
* Finds the edge starting at the origin of this edge |
| 203 |
* with the given dest vertex, |
| 204 |
* if any. |
| 205 |
* |
| 206 |
* @param dest the dest vertex to search for |
| 207 |
* @return the edge with the required dest vertex, if it exists, |
| 208 |
* or null |
| 209 |
*/ |
| 210 |
public HalfEdge find(Coordinate dest) { |
| 211 |
HalfEdge oNext = this; |
| 212 |
do { |
| 213 |
if (oNext == null) return null; |
| 214 |
if (oNext.dest().equals2D(dest)) |
| 215 |
return oNext; |
| 216 |
oNext = oNext.oNext(); |
| 217 |
} while (oNext != this); |
| 218 |
return null; |
| 219 |
} |
| 220 |
|
| 221 |
/** |
| 222 |
* Tests whether this edge has the given orig and dest vertices. |
| 223 |
* |
| 224 |
* @param p0 the origin vertex to test |
| 225 |
* @param p1 the destination vertex to test |
| 226 |
* @return true if the vertices are equal to the ones of this edge |
| 227 |
*/ |
| 228 |
public boolean equals(Coordinate p0, Coordinate p1) { |
| 229 |
return orig.equals2D(p0) && sym.orig.equals(p1); |
| 230 |
} |
| 231 |
|
| 232 |
/** |
| 233 |
* Inserts an edge |
| 234 |
* into the ring of edges around the origin vertex of this edge, |
| 235 |
* ensuring that the edges remain ordered CCW. |
| 236 |
* The inserted edge must have the same origin as this edge. |
| 237 |
* |
| 238 |
* @param eAdd the edge to insert |
| 239 |
*/ |
| 240 |
public void insert(HalfEdge eAdd) { |
| 241 |
|
| 242 |
if (oNext() == this) { |
| 243 |
|
| 244 |
insertAfter(eAdd); |
| 245 |
return; |
| 246 |
} |
| 247 |
|
| 248 |
|
| 249 |
|
| 250 |
HalfEdge ePrev = insertionEdge(eAdd); |
| 251 |
ePrev.insertAfter(eAdd); |
| 252 |
} |
| 253 |
|
| 254 |
/** |
| 255 |
* Finds the insertion edge for a edge |
| 256 |
* being added to this origin, |
| 257 |
* ensuring that the star of edges |
| 258 |
* around the origin remains fully CCW. |
| 259 |
* |
| 260 |
* @param eAdd the edge being added |
| 261 |
* @return the edge to insert after |
| 262 |
*/ |
| 263 |
private HalfEdge insertionEdge(HalfEdge eAdd) { |
| 264 |
HalfEdge ePrev = this; |
| 265 |
do { |
| 266 |
HalfEdge eNext = ePrev.oNext(); |
| 267 |
/** |
| 268 |
* Case 1: General case, |
| 269 |
* with eNext higher than ePrev. |
| 270 |
* |
| 271 |
* Insert edge here if it lies between ePrev and eNext. |
| 272 |
*/ |
| 273 |
if (eNext.compareTo(ePrev) > 0 |
| 274 |
&& eAdd.compareTo(ePrev) >= 0 |
| 275 |
&& eAdd.compareTo(eNext) <= 0) { |
| 276 |
return ePrev; |
| 277 |
} |
| 278 |
/** |
| 279 |
* Case 2: Origin-crossing case, |
| 280 |
* indicated by eNext <= ePrev. |
| 281 |
* |
| 282 |
* Insert edge here if it lies |
| 283 |
* in the gap between ePrev and eNext across the origin. |
| 284 |
*/ |
| 285 |
if (eNext.compareTo(ePrev) <= 0 |
| 286 |
&& (eAdd.compareTo(eNext) <= 0 || eAdd.compareTo(ePrev) >= 0)) { |
| 287 |
return ePrev; |
| 288 |
} |
| 289 |
ePrev = eNext; |
| 290 |
} while (ePrev != this); |
| 291 |
Assert.shouldNeverReachHere(); |
| 292 |
return null; |
| 293 |
} |
| 294 |
|
| 295 |
/** |
| 296 |
* Insert an edge with the same origin after this one. |
| 297 |
* Assumes that the inserted edge is in the correct |
| 298 |
* position around the ring. |
| 299 |
* |
| 300 |
* @param e the edge to insert (with same origin) |
| 301 |
*/ |
| 302 |
private void insertAfter(HalfEdge e) { |
| 303 |
Assert.equals(orig, e.orig()); |
| 304 |
HalfEdge save = oNext(); |
| 305 |
sym.setNext(e); |
| 306 |
e.sym().setNext(save); |
| 307 |
} |
| 308 |
|
| 309 |
/** |
| 310 |
* Tests whether the edges around the origin |
| 311 |
* are sorted correctly. |
| 312 |
* Note that edges must be strictly increasing, |
| 313 |
* which implies no two edges can have the same direction point. |
| 314 |
* |
| 315 |
* @return true if the origin edges are sorted correctly |
| 316 |
*/ |
| 317 |
public boolean isEdgesSorted() { |
| 318 |
|
| 319 |
HalfEdge lowest = findLowest(); |
| 320 |
HalfEdge e = lowest; |
| 321 |
|
| 322 |
do { |
| 323 |
HalfEdge eNext = e.oNext(); |
| 324 |
if (eNext == lowest) break; |
| 325 |
boolean isSorted = eNext.compareTo(e) > 0; |
| 326 |
if (! isSorted) { |
| 327 |
|
| 328 |
return false; |
| 329 |
} |
| 330 |
e = eNext; |
| 331 |
} while (e != lowest); |
| 332 |
return true; |
| 333 |
} |
| 334 |
|
| 335 |
/** |
| 336 |
* Finds the lowest edge around the origin, |
| 337 |
* using the standard edge ordering. |
| 338 |
* |
| 339 |
* @return the lowest edge around the origin |
| 340 |
*/ |
| 341 |
private HalfEdge findLowest() { |
| 342 |
HalfEdge lowest = this; |
| 343 |
HalfEdge e = this.oNext(); |
| 344 |
do { |
| 345 |
if (e.compareTo(lowest) < 0) |
| 346 |
lowest = e; |
| 347 |
e = e.oNext(); |
| 348 |
} while (e != this); |
| 349 |
return lowest; |
| 350 |
} |
| 351 |
|
| 352 |
/** |
| 353 |
* Compares edges which originate at the same vertex |
| 354 |
* based on the angle they make at their origin vertex with the positive X-axis. |
| 355 |
* This allows sorting edges around their origin vertex in CCW order. |
| 356 |
*/ |
| 357 |
public int compareTo(Object obj) |
| 358 |
{ |
| 359 |
HalfEdge e = (HalfEdge) obj; |
| 360 |
int comp = compareAngularDirection(e); |
| 361 |
return comp; |
| 362 |
} |
| 363 |
|
| 364 |
/** |
| 365 |
* Implements the total order relation: |
| 366 |
* <p> |
| 367 |
* The angle of edge a is greater than the angle of edge b, |
| 368 |
* where the angle of an edge is the angle made by |
| 369 |
* the first segment of the edge with the positive x-axis |
| 370 |
* <p> |
| 371 |
* When applied to a list of edges originating at the same point, |
| 372 |
* this produces a CCW ordering of the edges around the point. |
| 373 |
* <p> |
| 374 |
* Using the obvious algorithm of computing the angle is not robust, |
| 375 |
* since the angle calculation is susceptible to roundoff error. |
| 376 |
* A robust algorithm is: |
| 377 |
* <ul> |
| 378 |
* <li>First, compare the quadrants the edge vectors lie in. |
| 379 |
* If the quadrants are different, |
| 380 |
* it is trivial to determine which edge has a greater angle. |
| 381 |
* |
| 382 |
* <li>if the vectors lie in the same quadrant, the |
| 383 |
* {@link Orientation#index(Coordinate, Coordinate, Coordinate)} function |
| 384 |
* can be used to determine the relative orientation of the vectors. |
| 385 |
* </ul> |
| 386 |
*/ |
| 387 |
public int compareAngularDirection(HalfEdge e) |
| 388 |
{ |
| 389 |
double dx = directionX(); |
| 390 |
double dy = directionY(); |
| 391 |
double dx2 = e.directionX(); |
| 392 |
double dy2 = e.directionY(); |
| 393 |
|
| 394 |
|
| 395 |
if (dx == dx2 && dy == dy2) |
| 396 |
return 0; |
| 397 |
|
| 398 |
int quadrant = Quadrant.quadrant(dx, dy); |
| 399 |
int quadrant2 = Quadrant.quadrant(dx2, dy2); |
| 400 |
|
| 401 |
/** |
| 402 |
* If the direction vectors are in different quadrants, |
| 403 |
* that determines the ordering |
| 404 |
*/ |
| 405 |
if (quadrant > quadrant2) return 1; |
| 406 |
if (quadrant < quadrant2) return -1; |
| 407 |
|
| 408 |
|
| 409 |
|
| 410 |
|
| 411 |
Coordinate dir1 = directionPt(); |
| 412 |
Coordinate dir2 = e.directionPt(); |
| 413 |
return Orientation.index(e.orig, dir2, dir1); |
| 414 |
} |
| 415 |
|
| 416 |
/** |
| 417 |
* Computes a string representation of a HalfEdge. |
| 418 |
* |
| 419 |
* @return a string representation |
| 420 |
*/ |
| 421 |
public String toString() |
| 422 |
{ |
| 423 |
return "HE("+orig.x + " " + orig.y |
| 424 |
+ ", " |
| 425 |
+ sym.orig.x + " " + sym.orig.y |
| 426 |
+ ")"; |
| 427 |
} |
| 428 |
|
| 429 |
public String toStringNode() { |
| 430 |
Coordinate orig = orig(); |
| 431 |
Coordinate dest = dest(); |
| 432 |
StringBuilder sb = new StringBuilder(); |
| 433 |
sb.append("Node( " + WKTWriter.format(orig) + " )" + "\n"); |
| 434 |
HalfEdge e = this; |
| 435 |
do { |
| 436 |
sb.append(" -> " + e); |
| 437 |
sb.append("\n"); |
| 438 |
e = e.oNext(); |
| 439 |
} while (e != this); |
| 440 |
return sb.toString(); |
| 441 |
} |
| 442 |
|
| 443 |
private String toStringNodeEdge() { |
| 444 |
return " -> (" + WKTWriter.format(dest()); |
| 445 |
} |
| 446 |
|
| 447 |
/** |
| 448 |
* Computes the degree of the origin vertex. |
| 449 |
* The degree is the number of edges |
| 450 |
* originating from the vertex. |
| 451 |
* |
| 452 |
* @return the degree of the origin vertex |
| 453 |
*/ |
| 454 |
public int degree() { |
| 455 |
int degree = 0; |
| 456 |
HalfEdge e = this; |
| 457 |
do { |
| 458 |
degree++; |
| 459 |
e = e.oNext(); |
| 460 |
} while (e != this); |
| 461 |
return degree; |
| 462 |
} |
| 463 |
|
| 464 |
/** |
| 465 |
* Finds the first node previous to this edge, if any. |
| 466 |
* If no such node exists (i.e. the edge is part of a ring) |
| 467 |
* then null is returned. |
| 468 |
* |
| 469 |
* @return an edge originating at the node prior to this edge, if any, |
| 470 |
* or null if no node exists |
| 471 |
*/ |
| 472 |
public HalfEdge prevNode() { |
| 473 |
HalfEdge e = this; |
| 474 |
while (e.degree() == 2) { |
| 475 |
e = e.prev(); |
| 476 |
if (e == this) |
| 477 |
return null; |
| 478 |
} |
| 479 |
return e; |
| 480 |
} |
| 481 |
|
| 482 |
} |
| 483 |
|