/**********************************************************************
 *
 * 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+)
 *
 **********************************************************************/

#include <geos/constants.h>
#include <geos/algorithm/LineIntersector.h>
#include <geos/algorithm/Distance.h>
#include <geos/algorithm/Orientation.h>
#include <geos/algorithm/Intersection.h>
#include <geos/algorithm/NotRepresentableException.h>
#include <geos/geom/Coordinate.h>
#include <geos/geom/PrecisionModel.h>
#include <geos/geom/Envelope.h>

#include <algorithm> // for max()
#include <string>
#include <cmath> // for fabs()
#include <cassert>


#ifndef GEOS_DEBUG
#define GEOS_DEBUG 0
#endif

#ifdef GEOS_DEBUG
#include <iostream>
#endif

#ifndef COMPUTE_Z
#define COMPUTE_Z 1
#endif // COMPUTE_Z

using namespace std;

using namespace geos::geom;

namespace geos {
namespace algorithm { // geos.algorithm

namespace { // anonymous

/**
 * 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
 */
Coordinate
nearestEndpoint(const Coordinate& p1, const Coordinate& p2,
                const Coordinate& q1, const Coordinate& q2)
{
    const Coordinate* nearestPt = &p1;
    double minDist = Distance::pointToSegment(p1, q1, q2);

    double dist = Distance::pointToSegment(p2, q1, q2);
    if(dist < minDist) {
        minDist = dist;
        nearestPt = &p2;
    }
    dist = Distance::pointToSegment(q1, p1, p2);
    if(dist < minDist) {
        minDist = dist;
        nearestPt = &q1;
    }
    dist = Distance::pointToSegment(q2, p1, p2);
    if(dist < minDist) {
        minDist = dist;
        nearestPt = &q2;
    }
    return *nearestPt;
}


} // anonymous namespace

/*public static*/
double
LineIntersector::computeEdgeDistance(const Coordinate& p, const Coordinate& p0, const Coordinate& p1)
{
    double dx = fabs(p1.x - p0.x);
    double dy = fabs(p1.y - p0.y);
    double dist = -1.0;	// sentinel value
    if(p == p0) {
        dist = 0.0;
    }
    else if(p == p1) {
        if(dx > dy) {
            dist = dx;
        }
        else {
            dist = dy;
        }
    }
    else {
        double pdx = fabs(p.x - p0.x);
        double pdy = fabs(p.y - p0.y);
        if(dx > dy) {
            dist = pdx;
        }
        else {
            dist = pdy;
        }
        // <FIX>
        // hack to ensure that non-endpoints always have a non-zero distance
        if(dist == 0.0 && !(p == p0)) {
            dist = std::max(pdx, pdy);
        }
    }
    assert(!(dist == 0.0 && !(p == p0))); // Bad distance calculation
    return dist;
}

/*public*/
void
LineIntersector::computeIntersection(const Coordinate& p1, const Coordinate& p2, const Coordinate& p3,
                                     const Coordinate& p4)
{
    inputLines[0][0] = &p1;
    inputLines[0][1] = &p2;
    inputLines[1][0] = &p3;
    inputLines[1][1] = &p4;
    result = computeIntersect(p1, p2, p3, p4);
    //numIntersects++;
}

/*public*/
string
LineIntersector::toString() const
{
    string str = inputLines[0][0]->toString() + "_"
                 + inputLines[0][1]->toString() + " "
                 + inputLines[1][0]->toString() + "_"
                 + inputLines[1][1]->toString() + " : ";
    if(isEndPoint()) {
        str += " endpoint";
    }
    if(isProperVar) {
        str += " proper";
    }
    if(isCollinear()) {
        str += " collinear";
    }
    return str;
}

/*public static*/
bool
LineIntersector::isSameSignAndNonZero(double a, double b)
{
    if(a == 0 || b == 0) {
        return false;
    }
    return (a < 0 && b < 0) || (a > 0 && b > 0);
}

/*private*/
void
LineIntersector::computeIntLineIndex()
{
    computeIntLineIndex(0);
    computeIntLineIndex(1);
}

/*public*/
bool
LineIntersector::isIntersection(const Coordinate& pt) const
{
    for(size_t i = 0; i < result; ++i) {
        if(intPt[i].equals2D(pt)) {
            return true;
        }
    }
    return false;
}

/*public*/
const Coordinate&
LineIntersector::getIntersectionAlongSegment(int segmentIndex, int intIndex)
{
    // lazily compute int line array
    computeIntLineIndex();
    return intPt[intLineIndex[segmentIndex][intIndex]];
}

/*public*/
int
LineIntersector::getIndexAlongSegment(int segmentIndex, int intIndex)
{
    computeIntLineIndex();
    return intLineIndex[segmentIndex][intIndex];
}

/*private*/
void
LineIntersector::computeIntLineIndex(int segmentIndex)
{
    double dist0 = getEdgeDistance(segmentIndex, 0);
    double dist1 = getEdgeDistance(segmentIndex, 1);
    if(dist0 > dist1) {
        intLineIndex[segmentIndex][0] = 0;
        intLineIndex[segmentIndex][1] = 1;
    }
    else {
        intLineIndex[segmentIndex][0] = 1;
        intLineIndex[segmentIndex][1] = 0;
    }
}

/*public*/
double
LineIntersector::getEdgeDistance(size_t segmentIndex, size_t intIndex) const
{
    double dist = computeEdgeDistance(intPt[intIndex],
                                      *inputLines[segmentIndex][0],
                                      *inputLines[segmentIndex][1]);
    return dist;
}

/*public*/
bool
LineIntersector::isInteriorIntersection()
{
    if(isInteriorIntersection(0)) {
        return true;
    }
    if(isInteriorIntersection(1)) {
        return true;
    }
    return false;
}

/*public*/
bool
LineIntersector::isInteriorIntersection(int inputLineIndex)
{
    for(size_t i = 0; i < result; ++i) {
        if(!(intPt[i].equals2D(*inputLines[inputLineIndex][0])
                || intPt[i].equals2D(*inputLines[inputLineIndex][1]))) {
            return true;
        }
    }
    return false;
}

/*public static*/
double
LineIntersector::interpolateZ(const Coordinate& p,
                              const Coordinate& p1, const Coordinate& p2)
{
#if GEOS_DEBUG
    cerr << "LineIntersector::interpolateZ(" << p.toString() << ", " << p1.toString() << ", " << p2.toString() << ")" <<
         endl;
#endif

    if(std::isnan(p1.z)) {
#if GEOS_DEBUG
        cerr << " p1 do not have a Z" << endl;
#endif
        return p2.z; // might be DoubleNotANumber again
    }

    if(std::isnan(p2.z)) {
#if GEOS_DEBUG
        cerr << " p2 do not have a Z" << endl;
#endif
        return p1.z; // might be DoubleNotANumber again
    }

    if(p == p1) {
#if GEOS_DEBUG
        cerr << " p==p1, returning " << p1.z << endl;
#endif
        return p1.z;
    }
    if(p == p2) {
#if GEOS_DEBUG
        cerr << " p==p2, returning " << p2.z << endl;
#endif
        return p2.z;
    }

    //double zgap = fabs(p2.z - p1.z);
    double zgap = p2.z - p1.z;
    if(! zgap) {
#if GEOS_DEBUG
        cerr << " no zgap, returning " << p2.z << endl;
#endif
        return p2.z;
    }
    double xoff = (p2.x - p1.x);
    double yoff = (p2.y - p1.y);
    double seglen = (xoff * xoff + yoff * yoff);
    xoff = (p.x - p1.x);
    yoff = (p.y - p1.y);
    double pdist = (xoff * xoff + yoff * yoff);
    double fract = sqrt(pdist / seglen);
    double zoff = zgap * fract;
    //double interpolated = p1.z < p2.z ? p1.z+zoff : p1.z-zoff;
    double interpolated = p1.z + zoff;
#if GEOS_DEBUG
    cerr << " zgap:" << zgap << " seglen:" << seglen << " pdist:" << pdist
         << " fract:" << fract << " z:" << interpolated << endl;
#endif
    return interpolated;

}


/*public*/
void
LineIntersector::computeIntersection(const Coordinate& p, const Coordinate& p1, const Coordinate& p2)
{
    isProperVar = false;

    // do between check first, since it is faster than the orientation test
    if(Envelope::intersects(p1, p2, p)) {
        if((Orientation::index(p1, p2, p) == 0) &&
                (Orientation::index(p2, p1, p) == 0)) {
            isProperVar = true;
            if((p == p1) || (p == p2)) { // 2d only test
                isProperVar = false;
            }
#if COMPUTE_Z
            intPt[0] = p;
#if GEOS_DEBUG
            cerr << "RobustIntersector::computeIntersection(Coordinate,Coordinate,Coordinate) calling interpolateZ" << endl;
#endif
            double z = interpolateZ(p, p1, p2);
            if(!std::isnan(z)) {
                if(std::isnan(intPt[0].z)) {
                    intPt[0].z = z;
                }
                else {
                    intPt[0].z = (intPt[0].z + z) / 2;
                }
            }
#endif // COMPUTE_Z
            result = POINT_INTERSECTION;
            return;
        }
    }
    result = NO_INTERSECTION;
}

/* public static */
bool
LineIntersector::hasIntersection(const Coordinate& p, const Coordinate& p1, const Coordinate& p2)
{
    if(Envelope::intersects(p1, p2, p)) {
        if((Orientation::index(p1, p2, p) == 0) &&
                (Orientation::index(p2, p1, p) == 0)) {
            return true;
        }
    }
    return false;
}

/*private*/
int
LineIntersector::computeIntersect(const Coordinate& p1, const Coordinate& p2,
                                  const Coordinate& q1, const Coordinate& q2)
{
#if GEOS_DEBUG
    cerr << "LineIntersector::computeIntersect called" << endl;
    cerr << " p1:" << p1.toString() << " p2:" << p2.toString() << " q1:" << q1.toString() << " q2:" << q2.toString() <<
         endl;
#endif // GEOS_DEBUG

    isProperVar = false;

    // first try a fast test to see if the envelopes of the lines intersect
    if(!Envelope::intersects(p1, p2, q1, q2)) {
#if GEOS_DEBUG
        cerr << " NO_INTERSECTION" << endl;
#endif
        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)) {
#if GEOS_DEBUG
        cerr << " NO_INTERSECTION" << endl;
#endif
        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)) {
#if GEOS_DEBUG
        cerr << " NO_INTERSECTION" << endl;
#endif
        return NO_INTERSECTION;
    }

