DYT/Tool/OpenSceneGraph-3.6.5/include/geos/algorithm/LineIntersector.h

668 lines
22 KiB
C
Raw Permalink Normal View History

2024-12-24 23:49:36 +00:00
/**********************************************************************
*
* GEOS - Geometry Engine Open Source
* http://geos.osgeo.org
*
* Copyright (C) 2005-2006 Refractions Research Inc.
* Copyright (C) 2001-2002 Vivid Solutions Inc.
*
* This is free software; you can redistribute and/or modify it under
* the terms of the GNU Lesser General Public Licence as published
* by the Free Software Foundation.
* See the COPYING file for more information.
*
**********************************************************************
*
* Last port: algorithm/RobustLineIntersector.java r785 (JTS-1.13+)
*
**********************************************************************/
#pragma once
#include <geos/export.h>
#include <geos/algorithm/Intersection.h>
#include <geos/algorithm/Interpolate.h>
#include <geos/algorithm/Orientation.h>
#include <geos/geom/Coordinate.h>
#include <geos/geom/Envelope.h>
#include <geos/geom/PrecisionModel.h>
#include <string>
// Forward declarations
namespace geos {
namespace geom {
class PrecisionModel;
}
}
namespace geos {
namespace algorithm { // geos::algorithm
/** \brief
* A LineIntersector is an algorithm that can both test whether
* two line segments intersect and compute the intersection point
* if they do.
*
* The intersection point may be computed in a precise or non-precise manner.
* Computing it precisely involves rounding it to an integer. (This assumes
* that the input coordinates have been made precise by scaling them to
* an integer grid.)
*
*/
class GEOS_DLL LineIntersector {
public:
/// Computes the "edge distance" of an intersection point p in an edge.
///
/// The edge distance is a metric of the point along the edge.
/// The metric used is a robust and easy to compute metric function.
/// It is <b>not</b> equivalent to the usual Euclidean metric.
/// It relies on the fact that either the x or the y ordinates of the
/// points in the edge are unique, depending on whether the edge is longer in
/// the horizontal or vertical direction.
///
/// NOTE: This function may produce incorrect distances
/// for inputs where p is not precisely on p1-p2
/// (E.g. p = (139,9) p1 = (139,10), p2 = (280,1) produces distanct
/// 0.0, which is incorrect.
///
/// My hypothesis is that the function is safe to use for points which are the
/// result of <b>rounding</b> points which lie on the line,
/// but not safe to use for <b>truncated</b> points.
///
static double computeEdgeDistance(const geom::CoordinateXY& p, const geom::CoordinateXY& p0, const geom::CoordinateXY& p1);
static double nonRobustComputeEdgeDistance(const geom::Coordinate& p, const geom::Coordinate& p1,
const geom::Coordinate& p2);
explicit LineIntersector(const geom::PrecisionModel* initialPrecisionModel = nullptr)
:
precisionModel(initialPrecisionModel),
result(0),
inputLines(),
isProperVar(false)
{}
~LineIntersector() = default;
/** \brief
* Tests whether either intersection point is an interior point of
* one of the input segments.
*
* @return <code>true</code> if either intersection point is in
* the interior of one of the input segments
*/
bool isInteriorIntersection()
{
if(isInteriorIntersection(0)) {
return true;
}
if(isInteriorIntersection(1)) {
return true;
}
return false;
};
/** \brief
* Tests whether either intersection point is an interior point
* of the specified input segment.
*
* @return <code>true</code> if either intersection point is in
* the interior of the input segment
*/
bool isInteriorIntersection(std::size_t inputLineIndex)
{
for(std::size_t i = 0; i < result; ++i) {
if(!(intPt[i].equals2D(*inputLines[inputLineIndex][0])
|| intPt[i].equals2D(*inputLines[inputLineIndex][1]))) {
return true;
}
}
return false;
};
/// Force computed intersection to be rounded to a given precision model.
///
/// No getter is provided, because the precision model is not required
/// to be specified.
/// @param newPM the PrecisionModel to use for rounding
///
void
setPrecisionModel(const geom::PrecisionModel* newPM)
{
precisionModel = newPM;
}
enum intersection_type : uint8_t {
/// Indicates that line segments do not intersect
NO_INTERSECTION = 0,
/// Indicates that line segments intersect in a single point
POINT_INTERSECTION = 1,
/// Indicates that line segments intersect in a line segment
COLLINEAR_INTERSECTION = 2
};
/// Computes the intersection of the lines p1-p2 and p3-p4
template<typename C1, typename C2>
void computeIntersection(const C1& p1, const C1& p2,
const C2& p3, const C2& p4)
{
inputLines[0][0] = &p1;
inputLines[0][1] = &p2;
inputLines[1][0] = &p3;
inputLines[1][1] = &p4;
result = computeIntersect(p1, p2, p3, p4);
}
/// Compute the intersection between two segments, given a sequence and starting index of each
void computeIntersection(const geom::CoordinateSequence& p, std::size_t p0,
const geom::CoordinateSequence& q, std::size_t q0);
std::string toString() const;
/**
* Tests whether the input geometries intersect.
*
* @return true if the input geometries intersect
*/
bool
hasIntersection() const
{
return result != NO_INTERSECTION;
}
/**
* Gets an endpoint of an input segment.
*
* @param segmentIndex the index of the input segment (0 or 1)
* @param ptIndex the index of the endpoint (0 or 1)
* @return the specified endpoint
*/
const geom::CoordinateXY*
getEndpoint(std::size_t segmentIndex, std::size_t ptIndex) const
{
return inputLines[segmentIndex][ptIndex];
}
/// Returns the number of intersection points found.
///
/// This will be either 0, 1 or 2.
///
size_t
getIntersectionNum() const
{
return result;
}
/// Returns the intIndex'th intersection point
///
/// @param intIndex is 0 or 1
///
/// @return the intIndex'th intersection point
///
const geom::CoordinateXYZM&
getIntersection(std::size_t intIndex) const
{
return intPt[intIndex];
}
/// Returns false if both numbers are zero.
///
/// @return true if both numbers are positive or if both numbers are negative.
///
static bool isSameSignAndNonZero(double a, double b);
/** \brief
* Test whether a point is a intersection point of two line segments.
*
* Note that if the intersection is a line segment, this method only tests for
* equality with the endpoints of the intersection segment.
* It does <b>not</b> return true if
* the input point is internal to the intersection segment.
*
* @return true if the input point is one of the intersection points.
*/
bool isIntersection(const geom::Coordinate& pt) const
{
for(std::size_t i = 0; i < result; ++i) {
if(intPt[i].equals2D(pt)) {
return true;
}
}
return false;
};
/** \brief
* Tests whether an intersection is proper.
*
* The intersection between two line segments is considered proper if
* they intersect in a single point in the interior of both segments
* (e.g. the intersection is a single point and is not equal to any of the
* endpoints).
*
* The intersection between a point and a line segment is considered proper
* if the point lies in the interior of the segment (e.g. is not equal to
* either of the endpoints).
*
* @return true if the intersection is proper
*/
bool
isProper() const
{
return hasIntersection() && isProperVar;
}
/** \brief
* Computes the intIndex'th intersection point in the direction of
* a specified input line segment
*
* @param segmentIndex is 0 or 1
* @param intIndex is 0 or 1
*
* @return the intIndex'th intersection point in the direction of the
* specified input line segment
*/
const geom::Coordinate& getIntersectionAlongSegment(std::size_t segmentIndex, std::size_t intIndex);
/** \brief
* Computes the index of the intIndex'th intersection point in the direction of
* a specified input line segment
*
* @param segmentIndex is 0 or 1
* @param intIndex is 0 or 1
*
* @return the index of the intersection point along the segment (0 or 1)
*/
std::size_t getIndexAlongSegment(std::size_t segmentIndex, std::size_t intIndex);
/** \brief
* Computes the "edge distance" of an intersection point along the specified
* input line segment.
*
* @param geomIndex is 0 or 1
* @param intIndex is 0 or 1
*
* @return the edge distance of the intersection point
*/
double getEdgeDistance(std::size_t geomIndex, std::size_t intIndex) const;
private:
/**
* If makePrecise is true, computed intersection coordinates
* will be made precise using Coordinate#makePrecise
*/
const geom::PrecisionModel* precisionModel;
std::size_t result;
const geom::CoordinateXY* inputLines[2][2];
/**
* We store real Coordinates here because
* we must compute the Z of intersection point.
*/
geom::CoordinateXYZM intPt[2];
/**
* The indexes of the endpoints of the intersection lines, in order along
* the corresponding line
*/
std::size_t intLineIndex[2][2];
bool isProperVar;
//Coordinate &pa;
//Coordinate &pb;
bool
isCollinear() const
{
return result == COLLINEAR_INTERSECTION;
}
template<typename C1, typename C2>
uint8_t computeIntersect(const C1& p1, const C1& p2, const C2& q1, const C2& q2)
{
isProperVar = false;
// first try a fast test to see if the envelopes of the lines intersect
if(!geom::Envelope::intersects(p1, p2, q1, q2)) {
return NO_INTERSECTION;
}
// for each endpoint, compute which side of the other segment it lies
// if both endpoints lie on the same side of the other segment,
// the segments do not intersect
int Pq1 = Orientation::index(p1, p2, q1);
int Pq2 = Orientation::index(p1, p2, q2);
if((Pq1 > 0 && Pq2 > 0) || (Pq1 < 0 && Pq2 < 0)) {
return NO_INTERSECTION;
}
int Qp1 = Orientation::index(q1, q2, p1);
int Qp2 = Orientation::index(q1, q2, p2);
if((Qp1 > 0 && Qp2 > 0) || (Qp1 < 0 && Qp2 < 0)) {
return NO_INTERSECTION;
}
/**
* Intersection is collinear if each endpoint lies on the other line.
*/
bool collinear = Pq1 == 0 && Pq2 == 0 && Qp1 == 0 && Qp2 == 0;
if(collinear) {
return computeCollinearIntersection(p1, p2, q1, q2);
}
/*
* At this point we know that there is a single intersection point
* (since the lines are not collinear).
*/
/*
* Check if the intersection is an endpoint.
* If it is, copy the endpoint as
* the intersection point. Copying the point rather than
* computing it ensures the point has the exact value,
* which is important for robustness. It is sufficient to
* simply check for an endpoint which is on the other line,
* since at this point we know that the inputLines must
* intersect.
*/
geom::CoordinateXYZM p;
double z = DoubleNotANumber;
double m = DoubleNotANumber;
if(Pq1 == 0 || Pq2 == 0 || Qp1 == 0 || Qp2 == 0) {
isProperVar = false;
/* Check for two equal endpoints.
* This is done explicitly rather than by the orientation tests
* below in order to improve robustness.
*
* (A example where the orientation tests fail
* to be consistent is:
*
* LINESTRING ( 19.850257749638203 46.29709338043669,
* 20.31970698357233 46.76654261437082 )
* and
* LINESTRING ( -48.51001596420236 -22.063180333403878,
* 19.850257749638203 46.29709338043669 )
*
* which used to produce the INCORRECT result:
* (20.31970698357233, 46.76654261437082, NaN)
*/
if (p1.equals2D(q1)) {
p = p1;
z = Interpolate::zGet(p1, q1);
m = Interpolate::mGet(p1, q1);
}
else if (p1.equals2D(q2)) {
p = p1;
z = Interpolate::zGet(p1, q2);
m = Interpolate::mGet(p1, q2);
}
else if (p2.equals2D(q1)) {
p = p2;
z = Interpolate::zGet(p2, q1);
m = Interpolate::mGet(p2, q1);
}
else if (p2.equals2D(q2)) {
p = p2;
z = Interpolate::zGet(p2, q2);
m = Interpolate::mGet(p2, q2);
}
/*
* Now check to see if any endpoint lies on the interior of the other segment.
*/
else if(Pq1 == 0) {
p = q1;
z = Interpolate::zGetOrInterpolate(q1, p1, p2);
m = Interpolate::mGetOrInterpolate(q1, p1, p2);
}
else if(Pq2 == 0) {
p = q2;
z = Interpolate::zGetOrInterpolate(q2, p1, p2);
m = Interpolate::mGetOrInterpolate(q2, p1, p2);
}
else if(Qp1 == 0) {
p = p1;
z = Interpolate::zGetOrInterpolate(p1, q1, q2);
m = Interpolate::mGetOrInterpolate(p1, q1, q2);
}
else if(Qp2 == 0) {
p = p2;
z = Interpolate::zGetOrInterpolate(p2, q1, q2);
m = Interpolate::mGetOrInterpolate(p2, q1, q2);
}
} else {
isProperVar = true;
p = intersection(p1, p2, q1, q2);
z = Interpolate::zInterpolate(p, p1, p2, q1, q2);
m = Interpolate::mInterpolate(p, p1, p2, q1, q2);
}
intPt[0] = geom::CoordinateXYZM(p.x, p.y, z, m);
#if GEOS_DEBUG
std::cerr << " POINT_INTERSECTION; intPt[0]:" << intPt[0].toString() << std::endl;
#endif // GEOS_DEBUG
return POINT_INTERSECTION;
}
bool
isEndPoint() const
{
return hasIntersection() && !isProperVar;
}
void computeIntLineIndex();
void computeIntLineIndex(std::size_t segmentIndex);
template<typename C1, typename C2>
uint8_t computeCollinearIntersection(const C1& p1, const C1& p2, const C2& q1, const C2& q2)
{
bool q1inP = geom::Envelope::intersects(p1, p2, q1);
bool q2inP = geom::Envelope::intersects(p1, p2, q2);
bool p1inQ = geom::Envelope::intersects(q1, q2, p1);
bool p2inQ = geom::Envelope::intersects(q1, q2, p2);
if(q1inP && q2inP) {
intPt[0] = zmGetOrInterpolateCopy(q1, p1, p2);
intPt[1] = zmGetOrInterpolateCopy(q2, p1, p2);
return COLLINEAR_INTERSECTION;
}
if(p1inQ && p2inQ) {
intPt[0] = zmGetOrInterpolateCopy(p1, q1, q2);
intPt[1] = zmGetOrInterpolateCopy(p2, q1, q2);
return COLLINEAR_INTERSECTION;
}
if(q1inP && p1inQ) {
// if pts are equal Z is chosen arbitrarily
intPt[0] = zmGetOrInterpolateCopy(q1, p1, p2);
intPt[1] = zmGetOrInterpolateCopy(p1, q1, q2);
return (q1 == p1) && !q2inP && !p2inQ ? POINT_INTERSECTION : COLLINEAR_INTERSECTION;
}
if(q1inP && p2inQ) {
// if pts are equal Z is chosen arbitrarily
intPt[0] = zmGetOrInterpolateCopy(q1, p1, p2);
intPt[1] = zmGetOrInterpolateCopy(p2, q1, q2);
return (q1 == p2) && !q2inP && !p1inQ ? POINT_INTERSECTION : COLLINEAR_INTERSECTION;
}
if(q2inP && p1inQ) {
// if pts are equal Z is chosen arbitrarily
intPt[0] = zmGetOrInterpolateCopy(q2, p1, p2);
intPt[1] = zmGetOrInterpolateCopy(p1, q1, q2);
return (q2 == p1) && !q1inP && !p2inQ ? POINT_INTERSECTION : COLLINEAR_INTERSECTION;
}
if(q2inP && p2inQ) {
// if pts are equal Z is chosen arbitrarily
intPt[0] = zmGetOrInterpolateCopy(q2, p1, p2);
intPt[1] = zmGetOrInterpolateCopy(p2, q1, q2);
return (q2 == p2) && !q1inP && !p1inQ ? POINT_INTERSECTION : COLLINEAR_INTERSECTION;
}
return NO_INTERSECTION;
}
/** \brief
* This method computes the actual value of the intersection point.
*
* To obtain the maximum precision from the intersection calculation,
* the coordinates are normalized by subtracting the minimum
* ordinate values (in absolute value). This has the effect of
* removing common significant digits from the calculation to
* maintain more bits of precision.
*/
template<typename C1, typename C2>
geom::CoordinateXYZM intersection (const C1& p1, const C1& p2, const C2& q1, const C2& q2) const {
auto intPtOut = intersectionSafe(p1, p2, q1, q2);
/*
* Due to rounding it can happen that the computed intersection is
* outside the envelopes of the input segments. Clearly this
* is inconsistent.
* This code checks this condition and forces a more reasonable answer
*
* MD - May 4 2005 - This is still a problem. Here is a failure case:
*
* LINESTRING (2089426.5233462777 1180182.3877339689,
* 2085646.6891757075 1195618.7333999649)
* LINESTRING (1889281.8148903656 1997547.0560044837,
* 2259977.3672235999 483675.17050843034)
* int point = (2097408.2633752143,1144595.8008114607)
*/
if(! isInSegmentEnvelopes(intPtOut)) {
//intPt = CentralEndpointIntersector::getIntersection(p1, p2, q1, q2);
intPtOut = nearestEndpoint(p1, p2, q1, q2);
}
if(precisionModel != nullptr) {
precisionModel->makePrecise(intPtOut);
}
return intPtOut;
}
/**
* Test whether a point lies in the envelopes of both input segments.
* A correctly computed intersection point should return true
* for this test.
* Since this test is for debugging purposes only, no attempt is
* made to optimize the envelope test.
*
* @return true if the input point lies within both
* input segment envelopes
*/
bool isInSegmentEnvelopes(const geom::CoordinateXY& pt) const
{
geom::Envelope env0(*inputLines[0][0], *inputLines[0][1]);
geom::Envelope env1(*inputLines[1][0], *inputLines[1][1]);
return env0.contains(pt) && env1.contains(pt);
};
/**
* Computes a segment intersection.
* Round-off error can cause the raw computation to fail,
* (usually due to the segments being approximately parallel).
* If this happens, a reasonable approximation is computed instead.
*
* @param p1 a segment endpoint
* @param p2 a segment endpoint
* @param q1 a segment endpoint
* @param q2 a segment endpoint
* @return the computed intersection point is stored there
*/
template<typename C1, typename C2>
geom::CoordinateXYZM intersectionSafe(const C1& p1, const C1& p2,
const C2& q1, const C2& q2) const
{
geom::CoordinateXYZM ptInt(Intersection::intersection(p1, p2, q1, q2));
if (ptInt.isNull()) {
const geom::CoordinateXY& nearest = nearestEndpoint(p1, p2, q1, q2);
#if __cplusplus >= 201703L
if constexpr (std::is_same<C1, C2>::value) {
#else
if (std::is_same<C1, C2>::value) {
#endif
ptInt = static_cast<const C1&>(nearest);
} else {
if (&nearest == static_cast<const geom::CoordinateXY*>(&p1) || &nearest == static_cast<const geom::CoordinateXY*>(&p2)) {
ptInt = static_cast<const C1&>(nearest);
} else {
ptInt = static_cast<const C2&>(nearest);
}
}
}
return ptInt;
}
/**
* Finds the endpoint of the segments P and Q which
* is closest to the other segment.
* This is a reasonable surrogate for the true
* intersection points in ill-conditioned cases
* (e.g. where two segments are nearly coincident,
* or where the endpoint of one segment lies almost on the other segment).
* <p>
* This replaces the older CentralEndpoint heuristic,
* which chose the wrong endpoint in some cases
* where the segments had very distinct slopes
* and one endpoint lay almost on the other segment.
*
* @param p1 an endpoint of segment P
* @param p2 an endpoint of segment P
* @param q1 an endpoint of segment Q
* @param q2 an endpoint of segment Q
* @return the nearest endpoint to the other segment
*/
static const geom::CoordinateXY& nearestEndpoint(const geom::CoordinateXY& p1,
const geom::CoordinateXY& p2,
const geom::CoordinateXY& q1,
const geom::CoordinateXY& q2);
template<typename C1, typename C2>
static geom::CoordinateXYZM zmGetOrInterpolateCopy(
const C1& p,
const C2& p1,
const C2& p2)
{
geom::CoordinateXYZM pCopy(p);
pCopy.z = Interpolate::zGetOrInterpolate(p, p1, p2);
pCopy.m = Interpolate::mGetOrInterpolate(p, p1, p2);
return pCopy;
}
};
} // namespace geos::algorithm
} // namespace geos