GEOS  3.13.1
LineIntersector.h
1 /**********************************************************************
2  *
3  * GEOS - Geometry Engine Open Source
4  * http://geos.osgeo.org
5  *
6  * Copyright (C) 2005-2006 Refractions Research Inc.
7  * Copyright (C) 2001-2002 Vivid Solutions Inc.
8  *
9  * This is free software; you can redistribute and/or modify it under
10  * the terms of the GNU Lesser General Public Licence as published
11  * by the Free Software Foundation.
12  * See the COPYING file for more information.
13  *
14  **********************************************************************
15  *
16  * Last port: algorithm/RobustLineIntersector.java r785 (JTS-1.13+)
17  *
18  **********************************************************************/
19 
20 #pragma once
21 
22 #include <geos/export.h>
23 #include <geos/algorithm/Intersection.h>
24 #include <geos/algorithm/Interpolate.h>
25 #include <geos/algorithm/Orientation.h>
26 #include <geos/geom/Coordinate.h>
27 #include <geos/geom/Envelope.h>
28 #include <geos/geom/PrecisionModel.h>
29 
30 #include <string>
31 
32 // Forward declarations
33 namespace geos {
34 namespace geom {
35 class PrecisionModel;
36 }
37 }
38 
39 namespace geos {
40 namespace algorithm { // geos::algorithm
41 
53 class GEOS_DLL LineIntersector {
54 public:
55 
74  static double computeEdgeDistance(const geom::CoordinateXY& p, const geom::CoordinateXY& p0, const geom::CoordinateXY& p1);
75 
76  static double nonRobustComputeEdgeDistance(const geom::Coordinate& p, const geom::Coordinate& p1,
77  const geom::Coordinate& p2);
78 
79  explicit LineIntersector(const geom::PrecisionModel* initialPrecisionModel = nullptr)
80  :
81  precisionModel(initialPrecisionModel),
82  result(0),
83  inputLines(),
84  isProperVar(false)
85  {}
86 
87  ~LineIntersector() = default;
88 
97  {
98  if(isInteriorIntersection(0)) {
99  return true;
100  }
101  if(isInteriorIntersection(1)) {
102  return true;
103  }
104  return false;
105  };
106 
114  bool isInteriorIntersection(std::size_t inputLineIndex)
115  {
116  for(std::size_t i = 0; i < result; ++i) {
117  if(!(intPt[i].equals2D(*inputLines[inputLineIndex][0])
118  || intPt[i].equals2D(*inputLines[inputLineIndex][1]))) {
119  return true;
120  }
121  }
122  return false;
123  };
124 
131  void
133  {
134  precisionModel = newPM;
135  }
136 
137  enum intersection_type : uint8_t {
139  NO_INTERSECTION = 0,
140 
142  POINT_INTERSECTION = 1,
143 
145  COLLINEAR_INTERSECTION = 2
146  };
147 
149  template<typename C1, typename C2>
150  void computeIntersection(const C1& p1, const C1& p2,
151  const C2& p3, const C2& p4)
152  {
153  inputLines[0][0] = &p1;
154  inputLines[0][1] = &p2;
155  inputLines[1][0] = &p3;
156  inputLines[1][1] = &p4;
157  result = computeIntersect(p1, p2, p3, p4);
158  }
159 
161  void computeIntersection(const geom::CoordinateSequence& p, std::size_t p0,
162  const geom::CoordinateSequence& q, std::size_t q0);
163 
164  std::string toString() const;
165 
171  bool
173  {
174  return result != NO_INTERSECTION;
175  }
176 
177 
185  const geom::CoordinateXY*
186  getEndpoint(std::size_t segmentIndex, std::size_t ptIndex) const
187  {
188  return inputLines[segmentIndex][ptIndex];
189  }
190 
195  size_t
197  {
198  return result;
199  }
200 
201 
208  const geom::CoordinateXYZM&
209  getIntersection(std::size_t intIndex) const
210  {
211  return intPt[intIndex];
212  }
213 
218  static bool isSameSignAndNonZero(double a, double b);
219 
230  bool isIntersection(const geom::Coordinate& pt) const
231  {
232  for(std::size_t i = 0; i < result; ++i) {
233  if(intPt[i].equals2D(pt)) {
234  return true;
235  }
236  }
237  return false;
238  };
239 
254  bool
255  isProper() const
256  {
257  return hasIntersection() && isProperVar;
258  }
259 
270  const geom::Coordinate& getIntersectionAlongSegment(std::size_t segmentIndex, std::size_t intIndex);
271 
281  std::size_t getIndexAlongSegment(std::size_t segmentIndex, std::size_t intIndex);
282 
292  double getEdgeDistance(std::size_t geomIndex, std::size_t intIndex) const;
293 
294 private:
295 
300  const geom::PrecisionModel* precisionModel;
301 
302  std::size_t result;
303 
304  const geom::CoordinateXY* inputLines[2][2];
305 
310  geom::CoordinateXYZM intPt[2];
311 
316  std::size_t intLineIndex[2][2];
317 
318  bool isProperVar;
319  //Coordinate &pa;
320  //Coordinate &pb;
321 
322  bool
323  isCollinear() const
324  {
325  return result == COLLINEAR_INTERSECTION;
326  }
327 
328  template<typename C1, typename C2>
329  uint8_t computeIntersect(const C1& p1, const C1& p2, const C2& q1, const C2& q2)
330  {
331  isProperVar = false;
332 
333  // first try a fast test to see if the envelopes of the lines intersect
334  if(!geom::Envelope::intersects(p1, p2, q1, q2)) {
335  return NO_INTERSECTION;
336  }
337 
338  // for each endpoint, compute which side of the other segment it lies
339  // if both endpoints lie on the same side of the other segment,
340  // the segments do not intersect
341  int Pq1 = Orientation::index(p1, p2, q1);
342  int Pq2 = Orientation::index(p1, p2, q2);
343 
344  if((Pq1 > 0 && Pq2 > 0) || (Pq1 < 0 && Pq2 < 0)) {
345  return NO_INTERSECTION;
346  }
347 
348  int Qp1 = Orientation::index(q1, q2, p1);
349  int Qp2 = Orientation::index(q1, q2, p2);
350 
351  if((Qp1 > 0 && Qp2 > 0) || (Qp1 < 0 && Qp2 < 0)) {
352  return NO_INTERSECTION;
353  }
354 
358  bool collinear = Pq1 == 0 && Pq2 == 0 && Qp1 == 0 && Qp2 == 0;
359  if(collinear) {
360  return computeCollinearIntersection(p1, p2, q1, q2);
361  }
362 
363  /*
364  * At this point we know that there is a single intersection point
365  * (since the lines are not collinear).
366  */
367 
368  /*
369  * Check if the intersection is an endpoint.
370  * If it is, copy the endpoint as
371  * the intersection point. Copying the point rather than
372  * computing it ensures the point has the exact value,
373  * which is important for robustness. It is sufficient to
374  * simply check for an endpoint which is on the other line,
375  * since at this point we know that the inputLines must
376  * intersect.
377  */
378  geom::CoordinateXYZM p;
379  double z = DoubleNotANumber;
380  double m = DoubleNotANumber;
381 
382  if(Pq1 == 0 || Pq2 == 0 || Qp1 == 0 || Qp2 == 0) {
383 
384  isProperVar = false;
385 
386  /* Check for two equal endpoints.
387  * This is done explicitly rather than by the orientation tests
388  * below in order to improve robustness.
389  *
390  * (A example where the orientation tests fail
391  * to be consistent is:
392  *
393  * LINESTRING ( 19.850257749638203 46.29709338043669,
394  * 20.31970698357233 46.76654261437082 )
395  * and
396  * LINESTRING ( -48.51001596420236 -22.063180333403878,
397  * 19.850257749638203 46.29709338043669 )
398  *
399  * which used to produce the INCORRECT result:
400  * (20.31970698357233, 46.76654261437082, NaN)
401  */
402 
403  if (p1.equals2D(q1)) {
404  p = p1;
405  z = Interpolate::zGet(p1, q1);
406  m = Interpolate::mGet(p1, q1);
407  }
408  else if (p1.equals2D(q2)) {
409  p = p1;
410  z = Interpolate::zGet(p1, q2);
411  m = Interpolate::mGet(p1, q2);
412  }
413  else if (p2.equals2D(q1)) {
414  p = p2;
415  z = Interpolate::zGet(p2, q1);
416  m = Interpolate::mGet(p2, q1);
417  }
418  else if (p2.equals2D(q2)) {
419  p = p2;
420  z = Interpolate::zGet(p2, q2);
421  m = Interpolate::mGet(p2, q2);
422  }
423  /*
424  * Now check to see if any endpoint lies on the interior of the other segment.
425  */
426  else if(Pq1 == 0) {
427  p = q1;
428  z = Interpolate::zGetOrInterpolate(q1, p1, p2);
429  m = Interpolate::mGetOrInterpolate(q1, p1, p2);
430  }
431  else if(Pq2 == 0) {
432  p = q2;
433  z = Interpolate::zGetOrInterpolate(q2, p1, p2);
434  m = Interpolate::mGetOrInterpolate(q2, p1, p2);
435  }
436  else if(Qp1 == 0) {
437  p = p1;
438  z = Interpolate::zGetOrInterpolate(p1, q1, q2);
439  m = Interpolate::mGetOrInterpolate(p1, q1, q2);
440  }
441  else if(Qp2 == 0) {
442  p = p2;
443  z = Interpolate::zGetOrInterpolate(p2, q1, q2);
444  m = Interpolate::mGetOrInterpolate(p2, q1, q2);
445  }
446  } else {
447  isProperVar = true;
448  p = intersection(p1, p2, q1, q2);
449  z = Interpolate::zInterpolate(p, p1, p2, q1, q2);
450  m = Interpolate::mInterpolate(p, p1, p2, q1, q2);
451  }
452  intPt[0] = geom::CoordinateXYZM(p.x, p.y, z, m);
453  #if GEOS_DEBUG
454  std::cerr << " POINT_INTERSECTION; intPt[0]:" << intPt[0].toString() << std::endl;
455  #endif // GEOS_DEBUG
456  return POINT_INTERSECTION;
457  }
458 
459  bool
460  isEndPoint() const
461  {
462  return hasIntersection() && !isProperVar;
463  }
464 
465  void computeIntLineIndex();
466 
467  void computeIntLineIndex(std::size_t segmentIndex);
468 
469  template<typename C1, typename C2>
470  uint8_t computeCollinearIntersection(const C1& p1, const C1& p2, const C2& q1, const C2& q2)
471  {
472  bool q1inP = geom::Envelope::intersects(p1, p2, q1);
473  bool q2inP = geom::Envelope::intersects(p1, p2, q2);
474  bool p1inQ = geom::Envelope::intersects(q1, q2, p1);
475  bool p2inQ = geom::Envelope::intersects(q1, q2, p2);
476 
477  if(q1inP && q2inP) {
478  intPt[0] = zmGetOrInterpolateCopy(q1, p1, p2);
479  intPt[1] = zmGetOrInterpolateCopy(q2, p1, p2);
480  return COLLINEAR_INTERSECTION;
481  }
482  if(p1inQ && p2inQ) {
483  intPt[0] = zmGetOrInterpolateCopy(p1, q1, q2);
484  intPt[1] = zmGetOrInterpolateCopy(p2, q1, q2);
485  return COLLINEAR_INTERSECTION;
486  }
487  if(q1inP && p1inQ) {
488  // if pts are equal Z is chosen arbitrarily
489  intPt[0] = zmGetOrInterpolateCopy(q1, p1, p2);
490  intPt[1] = zmGetOrInterpolateCopy(p1, q1, q2);
491 
492  return (q1 == p1) && !q2inP && !p2inQ ? POINT_INTERSECTION : COLLINEAR_INTERSECTION;
493  }
494  if(q1inP && p2inQ) {
495  // if pts are equal Z is chosen arbitrarily
496  intPt[0] = zmGetOrInterpolateCopy(q1, p1, p2);
497  intPt[1] = zmGetOrInterpolateCopy(p2, q1, q2);
498 
499  return (q1 == p2) && !q2inP && !p1inQ ? POINT_INTERSECTION : COLLINEAR_INTERSECTION;
500  }
501  if(q2inP && p1inQ) {
502  // if pts are equal Z is chosen arbitrarily
503  intPt[0] = zmGetOrInterpolateCopy(q2, p1, p2);
504  intPt[1] = zmGetOrInterpolateCopy(p1, q1, q2);
505 
506  return (q2 == p1) && !q1inP && !p2inQ ? POINT_INTERSECTION : COLLINEAR_INTERSECTION;
507  }
508  if(q2inP && p2inQ) {
509  // if pts are equal Z is chosen arbitrarily
510  intPt[0] = zmGetOrInterpolateCopy(q2, p1, p2);
511  intPt[1] = zmGetOrInterpolateCopy(p2, q1, q2);
512  return (q2 == p2) && !q1inP && !p1inQ ? POINT_INTERSECTION : COLLINEAR_INTERSECTION;
513  }
514  return NO_INTERSECTION;
515  }
516 
526  template<typename C1, typename C2>
527  geom::CoordinateXYZM intersection (const C1& p1, const C1& p2, const C2& q1, const C2& q2) const {
528  auto intPtOut = intersectionSafe(p1, p2, q1, q2);
529 
530  /*
531  * Due to rounding it can happen that the computed intersection is
532  * outside the envelopes of the input segments. Clearly this
533  * is inconsistent.
534  * This code checks this condition and forces a more reasonable answer
535  *
536  * MD - May 4 2005 - This is still a problem. Here is a failure case:
537  *
538  * LINESTRING (2089426.5233462777 1180182.3877339689,
539  * 2085646.6891757075 1195618.7333999649)
540  * LINESTRING (1889281.8148903656 1997547.0560044837,
541  * 2259977.3672235999 483675.17050843034)
542  * int point = (2097408.2633752143,1144595.8008114607)
543  */
544 
545  if(! isInSegmentEnvelopes(intPtOut)) {
546  //intPt = CentralEndpointIntersector::getIntersection(p1, p2, q1, q2);
547  intPtOut = nearestEndpoint(p1, p2, q1, q2);
548  }
549 
550  if(precisionModel != nullptr) {
551  precisionModel->makePrecise(intPtOut);
552  }
553 
554  return intPtOut;
555  }
556 
567  bool isInSegmentEnvelopes(const geom::CoordinateXY& pt) const
568  {
569  geom::Envelope env0(*inputLines[0][0], *inputLines[0][1]);
570  geom::Envelope env1(*inputLines[1][0], *inputLines[1][1]);
571  return env0.contains(pt) && env1.contains(pt);
572  };
573 
586  template<typename C1, typename C2>
587  geom::CoordinateXYZM intersectionSafe(const C1& p1, const C1& p2,
588  const C2& q1, const C2& q2) const
589  {
590  geom::CoordinateXYZM ptInt(Intersection::intersection(p1, p2, q1, q2));
591  if (ptInt.isNull()) {
592  const geom::CoordinateXY& nearest = nearestEndpoint(p1, p2, q1, q2);
593 #if __cplusplus >= 201703L
594  if constexpr (std::is_same<C1, C2>::value) {
595 #else
596  if (std::is_same<C1, C2>::value) {
597 #endif
598  ptInt = static_cast<const C1&>(nearest);
599  } else {
600  if (&nearest == static_cast<const geom::CoordinateXY*>(&p1) || &nearest == static_cast<const geom::CoordinateXY*>(&p2)) {
601  ptInt = static_cast<const C1&>(nearest);
602  } else {
603  ptInt = static_cast<const C2&>(nearest);
604  }
605  }
606  }
607  return ptInt;
608  }
609 
629  static const geom::CoordinateXY& nearestEndpoint(const geom::CoordinateXY& p1,
630  const geom::CoordinateXY& p2,
631  const geom::CoordinateXY& q1,
632  const geom::CoordinateXY& q2);
633 
634 
635  template<typename C1, typename C2>
636  static geom::CoordinateXYZM zmGetOrInterpolateCopy(
637  const C1& p,
638  const C2& p1,
639  const C2& p2)
640  {
641  geom::CoordinateXYZM pCopy(p);
642  pCopy.z = Interpolate::zGetOrInterpolate(p, p1, p2);
643  pCopy.m = Interpolate::mGetOrInterpolate(p, p1, p2);
644  return pCopy;
645  }
646 
647 };
648 
649 
650 } // namespace geos::algorithm
651 } // namespace geos
652 
653 
654 
655 
656 
657 
658 
659 
660 
661 
662 
663 
664 
665 
666 
667 
bool isInteriorIntersection()
Tests whether either intersection point is an interior point of one of the input segments.
Definition: LineIntersector.h:96
size_t getIntersectionNum() const
Definition: LineIntersector.h:196
Coordinate is the lightweight class used to store coordinates.
Definition: Coordinate.h:217
const geom::CoordinateXY * getEndpoint(std::size_t segmentIndex, std::size_t ptIndex) const
Definition: LineIntersector.h:186
Specifies the precision model of the Coordinate in a Geometry.
Definition: PrecisionModel.h:88
intersection_type
Definition: LineIntersector.h:137
bool isIntersection(const geom::Coordinate &pt) const
Test whether a point is a intersection point of two line segments.
Definition: LineIntersector.h:230
A LineIntersector is an algorithm that can both test whether two line segments intersect and compute ...
Definition: LineIntersector.h:53
static geom::CoordinateXY intersection(const geom::CoordinateXY &p1, const geom::CoordinateXY &p2, const geom::CoordinateXY &q1, const geom::CoordinateXY &q2)
Computes the intersection point of two lines. If the lines are parallel or collinear this case is det...
bool isInteriorIntersection(std::size_t inputLineIndex)
Tests whether either intersection point is an interior point of the specified input segment...
Definition: LineIntersector.h:114
Basic namespace for all GEOS functionalities.
Definition: Angle.h:25
bool isProper() const
Tests whether an intersection is proper.
Definition: LineIntersector.h:255
bool hasIntersection() const
Definition: LineIntersector.h:172
double makePrecise(double val) const
Rounds a numeric value to the PrecisionModel grid.
static bool intersects(const CoordinateXY &p1, const CoordinateXY &p2, const CoordinateXY &q)
Test the point q to see whether it intersects the Envelope defined by p1-p2.
The internal representation of a list of coordinates inside a Geometry.
Definition: CoordinateSequence.h:56
void setPrecisionModel(const geom::PrecisionModel *newPM)
Definition: LineIntersector.h:132
void computeIntersection(const C1 &p1, const C1 &p2, const C2 &p3, const C2 &p4)
Computes the intersection of the lines p1-p2 and p3-p4.
Definition: LineIntersector.h:150
static int index(const geom::CoordinateXY &p1, const geom::CoordinateXY &p2, const geom::CoordinateXY &q)
Returns the orientation index of the direction of the point q relative to a directed infinite line sp...
const geom::CoordinateXYZM & getIntersection(std::size_t intIndex) const
Definition: LineIntersector.h:209