    bool collinear = Pq1 == 0 && Pq2 == 0 && Qp1 == 0 && Qp2 == 0;
    if(collinear) {
#if GEOS_DEBUG
        cerr << " computingCollinearIntersection" << endl;
#endif
        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.
     */
    if(Pq1 == 0 || Pq2 == 0 || Qp1 == 0 || Qp2 == 0) {
#if COMPUTE_Z
        int hits = 0;
        double z = 0.0;
#endif
        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 result:
         * (20.31970698357233, 46.76654261437082, NaN)
         */

        if(p1.equals2D(q1) || p1.equals2D(q2)) {
            intPt[0] = p1;
#if COMPUTE_Z
            if(!std::isnan(p1.z)) {
                z += p1.z;
                hits++;
            }
#endif
        }
        else if(p2.equals2D(q1) || p2.equals2D(q2)) {
            intPt[0] = p2;
#if COMPUTE_Z
            if(!std::isnan(p2.z)) {
                z += p2.z;
                hits++;
            }
#endif
        }

        /**
         * Now check to see if any endpoint lies on the interior of the other segment.
         */
        else if(Pq1 == 0) {
            intPt[0] = q1;
#if COMPUTE_Z
            if(!std::isnan(q1.z)) {
                z += q1.z;
                hits++;
            }
#endif
        }
        else if(Pq2 == 0) {
            intPt[0] = q2;
#if COMPUTE_Z
            if(!std::isnan(q2.z)) {
                z += q2.z;
                hits++;
            }
#endif
        }
        else if(Qp1 == 0) {
            intPt[0] = p1;
#if COMPUTE_Z
            if(!std::isnan(p1.z)) {
                z += p1.z;
                hits++;
            }
#endif
        }
        else if(Qp2 == 0) {
            intPt[0] = p2;
#if COMPUTE_Z
            if(!std::isnan(p2.z)) {
                z += p2.z;
                hits++;
            }
#endif
        }
#if COMPUTE_Z
#if GEOS_DEBUG
        cerr << "LineIntersector::computeIntersect: z:" << z << " hits:" << hits << endl;
#endif // GEOS_DEBUG
        if(hits) {
            intPt[0].z = z / hits;
        }
#endif // COMPUTE_Z
    }
    else {
        isProperVar = true;
        intPt[0] = intersection(p1, p2, q1, q2);
    }
#if GEOS_DEBUG
    cerr << " POINT_INTERSECTION; intPt[0]:" << intPt[0].toString() << endl;
#endif // GEOS_DEBUG
    return POINT_INTERSECTION;
}

/*private*/
int
LineIntersector::computeCollinearIntersection(const Coordinate& p1, const Coordinate& p2,
                                              const Coordinate& q1, const Coordinate& q2)
{
#if COMPUTE_Z
    double ztot;
    int hits;
    double p2z;
    double p1z;
    double q1z;
    double q2z;
#endif // COMPUTE_Z

#if GEOS_DEBUG
    cerr << "LineIntersector::computeCollinearIntersection called" << endl;
    cerr << " p1:" << p1.toString() << " p2:" << p2.toString() << " q1:" << q1.toString() << " q2:" << q2.toString() <<
         endl;
#endif // GEOS_DEBUG

    bool p1q1p2 = Envelope::intersects(p1, p2, q1);
    bool p1q2p2 = Envelope::intersects(p1, p2, q2);
    bool q1p1q2 = Envelope::intersects(q1, q2, p1);
    bool q1p2q2 = Envelope::intersects(q1, q2, p2);

    if(p1q1p2 && p1q2p2) {
#if GEOS_DEBUG
        cerr << " p1q1p2 && p1q2p2" << endl;
#endif
        intPt[0] = q1;
#if COMPUTE_Z
        ztot = 0;
        hits = 0;
        q1z = interpolateZ(q1, p1, p2);
        if(!std::isnan(q1z)) {
            ztot += q1z;
            hits++;
        }
        if(!std::isnan(q1.z)) {
            ztot += q1.z;
            hits++;
        }
        if(hits) {
            intPt[0].z = ztot / hits;
        }
#endif
        intPt[1] = q2;
#if COMPUTE_Z
        ztot = 0;
        hits = 0;
        q2z = interpolateZ(q2, p1, p2);
        if(!std::isnan(q2z)) {
            ztot += q2z;
            hits++;
        }
        if(!std::isnan(q2.z)) {
            ztot += q2.z;
            hits++;
        }
        if(hits) {
            intPt[1].z = ztot / hits;
        }
#endif
#if GEOS_DEBUG
        cerr << " intPt[0]: " << intPt[0].toString() << endl;
        cerr << " intPt[1]: " << intPt[1].toString() << endl;
#endif
        return COLLINEAR_INTERSECTION;
    }
    if(q1p1q2 && q1p2q2) {
#if GEOS_DEBUG
        cerr << " q1p1q2 && q1p2q2" << endl;
#endif
        intPt[0] = p1;
#if COMPUTE_Z
        ztot = 0;
        hits = 0;
        p1z = interpolateZ(p1, q1, q2);
        if(!std::isnan(p1z)) {
            ztot += p1z;
            hits++;
        }
        if(!std::isnan(p1.z)) {
            ztot += p1.z;
            hits++;
        }
        if(hits) {
            intPt[0].z = ztot / hits;
        }
#endif
        intPt[1] = p2;
#if COMPUTE_Z
        ztot = 0;
        hits = 0;
        p2z = interpolateZ(p2, q1, q2);
        if(!std::isnan(p2z)) {
            ztot += p2z;
            hits++;
        }
        if(!std::isnan(p2.z)) {
            ztot += p2.z;
            hits++;
        }
        if(hits) {
            intPt[1].z = ztot / hits;
        }
#endif
        return COLLINEAR_INTERSECTION;
    }
    if(p1q1p2 && q1p1q2) {
#if GEOS_DEBUG
        cerr << " p1q1p2 && q1p1q2" << endl;
#endif
        intPt[0] = q1;
#if COMPUTE_Z
        ztot = 0;
        hits = 0;
        q1z = interpolateZ(q1, p1, p2);
        if(!std::isnan(q1z)) {
            ztot += q1z;
            hits++;
        }
        if(!std::isnan(q1.z)) {
            ztot += q1.z;
            hits++;
        }
        if(hits) {
            intPt[0].z = ztot / hits;
        }
#endif
        intPt[1] = p1;
#if COMPUTE_Z
        ztot = 0;
        hits = 0;
        p1z = interpolateZ(p1, q1, q2);
        if(!std::isnan(p1z)) {
            ztot += p1z;
            hits++;
        }
        if(!std::isnan(p1.z)) {
            ztot += p1.z;
            hits++;
        }
        if(hits) {
            intPt[1].z = ztot / hits;
        }
#endif
#if GEOS_DEBUG
        cerr << " intPt[0]: " << intPt[0].toString() << endl;
        cerr << " intPt[1]: " << intPt[1].toString() << endl;
#endif
        return (q1 == p1) && !p1q2p2 && !q1p2q2 ? POINT_INTERSECTION : COLLINEAR_INTERSECTION;
    }
    if(p1q1p2 && q1p2q2) {
#if GEOS_DEBUG
        cerr << " p1q1p2 && q1p2q2" << endl;
#endif
        intPt[0] = q1;
#if COMPUTE_Z
        ztot = 0;
        hits = 0;
        q1z = interpolateZ(q1, p1, p2);
        if(!std::isnan(q1z)) {
            ztot += q1z;
            hits++;
        }
        if(!std::isnan(q1.z)) {
            ztot += q1.z;
            hits++;
        }
        if(hits) {
            intPt[0].z = ztot / hits;
        }
#endif
        intPt[1] = p2;
#if COMPUTE_Z
        ztot = 0;
        hits = 0;
        p2z = interpolateZ(p2, q1, q2);
        if(!std::isnan(p2z)) {
            ztot += p2z;
            hits++;
        }
        if(!std::isnan(p2.z)) {
            ztot += p2.z;
            hits++;
        }
        if(hits) {
            intPt[1].z = ztot / hits;
        }
#endif
#if GEOS_DEBUG
        cerr << " intPt[0]: " << intPt[0].toString() << endl;
        cerr << " intPt[1]: " << intPt[1].toString() << endl;
#endif
        return (q1 == p2) && !p1q2p2 && !q1p1q2 ? POINT_INTERSECTION : COLLINEAR_INTERSECTION;
    }
    if(p1q2p2 && q1p1q2) {
#if GEOS_DEBUG
        cerr << " p1q2p2 && q1p1q2" << endl;
#endif
        intPt[0] = q2;
#if COMPUTE_Z
        ztot = 0;
        hits = 0;
        q2z = interpolateZ(q2, p1, p2);
        if(!std::isnan(q2z)) {
            ztot += q2z;
            hits++;
        }
        if(!std::isnan(q2.z)) {
            ztot += q2.z;
            hits++;
        }
        if(hits) {
            intPt[0].z = ztot / hits;
        }
#endif
        intPt[1] = p1;
#if COMPUTE_Z
        ztot = 0;
        hits = 0;
        p1z = interpolateZ(p1, q1, q2);
        if(!std::isnan(p1z)) {
            ztot += p1z;
            hits++;
        }
        if(!std::isnan(p1.z)) {
            ztot += p1.z;
            hits++;
        }
        if(hits) {
            intPt[1].z = ztot / hits;
        }
#endif
#if GEOS_DEBUG
        cerr << " intPt[0]: " << intPt[0].toString() << endl;
        cerr << " intPt[1]: " << intPt[1].toString() << endl;
#endif
        return (q2 == p1) && !p1q1p2 && !q1p2q2 ? POINT_INTERSECTION : COLLINEAR_INTERSECTION;
    }
    if(p1q2p2 && q1p2q2) {
#if GEOS_DEBUG
        cerr << " p1q2p2 && q1p2q2" << endl;
#endif
        intPt[0] = q2;
#if COMPUTE_Z
        ztot = 0;
        hits = 0;
        q2z = interpolateZ(q2, p1, p2);
        if(!std::isnan(q2z)) {
            ztot += q2z;
            hits++;
        }
        if(!std::isnan(q2.z)) {
            ztot += q2.z;
            hits++;
        }
        if(hits) {
            intPt[0].z = ztot / hits;
        }
#endif
        intPt[1] = p2;
#if COMPUTE_Z
        ztot = 0;
        hits = 0;
        p2z = interpolateZ(p2, q1, q2);
        if(!std::isnan(p2z)) {
            ztot += p2z;
            hits++;
        }
        if(!std::isnan(p2.z)) {
            ztot += p2.z;
            hits++;
        }
        if(hits) {
            intPt[1].z = ztot / hits;
        }
#endif
#if GEOS_DEBUG
        cerr << " intPt[0]: " << intPt[0].toString() << endl;
        cerr << " intPt[1]: " << intPt[1].toString() << endl;
#endif
        return (q2 == p2) && !p1q1p2 && !q1p1q2 ? POINT_INTERSECTION : COLLINEAR_INTERSECTION;
    }
    return NO_INTERSECTION;
}

/*private*/
Coordinate
LineIntersector::intersection(const Coordinate& p1, const Coordinate& p2,
                              const Coordinate& q1, const Coordinate& q2) const
{

    Coordinate 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 GEOS_DEBUG
        cerr << "Intersection outside segment envelopes, snapped to "
             << intPt.toString() << endl;
#endif
    }

    if(precisionModel != nullptr) {
        precisionModel->makePrecise(intPtOut);
    }


#if COMPUTE_Z
    double ztot = 0;
    double zvals = 0;
    double zp = interpolateZ(intPtOut, p1, p2);
    double zq = interpolateZ(intPtOut, q1, q2);
    if(!std::isnan(zp)) {
        ztot += zp;
        zvals++;
    }
    if(!std::isnan(zq)) {
        ztot += zq;
        zvals++;
    }
    if(zvals) {
        intPtOut.z = ztot / zvals;
    }
#endif // COMPUTE_Z
    return intPtOut;
}

/*private*/
bool
LineIntersector::isInSegmentEnvelopes(const Coordinate& pt) const
{
    Envelope env0(*inputLines[0][0], *inputLines[0][1]);
    Envelope env1(*inputLines[1][0], *inputLines[1][1]);
    return env0.contains(pt) && env1.contains(pt);
}

/*private*/
Coordinate
LineIntersector::intersectionSafe(const Coordinate& p1, const Coordinate& p2,
                                  const Coordinate& q1, const Coordinate& q2) const
{
    Coordinate ptInt = Intersection::intersection(p1, p2, q1, q2);
    if (ptInt.isNull()) {
        ptInt = nearestEndpoint(p1, p2, q1, q2);
    }
    return ptInt;
}


} // namespace geos.algorithm
} // namespace geos
