[geos-commits] [SCM] GEOS branch main-relate-ng updated. 3c040699dca4eb10c711c09f279cc93235528a4e

git at osgeo.org git at osgeo.org
Tue Jul 2 15:31:23 PDT 2024


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "GEOS".

The branch, main-relate-ng has been updated
       via  3c040699dca4eb10c711c09f279cc93235528a4e (commit)
      from  d84cf1aec56c9948f4f22dfbbbd331f61c32b132 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 3c040699dca4eb10c711c09f279cc93235528a4e
Author: Paul Ramsey <pramsey at cleverelephant.ca>
Date:   Tue Jul 2 15:30:44 2024 -0700

    Port of NodeSection.java NodeSections.java RelateEdge.java and RelateNode.java (#1119)
    
    Clean build, tests showing red.

diff --git a/include/geos/algorithm/PolygonNodeTopology.h b/include/geos/algorithm/PolygonNodeTopology.h
index a1987e3f5..7b6508f1d 100644
--- a/include/geos/algorithm/PolygonNodeTopology.h
+++ b/include/geos/algorithm/PolygonNodeTopology.h
@@ -76,6 +76,21 @@ public:
     static bool isInteriorSegment(const CoordinateXY* nodePt,
         const CoordinateXY* a0, const CoordinateXY* a1, const CoordinateXY* b);
 
+    /**
+    * Compares the angles of two vectors
+    * relative to the positive X-axis at their origin.
+    * Angles increase CCW from the X-axis.
+    *
+    * @param origin the origin of the vectors
+    * @param p the endpoint of the vector P
+    * @param q the endpoint of the vector Q
+    * @return a negative integer, zero, or a positive integer as this vector P has angle less than, equal to, or greater than vector Q
+    */
+    static int compareAngle(
+        const CoordinateXY* origin,
+        const CoordinateXY* p,
+        const CoordinateXY* q);
+
 
 private:
 
diff --git a/include/geos/constants.h b/include/geos/constants.h
index 7d58ca3d5..47c8b3ef4 100644
--- a/include/geos/constants.h
+++ b/include/geos/constants.h
@@ -42,6 +42,7 @@ constexpr double DoubleNegInfinity = (-(std::numeric_limits<double>::infinity)()
 constexpr double DoubleEpsilon = std::numeric_limits<double>::epsilon();
 
 constexpr std::size_t NO_COORD_INDEX = std::numeric_limits<std::size_t>::max();
+constexpr std::size_t INDEX_UNKNOWN = std::numeric_limits<std::size_t>::max();
 
 } // namespace geos
 
diff --git a/include/geos/geom/Coordinate.h b/include/geos/geom/Coordinate.h
index 50763083c..0d8bced0f 100644
--- a/include/geos/geom/Coordinate.h
+++ b/include/geos/geom/Coordinate.h
@@ -22,6 +22,7 @@
 #include <vector> // for typedefs
 #include <string>
 #include <limits>
+#include <map>
 
 #ifdef _MSC_VER
 #pragma warning(push)
@@ -222,6 +223,7 @@ private:
 public:
     /// A set of const Coordinate pointers
     typedef std::set<const Coordinate*, CoordinateLessThan> ConstSet;
+    typedef std::set<const CoordinateXY*, CoordinateLessThan> ConstXYSet;
 
     /// A vector of const Coordinate pointers
     typedef std::vector<const Coordinate*> ConstVect;
@@ -232,6 +234,9 @@ public:
     /// A vector of Coordinate objects (real object, not pointers)
     typedef std::vector<Coordinate> Vect;
 
+    /// A map of const Coordinate pointers to integers
+    typedef std::map<const CoordinateXY*, int, CoordinateLessThan> ConstIntMap;
+
     /// z-coordinate
     double z;
 
diff --git a/include/geos/io/WKTWriter.h b/include/geos/io/WKTWriter.h
index 97007dc17..105b63739 100644
--- a/include/geos/io/WKTWriter.h
+++ b/include/geos/io/WKTWriter.h
@@ -121,7 +121,7 @@ public:
      *
      * @return the WKT
      */
-    static std::string toLineString(const geom::Coordinate& p0, const geom::Coordinate& p1);
+    static std::string toLineString(const geom::CoordinateXY& p0, const geom::CoordinateXY& p1);
 
     /**
      * Generates the WKT for a <code>Point</code>.
diff --git a/include/geos/operation/relateng/AdjacentEdgeLocator.h b/include/geos/operation/relateng/AdjacentEdgeLocator.h
new file mode 100644
index 000000000..fe72d5df9
--- /dev/null
+++ b/include/geos/operation/relateng/AdjacentEdgeLocator.h
@@ -0,0 +1,124 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ * 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.
+ *
+ **********************************************************************/
+
+#pragma once
+
+#include <geos/geom/Location.h>
+#include <geos/export.h>
+
+
+// Forward declarations
+namespace geos {
+namespace operation {
+namespace relateng {
+    class NodeSections;
+    class NodeSection;
+}
+}
+namespace geom {
+    class CoordinateSequence;
+    class CoordinateXY;
+    class Geometry;
+    class LinearRing;
+    class Polygon;
+}
+}
+
+
+using geos::geom::CoordinateXY;
+using geos::geom::CoordinateSequence;
+using geos::geom::Geometry;
+using geos::geom::LinearRing;
+using geos::geom::Polygon;
+using geos::geom::Location;
+
+
+
+namespace geos {      // geos.
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+/**
+ * Determines the location for a point which is known to lie
+ * on at least one edge of a set of polygons.
+ * This provides the union-semantics for determining
+ * point location in a GeometryCollection, which may
+ * have polygons with adjacent edges which are effectively
+ * in the interior of the geometry.
+ * Note that it is also possible to have adjacent edges which
+ * lie on the boundary of the geometry
+ * (e.g. a polygon contained within another polygon with adjacent edges).
+ *
+ * @author mdavis
+ *
+ */
+class GEOS_DLL AdjacentEdgeLocator {
+
+public:
+
+    AdjacentEdgeLocator(const Geometry* geom)
+    {
+        init(geom);
+    }
+
+    Location locate(const CoordinateXY* p);
+
+    /**
+     * Disable copy construction and assignment. Apparently needed to make this
+     * class compile under MSVC. (See https://stackoverflow.com/q/29565299)
+     */
+    AdjacentEdgeLocator(const AdjacentEdgeLocator&) = delete;
+    AdjacentEdgeLocator& operator=(const AdjacentEdgeLocator&) = delete;
+
+
+private:
+
+    // Members
+
+    std::vector<const CoordinateSequence*> ringList;
+
+    /*
+     * When we have to reorient rings, we end up allocating new
+     * rings, since we cannot reorient the rings of the input
+     * geometry, so this is where we store those "local" rings.
+     */
+    std::vector<std::unique_ptr<CoordinateSequence>> localRingList;
+
+
+    // Methods
+
+    void addSections(
+        const CoordinateXY* p,
+        const CoordinateSequence* ring,
+        NodeSections& sections);
+
+    NodeSection* createSection(
+        const CoordinateXY* p,
+        const CoordinateXY* prev,
+        const CoordinateXY* next);
+
+    void init(const Geometry* geom);
+
+    void addRings(const Geometry* geom);
+
+    void addRing(const LinearRing* ring, bool requireCW);
+
+
+};
+
+} // namespace geos.operation.relateng
+} // namespace geos.operation
+} // namespace geos
+
diff --git a/include/geos/operation/relateng/DimensionLocation.h b/include/geos/operation/relateng/DimensionLocation.h
new file mode 100644
index 000000000..e0e77b52b
--- /dev/null
+++ b/include/geos/operation/relateng/DimensionLocation.h
@@ -0,0 +1,60 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ * 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.
+ *
+ **********************************************************************/
+
+#pragma once
+
+#include <geos/geom/Location.h>
+#include <geos/export.h>
+
+
+using geos::geom::Location;
+
+
+namespace geos {      // geos.
+namespace operation { // geos.operation
+namespace relateng { // geos.operation.relateng
+
+
+class GEOS_DLL DimensionLocation {
+
+public:
+
+    enum DimensionLocationType {
+        EXTERIOR = -3,
+        POINT_INTERIOR = 103,
+        LINE_INTERIOR = 110,
+        LINE_BOUNDARY = 111,
+        AREA_INTERIOR = 120,
+        AREA_BOUNDARY = 121
+    };
+
+    static int locationArea(Location loc);
+
+    static int locationLine(Location loc);
+
+    static int locationPoint(Location loc);
+
+    static Location location(int dimLoc);
+
+    static int dimension(int dimLoc);
+
+    static int dimension(int dimLoc, int exteriorDim);
+
+};
+
+} // namespace geos.operation.relateng
+} // namespace geos.operation
+} // namespace geos
+
diff --git a/include/geos/operation/relateng/LinearBoundary.h b/include/geos/operation/relateng/LinearBoundary.h
new file mode 100644
index 000000000..18032663b
--- /dev/null
+++ b/include/geos/operation/relateng/LinearBoundary.h
@@ -0,0 +1,88 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ * 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.
+ *
+ **********************************************************************/
+
+#pragma once
+
+#include <geos/geom/Coordinate.h>
+#include <geos/export.h>
+
+#include <memory>
+#include <vector>
+
+// Forward declarations
+namespace geos {
+namespace algorithm {
+    class BoundaryNodeRule;
+}
+namespace geom {
+    class CoordinateXY;
+    class LineString;
+}
+}
+
+
+using geos::algorithm::BoundaryNodeRule;
+using geos::geom::Coordinate;
+using geos::geom::CoordinateXY;
+using geos::geom::LineString;
+
+
+namespace geos {      // geos.
+namespace operation { // geos.operation
+namespace relateng { // geos.operation.relateng
+
+class GEOS_DLL LinearBoundary {
+
+private:
+
+    // Members
+
+    Coordinate::ConstIntMap m_vertexDegree;
+    bool m_hasBoundary;
+    const BoundaryNodeRule& m_boundaryNodeRule;
+
+
+public:
+
+    // Constructors
+
+    LinearBoundary(std::vector<const LineString*>& lines, const BoundaryNodeRule& bnRule);
+
+    bool hasBoundary() const;
+
+    bool isBoundary(const CoordinateXY* pt) const;
+
+
+private:
+
+    // Methods
+
+    bool checkBoundary(Coordinate::ConstIntMap& vertexDegree) const;
+
+    static void computeBoundaryPoints(
+        std::vector<const LineString*>& lines,
+        Coordinate::ConstIntMap& vertexDegree);
+
+    static void addEndpoint(
+        const CoordinateXY *p,
+        Coordinate::ConstIntMap& vertexDegree);
+
+
+};
+
+} // namespace geos.operation.relateng
+} // namespace geos.operation
+} // namespace geos
+
diff --git a/include/geos/operation/relateng/NodeSection.h b/include/geos/operation/relateng/NodeSection.h
new file mode 100644
index 000000000..c464d7fed
--- /dev/null
+++ b/include/geos/operation/relateng/NodeSection.h
@@ -0,0 +1,163 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ * 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.
+ *
+ **********************************************************************/
+
+#pragma once
+
+#include <string>
+#include <sstream>
+#include <geos/export.h>
+
+// Forward declarations
+namespace geos {
+namespace geom {
+    class CoordinateXY;
+    class Geometry;
+}
+}
+
+
+using geos::geom::CoordinateXY;
+using geos::geom::Geometry;
+
+
+namespace geos {      // geos.
+namespace operation { // geos.operation
+namespace relateng { // geos.operation.relateng
+
+/**
+ * Represents a computed node along with the incident edges on either side of
+ * it (if they exist).
+ * This captures the information about a node in a geometry component
+ * required to determine the component's contribution to the node topology.
+ * A node in an area geometry always has edges on both sides of the node.
+ * A node in a linear geometry may have one or other incident edge missing, if
+ * the node occurs at an endpoint of the line.
+ * The edges of an area node are assumed to be provided
+ * with CW-shell orientation (as per JTS norm).
+ * This must be enforced by the caller.
+ *
+ * @author Martin Davis
+ *
+ */
+class GEOS_DLL NodeSection {
+
+private:
+
+    // Members
+    bool m_isA;
+    int m_dim;
+    int m_id;
+    int m_ringId;
+    const Geometry* m_poly;
+    bool m_isNodeAtVertex;
+    const CoordinateXY* m_v0;
+    const CoordinateXY* m_nodePt;
+    const CoordinateXY* m_v1;
+
+    // Methods
+
+    static int compareWithNull(const CoordinateXY* v0, const CoordinateXY* v1);
+
+    static int compare(int a, int b);
+
+public:
+
+    NodeSection(
+        bool isA,
+        int dim,
+        int id,
+        int ringId,
+        const Geometry* poly,
+        bool isNodeAtVertex,
+        const CoordinateXY* v0,
+        const CoordinateXY* nodePt,
+        const CoordinateXY* v1)
+        : m_isA(isA)
+        , m_dim(dim)
+        , m_id(id)
+        , m_ringId(ringId)
+        , m_poly(poly)
+        , m_isNodeAtVertex(isNodeAtVertex)
+        , m_v0(v0)
+        , m_nodePt(nodePt)
+        , m_v1(v1)
+        {};
+
+    NodeSection(const NodeSection* ns)
+        : m_isA(ns->isA())
+        , m_dim(ns->dimension())
+        , m_id(ns->id())
+        , m_ringId(ns->ringId())
+        , m_poly(ns->getPolygonal())
+        , m_isNodeAtVertex(ns->isNodeAtVertex())
+        , m_v0(ns->getVertex(0))
+        , m_nodePt(ns->nodePt())
+        , m_v1(ns->getVertex(1))
+        {};
+
+    const CoordinateXY* getVertex(int i) const;
+
+    const CoordinateXY* nodePt() const;
+
+    int dimension() const;
+
+    int id() const;
+
+    int ringId() const;
+
+    /**
+    * Gets the polygon this section is part of.
+    * Will be null if section is not on a polygon boundary.
+    *
+    * @return the associated polygon, or null
+    */
+    const Geometry* getPolygonal() const;
+
+    bool isShell() const;
+
+    bool isArea() const;
+
+    bool isA() const;
+
+    bool isSameGeometry(const NodeSection& ns) const;
+
+    bool isSamePolygon(const NodeSection& ns) const;
+
+    bool isNodeAtVertex() const;
+
+    bool isProper() const;
+
+    static bool isProper(const NodeSection& a, const NodeSection& b);
+
+    std::string toString() const;
+
+    static std::string edgeRep(const CoordinateXY* p0, const CoordinateXY* p1);
+
+    friend std::ostream& operator<<(std::ostream& os, const NodeSection& ns);
+
+    /**
+    * Compare node sections by parent geometry, dimension, element id and ring id,
+    * and edge vertices.
+    * Sections are assumed to be at the same node point.
+    */
+    int compareTo(const NodeSection& o) const;
+
+
+};
+
+} // namespace geos.operation.relateng
+} // namespace geos.operation
+} // namespace geos
+
diff --git a/include/geos/operation/relateng/NodeSections.h b/include/geos/operation/relateng/NodeSections.h
new file mode 100644
index 000000000..9f321dd27
--- /dev/null
+++ b/include/geos/operation/relateng/NodeSections.h
@@ -0,0 +1,102 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ * 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.
+ *
+ **********************************************************************/
+
+#pragma once
+
+#include <vector>
+#include <memory>
+// #include <geos/operation/relateng/NodeSection.h>
+#include <geos/export.h>
+
+
+// Forward declarations
+namespace geos {
+namespace operation {
+namespace relateng {
+class RelateNode;
+class NodeSection;
+}
+}
+namespace geom {
+    class CoordinateXY;
+    class Geometry;
+}
+}
+
+
+using geos::geom::CoordinateXY;
+using geos::geom::Geometry;
+
+
+namespace geos {      // geos.
+namespace operation { // geos.operation
+namespace relateng { // geos.operation.relateng
+
+
+class GEOS_DLL NodeSections {
+
+private:
+
+    // Members
+    const CoordinateXY* nodePt;
+    std::vector<std::unique_ptr<NodeSection>> sections;
+
+    // Methods
+
+    /**
+    * Sorts the sections so that:
+    *  * lines are before areas
+    *  * edges from the same polygon are contiguous
+    */
+    void prepareSections();
+
+    static bool hasMultiplePolygonSections(
+        std::vector<std::unique_ptr<NodeSection>>& sections,
+        std::size_t i);
+
+    static std::vector<const NodeSection*> collectPolygonSections(
+        std::vector<std::unique_ptr<NodeSection>>& sections,
+        std::size_t i);
+
+
+public:
+
+    NodeSections(const CoordinateXY* pt)
+        : nodePt(pt)
+        {};
+
+    const CoordinateXY* getCoordinate() const;
+
+    void addNodeSection(NodeSection* e);
+
+    bool hasInteractionAB() const;
+
+    const Geometry* getPolygonal(bool isA) const;
+
+    std::unique_ptr<RelateNode> createNode();
+
+    /**
+     * Disable copy construction and assignment. Apparently needed to make this
+     * class compile under MSVC. (See https://stackoverflow.com/q/29565299)
+     */
+    NodeSections(const NodeSections&) = delete;
+    NodeSections& operator=(const NodeSections&) = delete;
+
+};
+
+} // namespace geos.operation.relateng
+} // namespace geos.operation
+} // namespace geos
+
diff --git a/include/geos/operation/relateng/PolygonNodeConverter.h b/include/geos/operation/relateng/PolygonNodeConverter.h
new file mode 100644
index 000000000..0b1ddce35
--- /dev/null
+++ b/include/geos/operation/relateng/PolygonNodeConverter.h
@@ -0,0 +1,112 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ * 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.
+ *
+ **********************************************************************/
+
+#pragma once
+
+#include <geos/operation/relateng/NodeSection.h>
+#include <geos/export.h>
+#include <vector>
+#include <memory>
+
+// Forward declarations
+namespace geos {
+namespace operation {
+namespace relateng {
+// class NodeSection;
+}
+}
+}
+
+
+// using geos::geom::CoordinateXY;
+// using geos::geom::Geometry;
+
+
+namespace geos {      // geos.
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+/**
+ * Converts the node sections at a polygon node where
+ * a shell and one or more holes touch, or two or more holes touch.
+ * This converts the node topological structure from
+ * the OGC "touching-rings" (AKA "minimal-ring") model to the equivalent "self-touch"
+ * (AKA "inverted/exverted ring" or "maximal ring") model.
+ * In the "self-touch" model the converted NodeSection corners enclose areas
+ * which all lies inside the polygon
+ * (i.e. they does not enclose hole edges).
+ * This allows RelateNode to use simple area-additive semantics
+ * for adding edges and propagating edge locations.
+ *
+ * The input node sections are assumed to have canonical orientation
+ * (CW shells and CCW holes).
+ * The arrangement of shells and holes must be topologically valid.
+ * Specifically, the node sections must not cross or be collinear.
+ *
+ * This supports multiple shell-shell touches
+ * (including ones containing holes), and hole-hole touches,
+ * This generalizes the relate algorithm to support
+ * both the OGC model and the self-touch model.
+ *
+ * @author Martin Davis
+ * @see RelateNode
+ */
+class GEOS_DLL PolygonNodeConverter {
+
+public:
+
+    /**
+    * Converts a list of sections of valid polygon rings
+    * to have "self-touching" structure.
+    * There are the same number of output sections as input ones.
+    *
+    * @param polySections the original sections
+    * @return the converted sections
+    */
+    static std::vector<std::unique_ptr<NodeSection>> convert(
+        std::vector<const NodeSection*>& polySections);
+
+
+private:
+
+    static std::size_t convertShellAndHoles(
+        std::vector<const NodeSection*>& sections,
+        std::size_t shellIndex,
+        std::vector<std::unique_ptr<NodeSection>>& convertedSections);
+
+    static std::vector<std::unique_ptr<NodeSection>> convertHoles(
+        std::vector<const NodeSection*>& sections);
+
+    static NodeSection* createSection(
+        const NodeSection* ns,
+        const CoordinateXY* v0,
+        const CoordinateXY* v1);
+
+    static std::vector<const NodeSection*> extractUnique(
+        std::vector<const NodeSection*>& sections);
+
+    static std::size_t next(
+        std::vector<const NodeSection *>& ns, std::size_t i);
+
+    static std::size_t findShell(
+        std::vector<const NodeSection *>& polySections);
+
+
+};
+
+} // namespace geos.operation.relateng
+} // namespace geos.operation
+} // namespace geos
+
diff --git a/include/geos/operation/relateng/RelateEdge.h b/include/geos/operation/relateng/RelateEdge.h
new file mode 100644
index 000000000..96afc244c
--- /dev/null
+++ b/include/geos/operation/relateng/RelateEdge.h
@@ -0,0 +1,176 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ * 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.
+ *
+ **********************************************************************/
+
+#pragma once
+
+#include <geos/geom/Location.h>
+#include <geos/export.h>
+
+#include <memory>
+#include <vector>
+
+// Forward declarations
+namespace geos {
+namespace operation {
+namespace relateng {
+class RelateNode;
+}
+}
+namespace geom {
+    class CoordinateXY;
+    class Geometry;
+}
+}
+
+
+using geos::geom::CoordinateXY;
+using geos::geom::Geometry;
+using geos::geom::Location;
+
+
+namespace geos {      // geos.
+namespace operation { // geos.operation
+namespace relateng { // geos.operation.relateng
+
+
+class GEOS_DLL RelateEdge {
+
+private:
+
+    /**
+    * Indicates that the location is currently unknown
+    */
+    static constexpr Location LOC_UNKNOWN = Location::NONE;
+
+    // Members
+    const RelateNode* node;
+    const CoordinateXY* dirPt;
+
+    int aDim = DIM_UNKNOWN;
+    Location aLocLeft = LOC_UNKNOWN;
+    Location aLocRight = LOC_UNKNOWN;
+    Location aLocLine = LOC_UNKNOWN;
+
+    int bDim = DIM_UNKNOWN;
+    Location bLocLeft = LOC_UNKNOWN;
+    Location bLocRight = LOC_UNKNOWN;
+    Location bLocLine = LOC_UNKNOWN;
+
+
+public:
+
+    // Constants
+    static constexpr bool IS_FORWARD = true;
+    static constexpr bool IS_REVERSE = false;
+    static constexpr int DIM_UNKNOWN = -1;
+
+    // Constructors
+    RelateEdge(
+        const RelateNode* node, const CoordinateXY* pt,
+        bool isA, bool isForward);
+
+    RelateEdge(
+        const RelateNode* node, const CoordinateXY* pt,
+        bool isA);
+
+    RelateEdge(
+        const RelateNode* node, const CoordinateXY* pt,
+        bool isA, Location locLeft, Location locRight, Location locLine);
+
+    // Methods
+    static RelateEdge* create(
+        const RelateNode* node,
+        const CoordinateXY* dirPt,
+        bool isA, int dim, bool isForward);
+
+    static std::size_t findKnownEdgeIndex(
+        std::vector<std::unique_ptr<RelateEdge>>& edges,
+        bool isA);
+
+    static void setAreaInterior(
+        std::vector<std::unique_ptr<RelateEdge>>& edges,
+        bool isA);
+
+    bool isInterior(bool isA, int position) const;
+
+    Location location(bool isA, int position) const;
+
+    int compareToEdge(const CoordinateXY* edgeDirPt) const;
+
+    void setDimLocations(bool isA, int dim, Location loc);
+
+    void setAreaInterior(bool isA);
+
+    void setLocation(bool isA, int pos, Location loc);
+
+    void setAllLocations(bool isA, Location loc);
+
+    void setUnknownLocations(bool isA, Location loc);
+
+    void merge(bool isA, int dim, bool isForward);
+
+    std::string toString() const;
+
+    friend std::ostream& operator<<(std::ostream& os, const RelateEdge& re);
+
+
+private:
+
+    // Methods
+    void mergeSideLocation(bool isA, int pos, Location loc);
+
+    /**
+    * Area edges override Line edges.
+    * Merging edges of same dimension is a no-op for
+    * the dimension and on location.
+    * But merging an area edge into a line edge
+    * sets the dimension to A and the location to BOUNDARY.
+    *
+    * @param isA
+    * @param locEdge
+    */
+    void mergeDimEdgeLoc(bool isA, Location locEdge);
+
+    void setDimension(bool isA, int dimension);
+
+    void setLeft(bool isA, Location loc);
+
+    void setRight(bool isA, Location loc);
+
+    void setOn(bool isA, Location loc);
+
+    int dimension(bool isA) const;
+
+    bool isKnown(bool isA) const;
+
+    bool isKnown(bool isA, int pos) const;
+
+    void setLocations(bool isA, Location locLeft, Location locRight, Location locLine);
+
+    void setLocationsLine(bool isA);
+
+    void setLocationsArea(bool isA, bool isForward);
+
+    std::string labelString() const;
+
+    std::string locationString(bool isA) const;
+
+
+};
+
+} // namespace geos.operation.relateng
+} // namespace geos.operation
+} // namespace geos
+
diff --git a/include/geos/operation/relateng/RelateGeometry.h b/include/geos/operation/relateng/RelateGeometry.h
new file mode 100644
index 000000000..3969f414e
--- /dev/null
+++ b/include/geos/operation/relateng/RelateGeometry.h
@@ -0,0 +1,60 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ * 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.
+ *
+ **********************************************************************/
+
+#pragma once
+
+//#include <geos/operation/relateng/NodeSection.h>
+#include <geos/export.h>
+
+
+// Forward declarations
+namespace geos {
+namespace operation {
+namespace relateng {
+// class RelateNode;
+}
+}
+}
+
+
+// using geos::geom::CoordinateXY;
+// using geos::geom::Geometry;
+
+
+namespace geos {      // geos.
+namespace operation { // geos.operation
+namespace relateng { // geos.operation.relateng
+
+
+class GEOS_DLL RelateGeometry {
+
+private:
+
+    // Members
+
+    // Methods
+
+
+public:
+
+    static constexpr bool GEOM_A = true;
+    static constexpr bool GEOM_B = false;
+
+};
+
+} // namespace geos.operation.relateng
+} // namespace geos.operation
+} // namespace geos
+
diff --git a/include/geos/operation/relateng/RelateNode.h b/include/geos/operation/relateng/RelateNode.h
new file mode 100644
index 000000000..581eec359
--- /dev/null
+++ b/include/geos/operation/relateng/RelateNode.h
@@ -0,0 +1,146 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ * 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.
+ *
+ **********************************************************************/
+
+#pragma once
+
+#include <geos/operation/relateng/RelateEdge.h>
+
+#include <vector>
+#include <memory>
+#include <cassert>
+
+#include <geos/export.h>
+
+
+// Forward declarations
+namespace geos {
+namespace operation {
+namespace relateng {
+    class NodeSection;
+}
+}
+namespace geom {
+    class CoordinateXY;
+    class Geometry;
+}
+}
+
+
+using geos::geom::CoordinateXY;
+using geos::geom::Geometry;
+
+
+namespace geos {      // geos.
+namespace operation { // geos.operation
+namespace relateng { // geos.operation.relateng
+
+
+class GEOS_DLL RelateNode {
+
+private:
+
+    // Members
+
+    /**
+    * A list of the edges around the node in CCW order,
+    * ordered by their CCW angle with the positive X-axis.
+    */
+    std::vector<std::unique_ptr<RelateEdge>> edges;
+
+    const CoordinateXY* nodePt;
+
+
+    // Methods
+
+    void updateEdgesInArea(bool isA, std::size_t indexFrom, std::size_t indexTo);
+
+    void updateIfAreaPrev(bool isA, std::size_t index);
+
+    void updateIfAreaNext(bool isA, std::size_t index);
+
+    const RelateEdge* addLineEdge(bool isA, const CoordinateXY* dirPt);
+
+    const RelateEdge* addAreaEdge(bool isA, const CoordinateXY* dirPt, bool isForward);
+
+    /**
+    * Adds or merges an edge to the node.
+    *
+    * @param isA
+    * @param dirPt
+    * @param dim dimension of the geometry element containing the edge
+    * @param isForward the direction of the edge
+    *
+    * @return the created or merged edge for this point
+    */
+    const RelateEdge* addEdge(bool isA, const CoordinateXY* dirPt, int dim, bool isForward);
+
+    void finishNode(bool isA, bool isAreaInterior);
+
+    void propagateSideLocations(bool isA, std::size_t startIndex);
+
+    static std::size_t prevIndex(std::vector<std::unique_ptr<RelateEdge>>& list, std::size_t index);
+
+    static std::size_t nextIndex(std::vector<std::unique_ptr<RelateEdge>>& list, std::size_t i);
+
+    std::size_t indexOf(
+        const std::vector<std::unique_ptr<RelateEdge>>& edges,
+        const RelateEdge* edge) const;
+
+
+public:
+
+    RelateNode(const CoordinateXY* pt)
+        : nodePt(pt)
+        {};
+
+    const CoordinateXY* getCoordinate() const;
+
+    const std::vector<std::unique_ptr<RelateEdge>>& getEdges() const;
+
+    void addEdges(std::vector<const NodeSection *>& nss);
+    void addEdges(std::vector<std::unique_ptr<NodeSection>>& nss);
+
+    void addEdges(const NodeSection* ns);
+
+    /**
+    * Computes the final topology for the edges around this node.
+    * Although nodes lie on the boundary of areas or the interior of lines,
+    * in a mixed GC they may also lie in the interior of an area.
+    * This changes the locations of the sides and line to Interior.
+    *
+    * @param isAreaInteriorA true if the node is in the interior of A
+    * @param isAreaInteriorB true if the node is in the interior of B
+    */
+    void finish(bool isAreaInteriorA, bool isAreaInteriorB);
+
+    std::string toString() const;
+
+    bool hasExteriorEdge(bool isA);
+
+    friend std::ostream& operator<<(std::ostream& os, const RelateNode& ns);
+
+    /**
+     * Disable copy construction and assignment. Apparently needed to make this
+     * class compile under MSVC. (See https://stackoverflow.com/q/29565299)
+     */
+    RelateNode(const RelateNode&) = delete;
+    RelateNode& operator=(const RelateNode&) = delete;
+
+};
+
+} // namespace geos.operation.relateng
+} // namespace geos.operation
+} // namespace geos
+
diff --git a/include/geos/operation/relateng/RelatePointLocator.h b/include/geos/operation/relateng/RelatePointLocator.h
new file mode 100644
index 000000000..aab269c5a
--- /dev/null
+++ b/include/geos/operation/relateng/RelatePointLocator.h
@@ -0,0 +1,213 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ * 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.
+ *
+ **********************************************************************/
+
+#pragma once
+
+#include <geos/algorithm/BoundaryNodeRule.h>
+#include <geos/algorithm/locate/PointOnGeometryLocator.h>
+#include <geos/operation/relateng/AdjacentEdgeLocator.h>
+#include <geos/operation/relateng/LinearBoundary.h>
+#include <geos/geom/Coordinate.h>
+#include <geos/geom/Location.h>
+#include <geos/export.h>
+
+#include <memory>
+#include <vector>
+
+// Forward declarations
+namespace geos {
+namespace algorithm {
+    namespace locate {
+        // class PointOnGeometryLocator;
+    }
+}
+namespace operation {
+    namespace relateng {
+        // class LinearBoundary;
+        // class AdjacentEdgeLocator;
+    }
+}
+namespace geom {
+    class CoordinateXY;
+    class Geometry;
+    class LineString;
+    class Point;
+}
+}
+
+
+using geos::algorithm::BoundaryNodeRule;
+using geos::algorithm::locate::PointOnGeometryLocator;
+using geos::geom::Coordinate;
+using geos::geom::CoordinateXY;
+using geos::geom::Geometry;
+using geos::geom::LineString;
+using geos::geom::Point;
+using geos::geom::Location;
+
+
+namespace geos {      // geos.
+namespace operation { // geos.operation
+namespace relateng { // geos.operation.relateng
+
+
+/**
+ * Locates a point on a geometry, including mixed-type collections.
+ * The dimension of the containing geometry element is also determined.
+ * GeometryCollections are handled with union semantics;
+ * i.e. the location of a point is that location of that point
+ * on the union of the elements of the collection.
+ *
+ * Union semantics for GeometryCollections has the following behaviours:
+ *
+ *  * For a mixed-dimension (heterogeneous) collection
+ *    a point may lie on two geometry elements with different dimensions.
+ *    In this case the location on the largest-dimension element is reported.
+ *  * For a collection with overlapping or adjacent polygons,
+ *    points on polygon element boundaries may lie in the effective interior
+ *    of the collection geometry.
+ *
+ * Prepared mode is supported via cached spatial indexes.
+ *
+ * @author Martin Davis
+ */
+class GEOS_DLL RelatePointLocator {
+
+private:
+
+    // Members
+
+    const Geometry* geom;
+    bool isPrepared = false;
+    const BoundaryNodeRule& boundaryRule;
+    std::unique_ptr<AdjacentEdgeLocator> adjEdgeLocator;
+    Coordinate::ConstXYSet points;
+    std::vector<const LineString *> lines;
+    std::vector<const Geometry *> polygons;
+    std::vector<std::unique_ptr<PointOnGeometryLocator>> polyLocator;
+    std::unique_ptr<LinearBoundary> lineBoundary;
+    bool isEmpty;
+
+
+public:
+
+    // Constructors
+
+    RelatePointLocator(const Geometry* p_geom)
+        : RelatePointLocator(p_geom, false, BoundaryNodeRule::getBoundaryRuleMod2())
+        {};
+
+    RelatePointLocator(const Geometry* p_geom, bool p_isPrepared, const BoundaryNodeRule& p_bnRule)
+        : geom(p_geom)
+        , isPrepared(p_isPrepared)
+        , boundaryRule(p_bnRule)
+    {
+        init(geom);
+    };
+
+    void init(const Geometry* p_geom);
+
+    bool hasBoundary() const;
+
+    void extractElements(const Geometry* geom);
+
+    void addPoint(const Point* pt);
+
+    void addLine(const LineString* line);
+
+    void addPolygonal(const Geometry* polygonal);
+
+    Location locate(const CoordinateXY* p);
+
+    Location locateLineEnd(const CoordinateXY* p) const;
+
+    /*
+    * Locates a point which is known to be a node of the geometry
+    * (i.e. a point or on an edge).
+    *
+    * @param p the node point to locate
+    * @param parentPolygonal the polygon the point is a node of
+    * @return the location of the node point
+    */
+    Location locateNode(const CoordinateXY* p, const Geometry* parentPolygonal);
+
+    /**
+    * Locates a point which is known to be a node of the geometry,
+    * as a DimensionLocation.
+    *
+    * @param p the point to locate
+    * @param parentPolygonal the polygon the point is a node of
+    * @return the dimension and location of the point
+    */
+    int locateNodeWithDim(const CoordinateXY* p, const Geometry* parentPolygonal);
+
+    /**
+    * Computes the topological location ( Location) of a single point
+    * in a Geometry, as well as the dimension of the geometry element the point
+    * is located in (if not in the Exterior).
+    * It handles both single-element and multi-element Geometries.
+    * The algorithm for multi-part Geometries
+    * takes into account the SFS Boundary Determination Rule.
+    *
+    * @param p the point to locate
+    * @return the Location of the point relative to the input Geometry
+    */
+    int locateWithDim(const CoordinateXY* p);
+
+
+private:
+
+    // Methods
+
+    /**
+    * Computes the topological location (Location) of a single point
+    * in a Geometry, as well as the dimension of the geometry element the point
+    * is located in (if not in the Exterior).
+    * It handles both single-element and multi-element Geometries.
+    * The algorithm for multi-part Geometries
+    * takes into account the SFS Boundary Determination Rule.
+    *
+    * @param p the coordinate to locate
+    * @param isNode whether the coordinate is a node (on an edge) of the geometry
+    * @param polygon
+    * @return the Location of the point relative to the input Geometry
+    */
+    int locateWithDim(const CoordinateXY* p, bool isNode, const Geometry* parentPolygonal);
+
+    int computeDimLocation(const CoordinateXY* p, bool isNode, const Geometry* parentPolygonal);
+
+    Location locateOnPoints(const CoordinateXY* p) const;
+
+    Location locateOnLines(const CoordinateXY* p, bool isNode);
+
+    Location locateOnLine(const CoordinateXY* p, /*bool isNode,*/ const LineString* l);
+
+    Location locateOnPolygons(const CoordinateXY* p, bool isNode, const Geometry* parentPolygonal);
+
+    Location locateOnPolygonal(const CoordinateXY* p,
+        bool isNode,
+        const Geometry* parentPolygonal,
+        std::size_t index);
+
+    PointOnGeometryLocator * getLocator(std::size_t index);
+
+
+
+};
+
+} // namespace geos.operation.relateng
+} // namespace geos.operation
+} // namespace geos
+
diff --git a/include/geos/operation/valid/IsSimpleOp.h b/include/geos/operation/valid/IsSimpleOp.h
index b26d6d53f..1f2abfa68 100644
--- a/include/geos/operation/valid/IsSimpleOp.h
+++ b/include/geos/operation/valid/IsSimpleOp.h
@@ -29,9 +29,9 @@ namespace noding {
 class SegmentString;
 class BasicSegmentString;
 }
-namespace algorithm {
-class BoundaryNodeRule;
-}
+// namespace algorithm {
+// class BoundaryNodeRule;
+// }
 namespace geom {
 class LineString;
 class LinearRing;
diff --git a/src/algorithm/PolygonNodeTopology.cpp b/src/algorithm/PolygonNodeTopology.cpp
index 588cbdf9f..7b179c9ae 100644
--- a/src/algorithm/PolygonNodeTopology.cpp
+++ b/src/algorithm/PolygonNodeTopology.cpp
@@ -78,6 +78,37 @@ PolygonNodeTopology::isBetween(const CoordinateXY* origin,
 }
 
 
+/* public static */
+int
+PolygonNodeTopology::compareAngle(
+    const CoordinateXY* origin,
+    const CoordinateXY* p,
+    const CoordinateXY* q)
+{
+    int quadrantP = quadrant(origin, p);
+    int quadrantQ = quadrant(origin, q);
+
+    /**
+     * If the vectors are in different quadrants,
+     * that determines the ordering
+     */
+    if (quadrantP > quadrantQ) return 1;
+    if (quadrantP < quadrantQ) return -1;
+
+    //--- vectors are in the same quadrant
+    // Check relative orientation of vectors
+    // P > Q if it is CCW of Q
+    int orient = Orientation::index(*origin, *q, *p);
+    switch (orient) {
+        case Orientation::COUNTERCLOCKWISE:
+            return 1;
+        case Orientation::CLOCKWISE:
+            return -1;
+        default:
+            return 0;
+    }
+}
+
 /* private static */
 bool
 PolygonNodeTopology::isAngleGreater(const CoordinateXY* origin,
diff --git a/src/io/WKTWriter.cpp b/src/io/WKTWriter.cpp
index 116db3178..53fa14269 100644
--- a/src/io/WKTWriter.cpp
+++ b/src/io/WKTWriter.cpp
@@ -107,17 +107,11 @@ WKTWriter::toLineString(const CoordinateSequence& seq)
 
 /*static*/
 std::string
-WKTWriter::toLineString(const Coordinate& p0, const Coordinate& p1)
+WKTWriter::toLineString(const CoordinateXY& p0, const CoordinateXY& p1)
 {
     std::stringstream ret(std::ios_base::in | std::ios_base::out);
     ret << "LINESTRING (" << p0.x << " " << p0.y;
-#if PRINT_Z
-    ret << " " << p0.z;
-#endif
     ret << ", " << p1.x << " " << p1.y;
-#if PRINT_Z
-    ret << " " << p1.z;
-#endif
     ret << ")";
 
     return ret.str();
diff --git a/src/operation/relateng/AdjacentEdgeLocator.cpp b/src/operation/relateng/AdjacentEdgeLocator.cpp
new file mode 100644
index 000000000..a065ee984
--- /dev/null
+++ b/src/operation/relateng/AdjacentEdgeLocator.cpp
@@ -0,0 +1,159 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ * 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.
+ *
+ **********************************************************************/
+
+
+#include <geos/algorithm/Orientation.h>
+#include <geos/algorithm/PointLocation.h>
+#include <geos/geom/Coordinate.h>
+#include <geos/geom/Dimension.h>
+#include <geos/geom/Geometry.h>
+#include <geos/geom/LinearRing.h>
+#include <geos/geom/Polygon.h>
+#include <geos/operation/relateng/AdjacentEdgeLocator.h>
+#include <geos/operation/relateng/NodeSection.h>
+#include <geos/operation/relateng/NodeSections.h>
+#include <geos/operation/relateng/RelateNode.h>
+#include <geos/constants.h>
+
+
+using geos::algorithm::Orientation;
+using geos::algorithm::PointLocation;
+using geos::geom::CoordinateXY;
+using geos::geom::Dimension;
+using geos::geom::Geometry;
+using geos::geom::LinearRing;
+using geos::geom::Location;
+using geos::geom::Polygon;
+
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+
+/* public */
+Location
+AdjacentEdgeLocator::locate(const CoordinateXY* p)
+{
+    NodeSections sections(p);
+    for (const CoordinateSequence* ring : ringList) {
+        addSections(p, ring, sections);
+    }
+    std::unique_ptr<RelateNode> node = sections.createNode();
+
+    return node->hasExteriorEdge(true) ? Location::BOUNDARY : Location::INTERIOR;
+}
+
+
+/* private */
+void
+AdjacentEdgeLocator::addSections(
+    const CoordinateXY* p,
+    const CoordinateSequence* ring,
+    NodeSections& sections)
+{
+    for (std::size_t i = 0; i < ring->getSize() - 1; i++) {
+        const CoordinateXY& p0 = ring->getAt(i);
+        const CoordinateXY& pnext = ring->getAt(i+1);
+        if (p->equals2D(pnext)) {
+            //-- segment final point is assigned to next segment
+            continue;
+        }
+        else if (p->equals2D(p0)) {
+            std::size_t iprev = i > 0 ? i - 1 : ring->getSize() - 2;
+            const CoordinateXY& pprev = ring->getAt(iprev);
+            NodeSection *ns = createSection(p, &pprev, &pnext);
+            sections.addNodeSection(ns);
+        }
+        else if (PointLocation::isOnSegment(*p, p0, pnext)) {
+            NodeSection *ns = createSection(p, &p0, &pnext);
+            sections.addNodeSection(ns);
+        }
+    }
+}
+
+
+/* private */
+NodeSection*
+AdjacentEdgeLocator::createSection(const CoordinateXY* p,
+    const CoordinateXY* prev,
+    const CoordinateXY* next)
+{
+    if (prev->distance(*p) == 0 || next->distance(*p) == 0) {
+        //System.out.println("Found zero-length section segment");
+    };
+    return new NodeSection(true, Dimension::A, 1, 0, nullptr, false, prev, p, next);
+}
+
+
+/* private */
+void
+AdjacentEdgeLocator::init(const Geometry* geom)
+{
+    if (geom->isEmpty())
+        return;
+    addRings(geom);
+}
+
+
+/* private */
+void
+AdjacentEdgeLocator::addRings(const Geometry* geom)
+{
+    if (const Polygon* poly = dynamic_cast<const Polygon*>(geom)) {
+        const LinearRing* shell = poly->getExteriorRing();
+        addRing(shell, true);
+        for (std::size_t i = 0; i < poly->getNumInteriorRing(); i++) {
+            const LinearRing* hole = poly->getInteriorRingN(i);
+            addRing(hole, false);
+        }
+    }
+    else if (geom->isCollection()) {
+        //-- recurse through collections
+        for (std::size_t i = 0; i < geom->getNumGeometries(); i++) {
+            addRings(geom->getGeometryN(i));
+        }
+    }
+}
+
+
+/* private */
+void
+AdjacentEdgeLocator::addRing(const LinearRing* ring, bool requireCW)
+{
+    //TODO: remove repeated points?
+    const CoordinateSequence* pts = ring->getCoordinatesRO();
+    bool isFlipped = requireCW == Orientation::isCCW(pts);
+    /*
+     * In case of flipped rings, we need to keep a local copy
+     * since we cannot mutate the const geometry we are fed
+     * in the constructor.
+     */
+    if (isFlipped) {
+        std::unique_ptr<CoordinateSequence> localPts = pts->clone();
+        localPts->reverse();
+        ringList.push_back(localPts.get());
+        localRingList.push_back(std::move(localPts));
+    }
+    else {
+        ringList.push_back(pts);
+    }
+    return;
+}
+
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
diff --git a/src/operation/relateng/DimensionLocation.cpp b/src/operation/relateng/DimensionLocation.cpp
new file mode 100644
index 000000000..fd5fceb4f
--- /dev/null
+++ b/src/operation/relateng/DimensionLocation.cpp
@@ -0,0 +1,129 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ * 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.
+ *
+ **********************************************************************/
+
+#include <geos/operation/relateng/DimensionLocation.h>
+#include <geos/geom/Dimension.h>
+#include <geos/geom/Location.h>
+
+
+using geos::geom::Location;
+using geos::geom::Dimension;
+
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+
+/* public static */
+int
+DimensionLocation::locationArea(Location loc)
+{
+    switch (loc) {
+        case Location::INTERIOR: return AREA_INTERIOR;
+        case Location::BOUNDARY: return AREA_BOUNDARY;
+        default:
+            return EXTERIOR;
+    }
+    return EXTERIOR;
+}
+
+
+/* public static */
+int
+DimensionLocation::locationLine(Location loc)
+{
+    switch (loc) {
+        case Location::INTERIOR: return LINE_INTERIOR;
+        case Location::BOUNDARY: return LINE_BOUNDARY;
+        default:
+            return EXTERIOR;
+    }
+    return EXTERIOR;
+}
+
+
+/* public static */
+int
+DimensionLocation::locationPoint(Location loc)
+{
+    switch (loc) {
+        case Location::INTERIOR: return POINT_INTERIOR;
+        default:
+            return EXTERIOR;
+    }
+    return EXTERIOR;
+}
+
+
+/* public static */
+Location
+DimensionLocation::location(int dimLoc)
+{
+    switch (dimLoc) {
+        case POINT_INTERIOR:
+        case LINE_INTERIOR:
+        case AREA_INTERIOR:
+            return Location::INTERIOR;
+        case LINE_BOUNDARY:
+        case AREA_BOUNDARY:
+            return Location::BOUNDARY;
+        default:
+            return Location::EXTERIOR;
+    }
+    return Location::EXTERIOR;
+}
+
+
+/* public static */
+int
+DimensionLocation::dimension(int dimLoc)
+{
+    switch (dimLoc) {
+        case POINT_INTERIOR:
+            return Dimension::P;
+        case LINE_INTERIOR:
+        case LINE_BOUNDARY:
+            return Dimension::L;
+        case AREA_INTERIOR:
+        case AREA_BOUNDARY:
+            return Dimension::A;
+        default:
+            return Dimension::False;
+    }
+    return Dimension::False;
+}
+
+
+/* public static */
+int
+DimensionLocation::dimension(int dimLoc, int exteriorDim)
+{
+    if (dimLoc == EXTERIOR)
+        return exteriorDim;
+    return dimension(dimLoc);
+}
+
+
+
+
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
+
+
+
+
diff --git a/src/operation/relateng/LinearBoundary.cpp b/src/operation/relateng/LinearBoundary.cpp
new file mode 100644
index 000000000..84caee1c1
--- /dev/null
+++ b/src/operation/relateng/LinearBoundary.cpp
@@ -0,0 +1,116 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ * 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.
+ *
+ **********************************************************************/
+
+#include <geos/algorithm/BoundaryNodeRule.h>
+#include <geos/geom/Coordinate.h>
+#include <geos/geom/CoordinateSequence.h>
+#include <geos/geom/LineString.h>
+#include <geos/operation/relateng/LinearBoundary.h>
+#include <geos/constants.h>
+
+#include <map>
+#include <set>
+
+using geos::algorithm::BoundaryNodeRule;
+using geos::geom::CoordinateXY;
+using geos::geom::CoordinateSequence;
+using geos::geom::LineString;
+
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+
+LinearBoundary::LinearBoundary(std::vector<const LineString*>& lines, const BoundaryNodeRule& bnRule)
+    : m_boundaryNodeRule(bnRule)
+{
+    //assert: dim(geom) == 1
+    computeBoundaryPoints(lines, m_vertexDegree);
+    m_hasBoundary = checkBoundary(m_vertexDegree);
+}
+
+/* private */
+bool
+LinearBoundary::checkBoundary(Coordinate::ConstIntMap& vertexDegree) const
+{
+    // Iterate over the map and test the values
+    for (const auto& pair : vertexDegree) {
+        int degree = pair.second;
+        if (m_boundaryNodeRule.isInBoundary(degree)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+/* public */
+bool
+LinearBoundary::hasBoundary() const
+{
+    return m_hasBoundary;
+}
+
+/* public */
+bool
+LinearBoundary::isBoundary(const CoordinateXY* pt) const
+{
+    auto it = m_vertexDegree.find(pt);
+    if (it == m_vertexDegree.end())
+        return false;
+
+    int degree = it->second;
+    return m_boundaryNodeRule.isInBoundary(degree);
+}
+
+/* private static */
+void
+LinearBoundary::computeBoundaryPoints(std::vector<const LineString*>& lines, Coordinate::ConstIntMap& vertexDegree)
+{
+    for (const LineString* line : lines) {
+        if (line->isEmpty())
+            continue;
+        const CoordinateSequence* cs = line->getCoordinatesRO();
+        const Coordinate& cs0 = cs->getAt(0);
+        const Coordinate& csn = cs->getAt(line->getNumPoints() - 1);
+        addEndpoint(&cs0, vertexDegree);
+        addEndpoint(&csn, vertexDegree);
+    }
+}
+
+/* private static */
+void
+LinearBoundary::addEndpoint(const CoordinateXY *p, Coordinate::ConstIntMap& vertexDegree)
+{
+    int dim = 0;
+    auto it = vertexDegree.find(p);
+
+    if (it != vertexDegree.end()) {
+        dim = it->second;
+    }
+    dim++;
+    std::pair<const CoordinateXY*, int> entry(p, dim);
+    vertexDegree.insert(entry);
+}
+
+
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
+
+
+
+
diff --git a/src/operation/relateng/NodeSection.cpp b/src/operation/relateng/NodeSection.cpp
new file mode 100644
index 000000000..3fdfac851
--- /dev/null
+++ b/src/operation/relateng/NodeSection.cpp
@@ -0,0 +1,243 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ * 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.
+ *
+ **********************************************************************/
+
+#include <geos/geom/Dimension.h>
+#include <geos/geom/Coordinate.h>
+#include <geos/geom/Geometry.h>
+#include <geos/io/WKTWriter.h>
+#include <geos/operation/relateng/NodeSection.h>
+#include <sstream>
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+using geos::geom::Coordinate;
+using geos::geom::CoordinateXY;
+using geos::geom::Geometry;
+using geos::geom::Dimension;
+using geos::io::WKTWriter;
+
+
+/* public */
+const CoordinateXY *
+NodeSection::getVertex(int i) const
+{
+    return i == 0 ? m_v0 : m_v1;
+}
+
+
+/* public */
+const CoordinateXY *
+NodeSection::nodePt() const
+{
+    return m_nodePt;
+}
+
+
+/* public */
+int
+NodeSection::dimension() const
+{
+    return m_dim;
+}
+
+
+/* public */
+int
+NodeSection::id() const
+{
+    return m_id;
+}
+
+
+/* public */
+int
+NodeSection::ringId() const
+{
+    return m_ringId;
+}
+
+
+/* public */
+const Geometry *
+NodeSection::getPolygonal() const
+{
+    return m_poly;
+}
+
+
+/* public */
+bool
+NodeSection::isShell() const
+{
+    return m_ringId == 0;
+}
+
+
+/* public */
+bool
+NodeSection::isArea() const
+{
+    return m_dim == Dimension::A;
+}
+
+
+/* public */
+bool
+NodeSection::isA() const
+{
+     return m_isA;
+}
+
+
+/* public */
+bool
+NodeSection::isSameGeometry(const NodeSection& ns) const
+{
+    return isA() == ns.isA();
+}
+
+
+/* public */
+bool
+NodeSection::isSamePolygon(const NodeSection& ns) const
+{
+    return isA() == ns.isA() && id() == ns.id();
+}
+
+
+/* public */
+bool
+NodeSection::isNodeAtVertex() const
+{
+    return m_isNodeAtVertex;
+}
+
+
+/* public */
+bool
+NodeSection::isProper() const
+{
+    return ! m_isNodeAtVertex;
+}
+
+
+/* public static */
+bool
+NodeSection::isProper(const NodeSection& a, const NodeSection& b)
+{
+    return a.isProper() && b.isProper();
+}
+
+
+/* public static */
+std::string
+NodeSection::edgeRep(const CoordinateXY* p0, const CoordinateXY* p1)
+{
+    if (p0 == nullptr || p1 == nullptr)
+        return "null";
+    return WKTWriter::toLineString(*p0, *p1);
+}
+
+
+/* private */
+int
+NodeSection::compare(int a, int b)
+{
+    if (a < b) return -1;
+    if (a > b) return 1;
+    return 0;
+}
+
+
+/* public */
+int
+NodeSection::compareTo(const NodeSection& o) const
+{
+    // Assert: nodePt.equals2D(o.nodePt())
+
+    // sort A before B
+    if (m_isA != o.m_isA) {
+        if (m_isA) return -1;
+        return 1;
+    }
+    //-- sort on dimensions
+    int compDim = compare(m_dim,  o.m_dim);
+    if (compDim != 0) return compDim;
+
+    //-- sort on id and ring id
+    int compId = compare(m_id, o.m_id);
+    if (compId != 0) return compId;
+
+    int compRingId = compare(m_ringId, o.m_ringId);
+    if (compRingId != 0) return compRingId;
+
+    //-- sort on edge coordinates
+    int compV0 = compareWithNull(m_v0, o.m_v0);
+    if (compV0 != 0) return compV0;
+
+    return compareWithNull(m_v1, o.m_v1);
+}
+
+
+/* private static */
+int
+NodeSection::compareWithNull(const CoordinateXY* v0, const CoordinateXY* v1)
+{
+    if (v0 == nullptr) {
+        if (v1 == nullptr)
+            return 0;
+        //-- null is lower than non-null
+        return -1;
+    }
+    // v0 is non-null
+    if (v1 == nullptr)
+        return 1;
+    return v0->compareTo(*v1);
+}
+
+
+/* public */
+std::string
+NodeSection::toString() const
+{
+    // TODO, port RelateGeometry
+    // os << RelateGeometry::name(m_isA);
+    std::stringstream ss;
+    ss << m_dim;
+    if (m_id >= 0) {
+        ss << "[" << m_id << ":" << m_ringId << "]";
+    }
+    ss << ": " << edgeRep(m_v0, m_nodePt);
+    ss << (m_isNodeAtVertex ? "-V-" : "---");
+    ss << " " << edgeRep(m_nodePt, m_v1);
+    return ss.str();
+}
+
+/* public friend */
+std::ostream&
+operator<<(std::ostream& os, const NodeSection& ns)
+{
+    os << ns.toString();
+    return os;
+}
+
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
+
+
diff --git a/src/operation/relateng/NodeSections.cpp b/src/operation/relateng/NodeSections.cpp
new file mode 100644
index 000000000..e02433d32
--- /dev/null
+++ b/src/operation/relateng/NodeSections.cpp
@@ -0,0 +1,163 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ * 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.
+ *
+ **********************************************************************/
+
+#include <geos/operation/relateng/NodeSections.h>
+#include <geos/operation/relateng/RelateNode.h>
+#include <geos/operation/relateng/PolygonNodeConverter.h>
+#include <algorithm>
+
+using geos::geom::CoordinateXY;
+using geos::geom::Geometry;
+
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+/* public */
+const CoordinateXY*
+NodeSections::getCoordinate() const
+{
+    return nodePt;
+}
+
+
+/* public */
+void
+NodeSections::addNodeSection(NodeSection* e)
+{
+    //System.out.println(e);
+    sections.emplace_back(e);
+}
+
+
+/* public */
+bool
+NodeSections::hasInteractionAB() const
+{
+    bool isA = false;
+    bool isB = false;
+    for (const std::unique_ptr<NodeSection>& ns : sections) {
+        if (ns->isA())
+            isA = true;
+        else
+            isB = true;
+
+        if (isA && isB)
+            return true;
+    }
+    return false;
+}
+
+
+/* public */
+const Geometry*
+NodeSections::getPolygonal(bool isA) const
+{
+    for (const std::unique_ptr<NodeSection>& ns : sections) {
+        if (ns->isA() == isA) {
+            const Geometry* poly = ns->getPolygonal();
+            if (poly != nullptr)
+                return poly;
+        }
+    }
+    return nullptr;
+}
+
+
+/* public */
+std::unique_ptr<RelateNode>
+NodeSections::createNode()
+{
+    prepareSections();
+
+    std::unique_ptr<RelateNode> node(new RelateNode(nodePt));
+    std::size_t i = 0;
+    while (i < sections.size()) {
+        const std::unique_ptr<NodeSection>& ns = sections[i];
+        //-- if there multiple polygon sections incident at node convert them to maximal-ring structure
+        if (ns->isArea() && hasMultiplePolygonSections(sections, i)) {
+            std::vector<const NodeSection*> polySections = collectPolygonSections(sections, i);
+            std::vector<std::unique_ptr<NodeSection>> nsConvert = PolygonNodeConverter::convert(polySections);
+            node->addEdges(nsConvert);
+            i += polySections.size();
+        }
+        else {
+            //-- the most common case is a line or a single polygon ring section
+            node->addEdges(ns.get());
+            i += 1;
+        }
+    }
+    return node;
+}
+
+
+/* private */
+void
+NodeSections::prepareSections()
+{
+    // Comparator lambda for sort support
+    auto comparator = [](
+        const std::unique_ptr<NodeSection>& a,
+        const std::unique_ptr<NodeSection>& b)
+    {
+        return a->compareTo(*b) < 0;
+    };
+
+    std::sort(sections.begin(), sections.end(), comparator);
+    //TODO: remove duplicate sections
+}
+
+
+/* private static */
+bool
+NodeSections::hasMultiplePolygonSections(
+    std::vector<std::unique_ptr<NodeSection>>& sections,
+    std::size_t i)
+{
+    //-- if last section can only be one
+    if (i >= sections.size() - 1)
+        return false;
+    //-- check if there are at least two sections for same polygon
+    std::unique_ptr<NodeSection>& ns = sections[i];
+    std::unique_ptr<NodeSection>& nsNext = sections[i + 1];
+    return ns->isSamePolygon(*nsNext);
+}
+
+
+/* private static */
+std::vector<const NodeSection*>
+NodeSections::collectPolygonSections(
+    std::vector<std::unique_ptr<NodeSection>>& sections,
+    std::size_t i)
+{
+    std::vector<const NodeSection*> polySections;
+    //-- note ids are only unique to a geometry
+    std::unique_ptr<NodeSection>& polySection = sections[i];
+    while (i < sections.size() &&
+        polySection->isSamePolygon(*(sections[i])))
+    {
+        polySections.push_back(sections[i].get());
+        i++;
+    }
+    return polySections;
+}
+
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
+
+
diff --git a/src/operation/relateng/PolygonNodeConverter.cpp b/src/operation/relateng/PolygonNodeConverter.cpp
new file mode 100644
index 000000000..54c080956
--- /dev/null
+++ b/src/operation/relateng/PolygonNodeConverter.cpp
@@ -0,0 +1,192 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ * 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.
+ *
+ **********************************************************************/
+
+#include <geos/operation/relateng/PolygonNodeConverter.h>
+
+#include <geos/algorithm/PolygonNodeTopology.h>
+#include <geos/geom/Coordinate.h>
+#include <geos/geom/Dimension.h>
+#include <geos/constants.h>
+
+#include <algorithm>
+
+
+using geos::algorithm::PolygonNodeTopology;
+using geos::geom::CoordinateXY;
+using geos::geom::Dimension;
+// using geos::geom::Location;
+// using geos::geom::Position;
+
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+
+/* public static */
+std::vector<std::unique_ptr<NodeSection>>
+PolygonNodeConverter::convert(std::vector<const NodeSection*>& polySections)
+{
+    auto comparator = [](
+        const NodeSection* ns1,
+        const NodeSection* ns2)
+    {
+        return PolygonNodeTopology::compareAngle(
+            ns1->nodePt(),
+            ns1->getVertex(0),
+            ns2->getVertex(0));
+    };
+
+    std::sort(polySections.begin(), polySections.end(), comparator);
+    // polySections.sort(new NodeSection.EdgeAngleComparator());
+
+    //TODO: move uniquing up to caller
+    std::vector<const NodeSection*> sections = extractUnique(polySections);
+    if (sections.size() == 1) {
+        std::vector<std::unique_ptr<NodeSection>> nss;
+        nss.emplace_back(new NodeSection(sections[0]));
+        return nss;
+    }
+
+    //-- find shell section index
+    std::size_t shellIndex = findShell(sections);
+    if (shellIndex == INDEX_UNKNOWN) {
+        return convertHoles(sections);
+    }
+    //-- at least one shell is present.  Handle multiple ones if present
+    std::vector<std::unique_ptr<NodeSection>> convertedSections;
+    std::size_t nextShellIndex = shellIndex;
+    do {
+        nextShellIndex = convertShellAndHoles(
+            sections, nextShellIndex, convertedSections);
+    } while (nextShellIndex != shellIndex);
+
+    return convertedSections;
+}
+
+
+/* private static */
+std::size_t
+PolygonNodeConverter::convertShellAndHoles(
+    std::vector<const NodeSection*>& sections,
+    std::size_t shellIndex,
+    std::vector<std::unique_ptr<NodeSection>>& convertedSections)
+{
+    const NodeSection* shellSection = sections[shellIndex];
+    const CoordinateXY* inVertex = shellSection->getVertex(0);
+    std::size_t i = next(sections, shellIndex);
+    const NodeSection* holeSection = nullptr;
+    while (! sections[i]->isShell()) {
+        holeSection = sections[i];
+        // Assert: holeSection.isShell() = false
+        const CoordinateXY* outVertex = holeSection->getVertex(1);
+        NodeSection* ns = createSection(shellSection, inVertex, outVertex);
+        convertedSections.emplace_back(ns);
+        inVertex = holeSection->getVertex(0);
+        i = next(sections, i);
+    }
+    //-- create final section for corner from last hole to shell
+    const CoordinateXY* outVertex = shellSection->getVertex(1);
+    NodeSection* ns = createSection(shellSection, inVertex, outVertex);
+    convertedSections.emplace_back(ns);
+    return i;
+}
+
+
+/* private static */
+std::vector<std::unique_ptr<NodeSection>>
+PolygonNodeConverter::convertHoles(std::vector<const NodeSection*>& sections)
+{
+    std::vector<std::unique_ptr<NodeSection>> convertedSections;
+    const NodeSection* copySection = sections[0];
+    for (std::size_t i = 0; i < sections.size(); i++) {
+        std::size_t inext = next(sections, i);
+        const CoordinateXY* inVertex = sections[i]->getVertex(0);
+        const CoordinateXY* outVertex = sections[inext]->getVertex(1);
+        NodeSection* ns = createSection(copySection, inVertex, outVertex);
+        convertedSections.emplace_back(ns);
+    }
+    return convertedSections;
+}
+
+
+/* private static */
+NodeSection*
+PolygonNodeConverter::createSection(
+    const NodeSection* ns,
+    const CoordinateXY* v0,
+    const CoordinateXY* v1)
+{
+    return new NodeSection(
+        ns->isA(),
+        Dimension::A,
+        ns->id(), 0,
+        ns->getPolygonal(),
+        ns->isNodeAtVertex(),
+        v0, ns->nodePt(), v1);
+}
+
+
+
+/* private static */
+std::vector<const NodeSection*>
+PolygonNodeConverter::extractUnique(std::vector<const NodeSection*>& sections)
+{
+    std::vector<const NodeSection*> uniqueSections;
+    const NodeSection* lastUnique = sections[0];
+    uniqueSections.push_back(lastUnique);
+    for (const NodeSection* ns : sections) {
+        if (0 != lastUnique->compareTo(ns)) {
+            uniqueSections.push_back(ns);
+            lastUnique = ns;
+        }
+    }
+    return uniqueSections;
+}
+
+
+/* private static */
+std::size_t
+PolygonNodeConverter::next(std::vector<const NodeSection *>& ns, std::size_t i)
+{
+    std::size_t nxt = i;
+    if (nxt == INDEX_UNKNOWN)
+        nxt = 0;
+    else
+        nxt = i + 1;
+
+    if (nxt >= ns.size())
+        nxt = 0;
+
+    return nxt;
+}
+
+
+/* private static */
+std::size_t
+PolygonNodeConverter::findShell(std::vector<const NodeSection *>& polySections)
+{
+    for (std::size_t i = 0; i < polySections.size(); i++) {
+        if (polySections[i]->isShell())
+            return i;
+    }
+    return INDEX_UNKNOWN;
+}
+
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
+
diff --git a/src/operation/relateng/RelateEdge.cpp b/src/operation/relateng/RelateEdge.cpp
new file mode 100644
index 000000000..b92cd93c3
--- /dev/null
+++ b/src/operation/relateng/RelateEdge.cpp
@@ -0,0 +1,476 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ * 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.
+ *
+ **********************************************************************/
+
+#include <geos/algorithm/PolygonNodeTopology.h>
+#include <geos/operation/relateng/RelateEdge.h>
+#include <geos/operation/relateng/RelateNode.h>
+#include <geos/operation/relateng/RelateGeometry.h>
+#include <geos/geom/Coordinate.h>
+#include <geos/geom/Dimension.h>
+#include <geos/geom/Position.h>
+#include <geos/io/WKTWriter.h>
+#include <geos/constants.h>
+#include <sstream>
+
+using geos::algorithm::PolygonNodeTopology;
+using geos::geom::CoordinateXY;
+using geos::geom::Dimension;
+using geos::geom::Location;
+using geos::geom::Position;
+using geos::io::WKTWriter;
+
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+
+/* public */
+RelateEdge::RelateEdge(const RelateNode* rNode, const CoordinateXY* pt, bool isA, bool isForward)
+    : node(rNode)
+    , dirPt(pt)
+{
+    setLocationsArea(isA, isForward);
+}
+
+
+/* public */
+RelateEdge::RelateEdge(const RelateNode* rNode, const CoordinateXY* pt, bool isA)
+    : node(rNode)
+    , dirPt(pt)
+{
+    setLocationsLine(isA);
+}
+
+
+/* public */
+RelateEdge::RelateEdge(const RelateNode* rNode, const CoordinateXY* pt,
+    bool isA, Location locLeft, Location locRight, Location locLine)
+    : node(rNode)
+    , dirPt(pt)
+{
+    setLocations(isA, locLeft, locRight, locLine);
+}
+
+
+/* public static */
+RelateEdge *
+RelateEdge::create(const RelateNode* node, const CoordinateXY* dirPt, bool isA, int dim, bool isForward)
+{
+    if (dim == Dimension::A)
+        //-- create an area edge
+        return new RelateEdge(node, dirPt, isA, isForward);
+    //-- create line edge
+    return new RelateEdge(node, dirPt, isA);
+}
+
+
+/* public static */
+std::size_t
+RelateEdge::findKnownEdgeIndex(
+    std::vector<std::unique_ptr<RelateEdge>>& edges,
+    bool isA)
+{
+    for (std::size_t i = 0; i < edges.size(); i++) {
+        auto& e = edges[i];
+        if (e->isKnown(isA))
+            return i;
+    }
+    return INDEX_UNKNOWN;
+}
+
+
+/* public static */
+void
+RelateEdge::setAreaInterior(
+    std::vector<std::unique_ptr<RelateEdge>>& edges,
+    bool isA)
+{
+    for (auto& e : edges) {
+        e->setAreaInterior(isA);
+    }
+}
+
+
+/* private */
+void
+RelateEdge::setLocations(bool isA, Location locLeft, Location locRight, Location locLine)
+{
+    if (isA) {
+        aDim = 2;
+        aLocLeft = locLeft;
+        aLocRight = locRight;
+        aLocLine = locLine;
+    }
+    else {
+        bDim = 2;
+        bLocLeft = locLeft;
+        bLocRight = locRight;
+        bLocLine = locLine;
+    }
+}
+
+
+/* private */
+void
+RelateEdge::setLocationsLine(bool isA)
+{
+    if (isA) {
+        aDim = 1;
+        aLocLeft = Location::EXTERIOR;
+        aLocRight = Location::EXTERIOR;
+        aLocLine = Location::INTERIOR;
+    }
+    else {
+        bDim = 1;
+        bLocLeft = Location::EXTERIOR;
+        bLocRight = Location::EXTERIOR;
+        bLocLine = Location::INTERIOR;
+    }
+}
+
+
+/* private */
+void
+RelateEdge::setLocationsArea(bool isA, bool isForward)
+{
+    Location locLeft = isForward ? Location::EXTERIOR : Location::INTERIOR;
+    Location locRight = isForward ? Location::INTERIOR : Location::EXTERIOR;
+    if (isA) {
+        aDim = 2;
+        aLocLeft = locLeft;
+        aLocRight = locRight;
+        aLocLine = Location::BOUNDARY;
+    }
+    else {
+        bDim = 2;
+        bLocLeft = locLeft;
+        bLocRight = locRight;
+        bLocLine = Location::BOUNDARY;
+    }
+}
+
+
+/* public */
+int
+RelateEdge::compareToEdge(const CoordinateXY* edgeDirPt) const
+{
+    return PolygonNodeTopology::compareAngle(
+        node->getCoordinate(),
+        dirPt,
+        edgeDirPt);
+}
+
+
+/* public */
+void
+RelateEdge::merge(bool isA, int dim, bool isForward)
+{
+    Location locEdge = Location::INTERIOR;
+    Location locLeft = Location::EXTERIOR;
+    Location locRight = Location::EXTERIOR;
+    if (dim == Dimension::A) {
+        locEdge = Location::BOUNDARY;
+        locLeft = isForward ? Location::EXTERIOR : Location::INTERIOR;
+        locRight = isForward ? Location::INTERIOR : Location::EXTERIOR;
+    }
+
+    if (! isKnown(isA)) {
+        setDimension(isA, dim);
+        setOn(isA, locEdge);
+        setLeft(isA, locLeft);
+        setRight(isA, locRight);
+        return;
+    }
+
+    // Assert: node-dirpt is collinear with node-pt
+    mergeDimEdgeLoc(isA, locEdge);
+    mergeSideLocation(isA, Position::LEFT, locLeft);
+    mergeSideLocation(isA, Position::RIGHT, locRight);
+}
+
+/**
+* Area edges override Line edges.
+* Merging edges of same dimension is a no-op for
+* the dimension and on location.
+* But merging an area edge into a line edge
+* sets the dimension to A and the location to BOUNDARY.
+*
+* @param isA
+* @param locEdge
+*/
+/* private */
+void
+RelateEdge::mergeDimEdgeLoc(bool isA, Location locEdge)
+{
+    //TODO: this logic needs work - ie handling A edges marked as Interior
+    int dim = (locEdge == Location::BOUNDARY) ? Dimension::A : Dimension::L;
+    if (dim == Dimension::A && dimension(isA) == Dimension::L) {
+        setDimension(isA, dim);
+        setOn(isA, Location::BOUNDARY);
+    }
+}
+
+
+/* private */
+void
+RelateEdge::mergeSideLocation(bool isA, int pos, Location loc)
+{
+    Location currLoc = location(isA, pos);
+    //-- INTERIOR takes precedence over EXTERIOR
+    if (currLoc != Location::INTERIOR) {
+        setLocation(isA, pos, loc);
+    }
+}
+
+
+/* private */
+void
+RelateEdge::setDimension(bool isA, int dimension)
+{
+    if (isA) {
+        aDim = dimension;
+    }
+    else {
+        bDim = dimension;
+    }
+}
+
+
+/* public */
+void
+RelateEdge::setLocation(bool isA, int pos, Location loc)
+{
+    switch (pos) {
+    case Position::LEFT:
+        setLeft(isA, loc);
+        break;
+    case Position::RIGHT:
+        setRight(isA, loc);
+        break;
+    case Position::ON:
+        setOn(isA, loc);
+        break;
+    }
+}
+
+
+/* public */
+void
+RelateEdge::setAllLocations(bool isA, Location loc)
+{
+    setLeft(isA, loc);
+    setRight(isA, loc);
+    setOn(isA, loc);
+}
+
+
+/* public */
+void
+RelateEdge::setUnknownLocations(bool isA, Location loc)
+{
+    if (! isKnown(isA, Position::LEFT)) {
+        setLocation(isA, Position::LEFT, loc);
+    }
+    if (! isKnown(isA, Position::RIGHT)) {
+        setLocation(isA, Position::RIGHT, loc);
+    }
+    if (! isKnown(isA, Position::ON)) {
+        setLocation(isA, Position::ON, loc);
+    }
+}
+
+
+/* private */
+void
+RelateEdge::setLeft(bool isA, Location loc)
+{
+    if (isA) {
+        aLocLeft = loc;
+    }
+    else {
+        bLocLeft = loc;
+    }
+}
+
+
+/* private */
+void
+RelateEdge::setRight(bool isA, Location loc)
+{
+    if (isA) {
+        aLocRight = loc;
+    }
+    else {
+        bLocRight = loc;
+    }
+}
+
+
+/* private */
+void
+RelateEdge::setOn(bool isA, Location loc)
+{
+    if (isA) {
+        aLocLine = loc;
+    }
+    else {
+        bLocLine = loc;
+    }
+}
+
+
+/* public */
+Location
+RelateEdge::location(bool isA, int position) const
+{
+    if (isA) {
+        switch (position) {
+            case Position::LEFT: return aLocLeft;
+            case Position::RIGHT: return aLocRight;
+            case Position::ON: return aLocLine;
+        }
+    }
+    else {
+        switch (position) {
+            case Position::LEFT: return bLocLeft;
+            case Position::RIGHT: return bLocRight;
+            case Position::ON: return bLocLine;
+        }
+    }
+    assert(false && "never get here");
+    return LOC_UNKNOWN;
+}
+
+/* private */
+int
+RelateEdge::dimension(bool isA) const
+{
+    return isA ? aDim : bDim;
+}
+
+/* private */
+bool
+RelateEdge::isKnown(bool isA) const
+{
+    if (isA)
+        return aDim != DIM_UNKNOWN;
+    return bDim != DIM_UNKNOWN;
+}
+
+/* private */
+bool
+RelateEdge::isKnown(bool isA, int pos) const
+{
+    return location(isA, pos) != LOC_UNKNOWN;
+}
+
+
+/* public */
+bool
+RelateEdge::isInterior(bool isA, int position) const
+{
+    return location(isA, position) == Location::INTERIOR;
+}
+
+
+/* public */
+void
+RelateEdge::setDimLocations(bool isA, int dim, Location loc)
+{
+    if (isA) {
+        aDim = dim;
+        aLocLeft = loc;
+        aLocRight = loc;
+        aLocLine = loc;
+    }
+    else {
+        bDim = dim;
+        bLocLeft = loc;
+        bLocRight = loc;
+        bLocLine = loc;
+    }
+}
+
+
+/* public */
+void
+RelateEdge::setAreaInterior(bool isA)
+{
+    if (isA) {
+        aLocLeft = Location::INTERIOR;
+        aLocRight = Location::INTERIOR;
+        aLocLine = Location::INTERIOR;
+    }
+    else {
+        bLocLeft = Location::INTERIOR;
+        bLocRight = Location::INTERIOR;
+        bLocLine = Location::INTERIOR;
+    }
+}
+
+
+/* public */
+std::string
+RelateEdge::toString() const
+{
+    std::stringstream ss;
+    ss << WKTWriter::toLineString(*(node->getCoordinate()), *dirPt);
+    ss << " - " << labelString();
+    return ss.str();
+}
+
+
+/* private */
+std::string
+RelateEdge::labelString() const
+{
+    std::stringstream ss;
+    ss << "A:";
+    ss << locationString(RelateGeometry::GEOM_A);
+    ss << "/B:";
+    ss << locationString(RelateGeometry::GEOM_B);
+    return ss.str();
+}
+
+
+/* private */
+std::string
+RelateEdge::locationString(bool isA) const
+{
+    std::stringstream ss;
+    ss << location(isA, Position::LEFT);
+    ss << location(isA, Position::ON);
+    ss << location(isA, Position::RIGHT);
+    return ss.str();
+}
+
+
+/* public friend */
+std::ostream&
+operator<<(std::ostream& os, const RelateEdge& re)
+{
+    os << re.toString();
+    return os;
+}
+
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
+
+
+
+
diff --git a/src/operation/relateng/RelateNode.cpp b/src/operation/relateng/RelateNode.cpp
new file mode 100644
index 000000000..0ec3045a6
--- /dev/null
+++ b/src/operation/relateng/RelateNode.cpp
@@ -0,0 +1,323 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ * 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.
+ *
+ **********************************************************************/
+
+#include <geos/operation/relateng/RelateNode.h>
+#include <geos/operation/relateng/RelateEdge.h>
+#include <geos/operation/relateng/RelateGeometry.h>
+#include <geos/operation/relateng/NodeSection.h>
+#include <geos/geom/Dimension.h>
+#include <geos/geom/Position.h>
+#include <geos/geom/Coordinate.h>
+#include <geos/io/WKTWriter.h>
+#include <geos/constants.h>
+
+
+using geos::geom::CoordinateXY;
+using geos::geom::Geometry;
+using geos::geom::Dimension;
+using geos::geom::Position;
+using geos::io::WKTWriter;
+
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+
+/* public */
+const CoordinateXY*
+RelateNode::getCoordinate() const
+{
+    return nodePt;
+}
+
+
+/* public */
+const std::vector<std::unique_ptr<RelateEdge>>&
+RelateNode::getEdges() const
+{
+    return edges;
+}
+
+
+/* public */
+void
+RelateNode::addEdges(std::vector<const NodeSection *>& nss)
+{
+    for (auto* ns : nss) {
+        addEdges(ns);
+    }
+}
+
+/* public */
+void
+RelateNode::addEdges(std::vector<std::unique_ptr<NodeSection>>& nss)
+{
+    for (auto& ns : nss) {
+        addEdges(ns.get());
+    }
+}
+
+/* private */
+std::size_t
+RelateNode::indexOf(
+    const std::vector<std::unique_ptr<RelateEdge>>& vEdges,
+    const RelateEdge* edge) const
+{
+    for (std::size_t i = 0; i < vEdges.size(); i++)
+    {
+        const std::unique_ptr<RelateEdge>& e = vEdges[i];
+        if (e.get() == edge)
+            return i;
+    }
+    return INDEX_UNKNOWN;
+}
+
+
+/* public */
+void
+RelateNode::addEdges(const NodeSection* ns)
+{
+  //Debug.println("Adding NS: " + ns);
+    switch (ns->dimension()) {
+    case Dimension::L:
+        addLineEdge(ns->isA(), ns->getVertex(0));
+        addLineEdge(ns->isA(), ns->getVertex(1));
+        break;
+    case Dimension::A:
+        //-- assumes node edges have CW orientation (as per JTS norm)
+        //-- entering edge - interior on L
+        const RelateEdge* e0 = addAreaEdge(ns->isA(), ns->getVertex(0), false);
+        //-- exiting edge - interior on R
+        const RelateEdge* e1 = addAreaEdge(ns->isA(), ns->getVertex(1), true);
+
+        std::size_t index0 = indexOf(edges, e0);
+        std::size_t index1 = indexOf(edges, e1);
+        updateEdgesInArea(ns->isA(), index0, index1);
+        updateIfAreaPrev(ns->isA(), index0);
+        updateIfAreaNext(ns->isA(), index1);
+    }
+}
+
+
+/* private */
+void
+RelateNode::updateEdgesInArea(bool isA, std::size_t indexFrom, std::size_t indexTo)
+{
+    std::size_t index = nextIndex(edges, indexFrom);
+    while (index != indexTo) {
+        auto& edge = edges[index];
+        edge->setAreaInterior(isA);
+        index = nextIndex(edges, index);
+    }
+}
+
+
+/* private */
+void
+RelateNode::updateIfAreaPrev(bool isA, std::size_t index)
+{
+    std::size_t indexPrev = prevIndex(edges, index);
+    auto& edgePrev = edges[indexPrev];
+    if (edgePrev->isInterior(isA, Position::LEFT)) {
+        std::unique_ptr<RelateEdge>& edge = edges[index];
+        edge->setAreaInterior(isA);
+    }
+}
+
+
+/* private */
+void
+RelateNode::updateIfAreaNext(bool isA, std::size_t index)
+{
+    std::size_t indexNext = nextIndex(edges, index);
+    auto& edgeNext = edges[indexNext];
+    if (edgeNext->isInterior(isA, Position::RIGHT)) {
+        auto& edge = edges[index];
+        edge->setAreaInterior(isA);
+    }
+}
+
+
+/* private */
+const RelateEdge*
+RelateNode::addLineEdge(bool isA, const CoordinateXY* dirPt)
+{
+    return addEdge(isA, dirPt, Dimension::L, false);
+}
+
+
+/* private */
+const RelateEdge*
+RelateNode::addAreaEdge(bool isA, const CoordinateXY* dirPt, bool isForward)
+{
+    return addEdge(isA, dirPt, Dimension::A, isForward);
+}
+
+
+/* private */
+const RelateEdge*
+RelateNode::addEdge(bool isA, const CoordinateXY* dirPt, int dim, bool isForward)
+{
+    //-- check for well-formed edge - skip null or zero-len input
+    if (dirPt == nullptr)
+        return nullptr;
+    if (nodePt->equals2D(*dirPt))
+        return nullptr;
+
+    std::size_t insertIndex = INDEX_UNKNOWN;
+    for (std::size_t i = 0; i < edges.size(); i++) {
+        auto* e = edges[i].get();
+        int comp = e->compareToEdge(dirPt);
+        if (comp == 0) {
+            e->merge(isA, dim, isForward);
+            return e;
+        }
+        if (comp == 1) {
+            //-- found further edge, so insert a new edge at this position
+            insertIndex = i;
+            break;
+        }
+    }
+    //-- add a new edge
+    RelateEdge* e = RelateEdge::create(this, dirPt, isA, dim, isForward);
+    if (insertIndex == INDEX_UNKNOWN) {
+        //-- add edge at end of list
+        edges.emplace_back(e);
+    }
+    else {
+        //-- add edge before higher edge found
+        std::unique_ptr<RelateEdge> re(e);
+        edges.insert(
+            edges.begin() + static_cast<long>(insertIndex),
+            std::move(re));
+    }
+    return e;
+}
+
+
+/* public */
+void
+RelateNode::finish(bool isAreaInteriorA, bool isAreaInteriorB)
+{
+    //Debug.println("finish Node.");
+    //Debug.println("Before: " + this);
+    finishNode(RelateGeometry::GEOM_A, isAreaInteriorA);
+    finishNode(RelateGeometry::GEOM_B, isAreaInteriorB);
+    //Debug.println("After: " + this);
+}
+
+
+/* private */
+void
+RelateNode::finishNode(bool isA, bool isAreaInterior)
+{
+    if (isAreaInterior) {
+        RelateEdge::setAreaInterior(edges, isA);
+    }
+    else {
+        std::size_t startIndex = RelateEdge::findKnownEdgeIndex(edges, isA);
+        //-- only interacting nodes are finished, so this should never happen
+        //Assert.isTrue(startIndex >= 0l, "Node at "+ nodePt + "does not have AB interaction");
+        propagateSideLocations(isA, startIndex);
+    }
+}
+
+
+/* private */
+void
+RelateNode::propagateSideLocations(bool isA, std::size_t startIndex)
+{
+    Location currLoc = edges[startIndex]->location(isA, Position::LEFT);
+    //-- edges are stored in CCW order
+    std::size_t index = nextIndex(edges, startIndex);
+    while (index != startIndex) {
+        const auto& e = edges[index];
+        e->setUnknownLocations(isA, currLoc);
+        currLoc = e->location(isA, Position::LEFT);
+        index = nextIndex(edges, index);
+    }
+}
+
+
+/* private static */
+std::size_t
+RelateNode::prevIndex(
+    std::vector<std::unique_ptr<RelateEdge>>& list,
+    std::size_t index)
+{
+    if (index > 0 && index != INDEX_UNKNOWN) {
+        return index - 1;
+    }
+    //-- index == 0
+    return list.size() - 1;
+}
+
+
+/* private static */
+std::size_t
+RelateNode::nextIndex(
+    std::vector<std::unique_ptr<RelateEdge>>& list,
+    std::size_t index)
+{
+    if (index >= list.size() - 1 || index == INDEX_UNKNOWN) {
+        return 0;
+    }
+    return index + 1;
+}
+
+
+/* public */
+bool
+RelateNode::hasExteriorEdge(bool isA)
+{
+    for (auto& e : edges) {
+        if (Location::EXTERIOR == e->location(isA, Position::LEFT) ||
+            Location::EXTERIOR == e->location(isA, Position::RIGHT))
+        {
+            return true;
+        }
+    }
+    return false;
+}
+
+
+/* public */
+std::string
+RelateNode::toString() const
+{
+    std::stringstream ss;
+    ss << "Node[" << WKTWriter::toPoint(*nodePt) << "]:" << std::endl;
+    for (auto& e : edges) {
+        ss << e->toString() << std::endl;
+    }
+    return ss.str();
+}
+
+
+/* public friend */
+std::ostream&
+operator<<(std::ostream& os, const RelateNode& rn)
+{
+    os << rn.toString();
+    return os;
+}
+
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
+
+
diff --git a/src/operation/relateng/RelatePointLocator.cpp b/src/operation/relateng/RelatePointLocator.cpp
new file mode 100644
index 000000000..aa29b374e
--- /dev/null
+++ b/src/operation/relateng/RelatePointLocator.cpp
@@ -0,0 +1,330 @@
+/**********************************************************************
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.osgeo.org
+ *
+ * Copyright (c) 2024 Martin Davis
+ * Copyright (C) 2024 Paul Ramsey <pramsey at cleverelephant.ca>
+ *
+ * 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.
+ *
+ **********************************************************************/
+
+#include <geos/algorithm/PointLocation.h>
+#include <geos/algorithm/locate/IndexedPointInAreaLocator.h>
+#include <geos/algorithm/locate/PointOnGeometryLocator.h>
+#include <geos/algorithm/locate/SimplePointInAreaLocator.h>
+#include <geos/geom/Coordinate.h>
+#include <geos/geom/CoordinateSequence.h>
+#include <geos/geom/Geometry.h>
+#include <geos/geom/GeometryCollection.h>
+#include <geos/geom/LineString.h>
+#include <geos/geom/Location.h>
+#include <geos/geom/MultiPolygon.h>
+#include <geos/geom/Point.h>
+#include <geos/geom/Polygon.h>
+#include <geos/operation/relateng/AdjacentEdgeLocator.h>
+#include <geos/operation/relateng/DimensionLocation.h>
+#include <geos/operation/relateng/LinearBoundary.h>
+#include <geos/operation/relateng/RelatePointLocator.h>
+#include <geos/constants.h>
+
+
+using geos::algorithm::PointLocation;
+using geos::algorithm::locate::IndexedPointInAreaLocator;
+using geos::algorithm::locate::PointOnGeometryLocator;
+using geos::algorithm::locate::SimplePointInAreaLocator;
+using namespace geos::geom;
+
+
+namespace geos {      // geos
+namespace operation { // geos.operation
+namespace relateng {  // geos.operation.relateng
+
+
+/* private */
+void
+RelatePointLocator::init(const Geometry* p_geom)
+{
+    //-- cache empty status, since may be checked many times
+    isEmpty = p_geom->isEmpty();
+    extractElements(p_geom);
+
+    if (!lines.empty()) {
+        lineBoundary.reset(new LinearBoundary(lines, boundaryRule));
+    }
+
+    if (!polygons.empty()) {
+        polyLocator.resize(polygons.size());
+    }
+}
+
+
+/* public */
+bool
+RelatePointLocator::hasBoundary() const
+{
+    return lineBoundary->hasBoundary();
+}
+
+
+/* private */
+void
+RelatePointLocator::extractElements(const Geometry* p_geom)
+{
+    if (p_geom->isEmpty())
+        return;
+
+    if (p_geom->getGeometryTypeId() == GEOS_POINT) {
+        addPoint(static_cast<const Point*>(p_geom));
+    }
+    else if (p_geom->getGeometryTypeId() == GEOS_LINESTRING) {
+        addLine(static_cast<const LineString*>(p_geom));
+    }
+    else if (p_geom->getGeometryTypeId() == GEOS_POLYGON ||
+             p_geom->getGeometryTypeId() == GEOS_MULTIPOLYGON)
+    {
+        addPolygonal(p_geom);
+    }
+    else if (p_geom->isCollection()) {
+        for (std::size_t i = 0; i < p_geom->getNumGeometries(); i++) {
+            const Geometry* g = p_geom->getGeometryN(i);
+            extractElements(g);
+        }
+    }
+}
+
+
+/* private */
+void
+RelatePointLocator::addPoint(const Point* pt)
+{
+    points.insert(pt->getCoordinate());
+}
+
+
+/* private */
+void
+RelatePointLocator::addLine(const LineString* line)
+{
+    lines.push_back(line);
+}
+
+
+/* private */
+void
+RelatePointLocator::addPolygonal(const Geometry* polygonal)
+{
+    polygons.push_back(polygonal);
+}
+
+
+/* public */
+Location
+RelatePointLocator::locate(const CoordinateXY* p)
+{
+    return DimensionLocation::location(locateWithDim(p));
+}
+
+
+/* public */
+Location
+RelatePointLocator::locateLineEnd(const CoordinateXY* p) const
+{
+    return lineBoundary->isBoundary(p) ? Location::BOUNDARY : Location::INTERIOR;
+}
+
+
+/* public */
+Location
+RelatePointLocator::locateNode(const CoordinateXY* p, const Geometry* parentPolygonal)
+{
+    return DimensionLocation::location(locateNodeWithDim(p, parentPolygonal));
+}
+
+
+/* public */
+int
+RelatePointLocator::locateNodeWithDim(const CoordinateXY* p, const Geometry* parentPolygonal)
+{
+    return locateWithDim(p, true, parentPolygonal);
+}
+
+
+/* public */
+int
+RelatePointLocator::locateWithDim(const CoordinateXY* p)
+{
+    return locateWithDim(p, false, nullptr);
+}
+
+
+/* private */
+int
+RelatePointLocator::locateWithDim(const CoordinateXY* p, bool isNode, const Geometry* parentPolygonal)
+{
+    if (isEmpty) return DimensionLocation::EXTERIOR;
+
+    /**
+     * In a polygonal geometry a node must be on the boundary.
+     * (This is not the case for a mixed collection, since
+     * the node may be in the interior of a polygon.)
+     */
+    GeometryTypeId geomType = geom->getGeometryTypeId();
+    if (isNode && (geomType == GEOS_POLYGON || geomType == GEOS_MULTIPOLYGON))
+        return DimensionLocation::AREA_BOUNDARY;
+
+    int dimLoc = computeDimLocation(p, isNode, parentPolygonal);
+    return dimLoc;
+}
+
+
+/* private */
+int
+RelatePointLocator::computeDimLocation(const CoordinateXY* p, bool isNode, const Geometry* parentPolygonal)
+{
+    //-- check dimensions in order of precedence
+    if (!polygons.empty()) {
+        Location locPoly = locateOnPolygons(p, isNode, parentPolygonal);
+        if (locPoly != Location::EXTERIOR)
+            return DimensionLocation::locationArea(locPoly);
+    }
+    if (!lines.empty()) {
+        Location locLine = locateOnLines(p, isNode);
+        if (locLine != Location::EXTERIOR)
+            return DimensionLocation::locationLine(locLine);
+    }
+    if (!points.empty()) {
+        Location locPt = locateOnPoints(p);
+        if (locPt != Location::EXTERIOR)
+            return DimensionLocation::locationPoint(locPt);
+    }
+    return DimensionLocation::EXTERIOR;
+}
+
+
+/* private */
+Location
+RelatePointLocator::locateOnPoints(const CoordinateXY* p) const
+{
+    auto search = points.find(p);
+    if (search != points.end())
+        return Location::INTERIOR;
+    else
+        return Location::EXTERIOR;
+}
+
+
+/* private */
+Location
+RelatePointLocator::locateOnLines(const CoordinateXY* p, bool isNode)
+{
+    if (lineBoundary != nullptr && lineBoundary->isBoundary(p)) {
+        return Location::BOUNDARY;
+    }
+    //-- must be on line, in interior
+    if (isNode)
+        return Location::INTERIOR;
+
+    //TODO: index the lines
+    for (const LineString* line : lines) {
+        //-- have to check every line, since any/all may contain point
+        Location loc = locateOnLine(p, /*isNode,*/ line);
+        if (loc != Location::EXTERIOR)
+            return loc;
+        //TODO: minor optimization - some BoundaryNodeRules can short-circuit
+    }
+    return Location::EXTERIOR;
+}
+
+
+/* private */
+Location
+RelatePointLocator::locateOnLine(const CoordinateXY* p, /*bool isNode,*/ const LineString* l)
+{
+    // bounding-box check
+    if (! l->getEnvelopeInternal()->intersects(*p))
+        return Location::EXTERIOR;
+
+    const CoordinateSequence* seq = l->getCoordinatesRO();
+    if (PointLocation::isOnLine(*p, seq)) {
+        return Location::INTERIOR;
+    }
+    return Location::EXTERIOR;
+}
+
+
+/* private */
+Location
+RelatePointLocator::locateOnPolygons(const CoordinateXY* p, bool isNode, const Geometry* parentPolygonal)
+{
+    int numBdy = 0;
+    //TODO: use a spatial index on the polygons
+    for (std::size_t i = 0; i < polygons.size(); i++) {
+        Location loc = locateOnPolygonal(p, isNode, parentPolygonal, i);
+        if (loc == Location::INTERIOR) {
+            return Location::INTERIOR;
+        }
+        if (loc == Location::BOUNDARY) {
+            numBdy += 1;
+        }
+    }
+    if (numBdy == 1) {
+        return Location::BOUNDARY;
+    }
+    //-- check for point lying on adjacent boundaries
+    else if (numBdy > 1) {
+        if (adjEdgeLocator == nullptr) {
+            adjEdgeLocator.reset(new AdjacentEdgeLocator(geom));
+        }
+        return adjEdgeLocator->locate(p);
+    }
+    return Location::EXTERIOR;
+}
+
+
+/* private */
+Location
+RelatePointLocator::locateOnPolygonal(const CoordinateXY* p,
+    bool isNode,
+    const Geometry* parentPolygonal,
+    std::size_t index)
+{
+    const Geometry* polygonal = polygons[index];
+    if (isNode && parentPolygonal == polygonal) {
+        return Location::BOUNDARY;
+    }
+    PointOnGeometryLocator* locator = getLocator(index);
+    return locator->locate(p);
+}
+
+
+/* private */
+PointOnGeometryLocator *
+RelatePointLocator::getLocator(std::size_t index)
+{
+    std::unique_ptr<PointOnGeometryLocator>& locator = polyLocator[index];
+    if (locator == nullptr) {
+        const Geometry* polygonal = polygons[index];
+        if (isPrepared) {
+            locator.reset(new IndexedPointInAreaLocator(*polygonal));
+        }
+        else {
+            locator.reset(new SimplePointInAreaLocator(*polygonal));
+        }
+    }
+    return locator.get();
+}
+
+
+
+} // namespace geos.operation.overlayng
+} // namespace geos.operation
+} // namespace geos
+
+
+
+
diff --git a/tests/unit/operation/relateng/AdjacentEdgeLocatorTest.cpp b/tests/unit/operation/relateng/AdjacentEdgeLocatorTest.cpp
new file mode 100644
index 000000000..8521954b5
--- /dev/null
+++ b/tests/unit/operation/relateng/AdjacentEdgeLocatorTest.cpp
@@ -0,0 +1,129 @@
+//
+// Test Suite for geos::operation::relateng::AdjacentEdgeLocator class.
+
+#include <tut/tut.hpp>
+#include <utility.h>
+
+// geos
+#include <geos/io/WKTReader.h>
+#include <geos/io/WKTWriter.h>
+#include <geos/geom/Geometry.h>
+#include <geos/geom/Location.h>
+#include <geos/operation/relateng/AdjacentEdgeLocator.h>
+
+// std
+#include <memory>
+
+using namespace geos::geom;
+using namespace geos::operation::relateng;
+using geos::io::WKTReader;
+// using geos::io::WKTWriter;
+
+namespace tut {
+//
+// Test Group
+//
+
+// Common data used by all tests
+struct test_adjacentedgelocator_data {
+
+    WKTReader r;
+    // WKTWriter w;
+
+    void
+    checkLocation(const std::string& wkt, int x, int y, Location expectedLoc)
+    {
+        std::unique_ptr<Geometry> geom = r.read(wkt);
+        AdjacentEdgeLocator ael(geom.get());
+        Coordinate c(x, y);
+        Location loc = ael.locate(&c);
+        ensure_equals("Locations are not equal ", expectedLoc, loc);
+    }
+};
+
+typedef test_group<test_adjacentedgelocator_data> group;
+typedef group::object object;
+
+group test_adjacentedgelocator_group("geos::operation::relateng::AdjacentEdgeLocator");
+
+//
+// Test Cases
+//
+
+// testAdjacent2
+template<>
+template<>
+void object::test<1> ()
+{
+    checkLocation(
+        "GEOMETRYCOLLECTION (POLYGON ((1 9, 5 9, 5 1, 1 1, 1 9)), POLYGON ((9 9, 9 1, 5 1, 5 9, 9 9)))",
+        5, 5, Location::INTERIOR
+        );
+}
+
+// testNonAdjacent
+template<>
+template<>
+void object::test<2> ()
+{
+    checkLocation(
+        "GEOMETRYCOLLECTION (POLYGON ((1 9, 4 9, 5 1, 1 1, 1 9)), POLYGON ((9 9, 9 1, 5 1, 5 9, 9 9)))",
+        5, 5, Location::BOUNDARY
+        );
+}
+
+// testAdjacent6WithFilledHoles
+template<>
+template<>
+void object::test<3> ()
+{
+    checkLocation(
+        "GEOMETRYCOLLECTION (POLYGON ((1 9, 5 9, 6 6, 1 5, 1 9), (2 6, 4 8, 6 6, 2 6)), POLYGON ((2 6, 4 8, 6 6, 2 6)), POLYGON ((9 9, 9 5, 6 6, 5 9, 9 9)), POLYGON ((9 1, 5 1, 6 6, 9 5, 9 1), (7 2, 6 6, 8 3, 7 2)), POLYGON ((7 2, 6 6, 8 3, 7 2)), POLYGON ((1 1, 1 5, 6 6, 5 1, 1 1)))",
+        6, 6, Location::INTERIOR
+        );
+}
+
+// testAdjacent5WithEmptyHole
+template<>
+template<>
+void object::test<4> ()
+{
+    checkLocation(
+        "GEOMETRYCOLLECTION (POLYGON ((1 9, 5 9, 6 6, 1 5, 1 9), (2 6, 4 8, 6 6, 2 6)), POLYGON ((2 6, 4 8, 6 6, 2 6)), POLYGON ((9 9, 9 5, 6 6, 5 9, 9 9)), POLYGON ((9 1, 5 1, 6 6, 9 5, 9 1), (7 2, 6 6, 8 3, 7 2)), POLYGON ((1 1, 1 5, 6 6, 5 1, 1 1)))",
+        6, 6, Location::BOUNDARY
+        );
+}
+
+// testContainedAndAdjacent
+template<>
+template<>
+void object::test<5> ()
+{
+    std::string wkt("GEOMETRYCOLLECTION (POLYGON ((1 9, 9 9, 9 1, 1 1, 1 9)), POLYGON ((9 2, 2 2, 2 8, 9 8, 9 2)))");
+    checkLocation(wkt,
+        9, 5, Location::BOUNDARY
+        );
+    checkLocation(wkt,
+        9, 8, Location::BOUNDARY
+        );
+}
+
+/**
+* Tests a bug caused by incorrect point-on-segment logic.
+*/
+// testDisjointCollineartemplate<>
+template<>
+template<>
+void object::test<6> ()
+{
+    checkLocation(
+        "GEOMETRYCOLLECTION (MULTIPOLYGON (((1 4, 4 4, 4 1, 1 1, 1 4)), ((5 4, 8 4, 8 1, 5 1, 5 4))))",
+        2, 4, Location::BOUNDARY
+        );
+}
+
+
+
+
+
+} // namespace tut
diff --git a/tests/unit/operation/relateng/PolygonNodeConverterTest.cpp b/tests/unit/operation/relateng/PolygonNodeConverterTest.cpp
new file mode 100644
index 000000000..4a4899f70
--- /dev/null
+++ b/tests/unit/operation/relateng/PolygonNodeConverterTest.cpp
@@ -0,0 +1,172 @@
+//
+// Test Suite for geos::operation::relateng::PolygonNodeConverter class.
+
+#include <tut/tut.hpp>
+#include <utility.h>
+
+// geos
+#include <geos/io/WKTReader.h>
+#include <geos/io/WKTWriter.h>
+#include <geos/geom/Geometry.h>
+#include <geos/geom/Location.h>
+#include <geos/operation/relateng/PolygonNodeConverter.h>
+#include <geos/algorithm/PolygonNodeTopology.h>
+
+
+// std
+#include <memory>
+
+using namespace geos::geom;
+using namespace geos::operation::relateng;
+using geos::algorithm::PolygonNodeTopology;
+using geos::io::WKTReader;
+// using geos::io::WKTWriter;
+
+namespace tut {
+//
+// Test Group
+//
+
+// Common data used by all tests
+struct test_polygonnodeconverter_data {
+
+    WKTReader r;
+    // WKTWriter w;
+
+    std::unique_ptr<CoordinateSequence>
+    readPts(const std::string& wkt)
+    {
+        std::unique_ptr<Geometry> line = r.read(wkt);
+        return line->getCoordinates();
+    }
+
+    void
+    checkCrossing(const std::string& wktA, const std::string& wktB)
+    {
+        checkCrossing(wktA, wktB, true);
+    }
+
+    void
+    checkNonCrossing(const std::string& wktA, const std::string& wktB)
+    {
+        checkCrossing(wktA, wktB, false);
+    }
+
+    void
+    checkCrossing(const std::string& wktA, const std::string& wktB, bool isExpected)
+    {
+        auto a = readPts(wktA);
+        auto b = readPts(wktB);
+        // assert: a[1] = b[1]
+        bool isCrossing = PolygonNodeTopology::isCrossing(
+            &(a->getAt<CoordinateXY>(1)),
+            &(a->getAt<CoordinateXY>(0)),
+            &(a->getAt<CoordinateXY>(2)),
+            &(b->getAt<CoordinateXY>(0)),
+            &(b->getAt<CoordinateXY>(2)));
+        ensure("checkCrossing", isCrossing == isExpected);
+    }
+
+    void
+    checkInterior(const std::string& wktA, const std::string& wktB)
+    {
+        checkInteriorSegment(wktA, wktB, true);
+    }
+
+    void
+    checkExterior(const std::string& wktA, const std::string& wktB)
+    {
+        checkInteriorSegment(wktA, wktB, false);
+    }
+
+    void
+    checkInteriorSegment(const std::string& wktA, const std::string& wktB, bool isExpected)
+    {
+        auto a = readPts(wktA);
+        auto b = readPts(wktB);
+        // assert: a[1] = b[1]
+        bool isInterior = PolygonNodeTopology::isInteriorSegment(
+            &(a->getAt<CoordinateXY>(1)),
+            &(a->getAt<CoordinateXY>(0)),
+            &(a->getAt<CoordinateXY>(2)),
+            &(b->getAt<CoordinateXY>(1)));
+        ensure("checkInteriorSegment", isInterior == isExpected);
+    }
+
+};
+
+typedef test_group<test_polygonnodeconverter_data> group;
+typedef group::object object;
+
+group test_polygonnodeconverter_group("geos::operation::relateng::PolygonNodeConverter");
+
+//
+// Test Cases
+//
+
+
+// testNonCrossing
+template<>
+template<>
+void object::test<1> ()
+{
+    checkCrossing("LINESTRING (500 1000, 1000 1000, 1000 1500)",
+        "LINESTRING (1000 500, 1000 1000, 500 1500)");
+}
+
+// testNonCrossingQuadrant2
+template<>
+template<>
+void object::test<2> ()
+{
+    checkNonCrossing("LINESTRING (500 1000, 1000 1000, 1000 1500)",
+        "LINESTRING (300 1200, 1000 1000, 500 1500)");
+}
+
+// testNonCrossingQuadrant4
+template<>
+template<>
+void object::test<3> ()
+{
+    checkNonCrossing("LINESTRING (500 1000, 1000 1000, 1000 1500)",
+        "LINESTRING (1000 500, 1000 1000, 1500 1000)");
+}
+
+// testNonCrossingCollinear
+template<>
+template<>
+void object::test<4> ()
+{
+    checkNonCrossing("LINESTRING (3 1, 5 5, 9 9)",
+        "LINESTRING (2 1, 5 5, 9 9)");
+}
+
+// testNonCrossingBothCollinear
+template<>
+template<>
+void object::test<5> ()
+{
+    checkNonCrossing("LINESTRING (3 1, 5 5, 9 9)",
+        "LINESTRING (3 1, 5 5, 9 9)");
+}
+
+// testInteriorSegment
+template<>
+template<>
+void object::test<6> ()
+{
+    checkInterior("LINESTRING (5 9, 5 5, 9 5)",
+        "LINESTRING (5 5, 0 0)");
+}
+
+// testExteriorSegment
+template<>
+template<>
+void object::test<7> ()
+{
+    checkExterior("LINESTRING (5 9, 5 5, 9 5)",
+        "LINESTRING (5 5, 9 9)");
+}
+
+
+} // namespace tut
diff --git a/tests/unit/operation/relateng/RelatePointLocatorTest.cpp b/tests/unit/operation/relateng/RelatePointLocatorTest.cpp
new file mode 100644
index 000000000..5947216bf
--- /dev/null
+++ b/tests/unit/operation/relateng/RelatePointLocatorTest.cpp
@@ -0,0 +1,149 @@
+//
+// Test Suite for geos::operation::relateng::RelatePointLocator class.
+
+#include <tut/tut.hpp>
+#include <utility.h>
+
+// geos
+#include <geos/io/WKTReader.h>
+#include <geos/io/WKTWriter.h>
+#include <geos/geom/Geometry.h>
+#include <geos/geom/Location.h>
+#include <geos/operation/relateng/RelatePointLocator.h>
+#include <geos/operation/relateng/DimensionLocation.h>
+
+// std
+#include <memory>
+
+using namespace geos::geom;
+using namespace geos::operation::relateng;
+using geos::io::WKTReader;
+// using geos::io::WKTWriter;
+
+namespace tut {
+//
+// Test Group
+//
+
+// Common data used by all tests
+struct test_relatepointlocator_data {
+
+    WKTReader r;
+    // WKTWriter w;
+
+    std::string gcPLA =
+        "GEOMETRYCOLLECTION (POINT (1 1), POINT (2 1), LINESTRING (3 1, 3 9), LINESTRING (4 1, 5 4, 7 1, 4 1), LINESTRING (12 12, 14 14), POLYGON ((6 5, 6 9, 9 9, 9 5, 6 5)), POLYGON ((10 10, 10 16, 16 16, 16 10, 10 10)), POLYGON ((11 11, 11 17, 17 17, 17 11, 11 11)), POLYGON ((12 12, 12 16, 16 16, 16 12, 12 12)))";
+
+    // void
+    // checkLocation(const std::string& wkt, int x, int y, Location expectedLoc)
+    // {
+    //     std::unique_ptr<Geometry> geom = r.read(wkt);
+    //     RelatePointLocator ael(geom.get());
+    //     Coordinate c(x, y);
+    //     Location loc = ael.locate(&c);
+    //     ensure_equals("Locations are not equal ", expectedLoc, loc);
+    // }
+
+
+    void checkDimLocation(const std::string& wkt, double i, double j, int expected)
+    {
+        std::unique_ptr<Geometry> geom = r.read(wkt);
+        RelatePointLocator locator(geom.get());
+        CoordinateXY c(i, j);
+        int actual = locator.locateWithDim(&c);
+        ensure_equals("checkLocation", expected, actual);
+    }
+
+    void checkNodeLocation(const std::string& wkt, double i, double j, Location expected)
+    {
+        std::unique_ptr<Geometry> geom = r.read(wkt);
+        RelatePointLocator locator(geom.get());
+        CoordinateXY c(i, j);
+        Location actual = locator.locateNode(&c, nullptr);
+        ensure_equals("checkNodeLocation", expected, actual);
+    }
+
+};
+
+typedef test_group<test_relatepointlocator_data> group;
+typedef group::object object;
+
+group test_relatepointlocator_group("geos::operation::relateng::RelatePointLocator");
+
+
+// testPoint
+template<>
+template<>
+void object::test<1> ()
+{
+    //std::string wkt("GEOMETRYCOLLECTION (POINT(0 0), POINT(1 1))");
+    checkDimLocation(gcPLA, 1, 1, DimensionLocation::POINT_INTERIOR);
+    checkDimLocation(gcPLA, 0, 1, DimensionLocation::EXTERIOR);
+}
+
+// testPointInLine
+template<>
+template<>
+void object::test<2> ()
+{
+    checkDimLocation(gcPLA, 3, 8, DimensionLocation::LINE_INTERIOR);
+}
+
+// testPointInArea
+template<>
+template<>
+void object::test<3> ()
+{
+    checkDimLocation(gcPLA, 8, 8, DimensionLocation::AREA_INTERIOR);
+}
+
+// testLine
+template<>
+template<>
+void object::test<4> ()
+{
+    checkDimLocation(gcPLA, 3, 3, DimensionLocation::LINE_INTERIOR);
+    checkDimLocation(gcPLA, 3, 1, DimensionLocation::LINE_BOUNDARY);
+}
+
+// testLineInArea
+template<>
+template<>
+void object::test<5> ()
+{
+    checkDimLocation(gcPLA, 11, 11, DimensionLocation::AREA_INTERIOR);
+    checkDimLocation(gcPLA, 14, 14, DimensionLocation::AREA_INTERIOR);
+}
+
+// testArea
+template<>
+template<>
+void object::test<6> ()
+{
+    checkDimLocation(gcPLA, 8, 8, DimensionLocation::AREA_INTERIOR);
+    checkDimLocation(gcPLA, 9, 9, DimensionLocation::AREA_BOUNDARY);
+}
+
+// testAreaInArea
+template<>
+template<>
+void object::test<7> ()
+{
+    checkDimLocation(gcPLA, 11, 11, DimensionLocation::AREA_INTERIOR);
+    checkDimLocation(gcPLA, 12, 12, DimensionLocation::AREA_INTERIOR);
+    checkDimLocation(gcPLA, 10, 10, DimensionLocation::AREA_BOUNDARY);
+    checkDimLocation(gcPLA, 16, 16, DimensionLocation::AREA_INTERIOR);
+}
+
+// testLineNode
+template<>
+template<>
+void object::test<8> ()
+{
+    //checkNodeLocation(gcPLA, 12.1, 12.2, Location::INTERIOR);
+    checkNodeLocation(gcPLA, 3, 1, Location::BOUNDARY);
+}
+
+
+
+} // namespace tut

-----------------------------------------------------------------------

Summary of changes:
 include/geos/algorithm/PolygonNodeTopology.h       |  15 +
 include/geos/constants.h                           |   1 +
 include/geos/geom/Coordinate.h                     |   5 +
 include/geos/io/WKTWriter.h                        |   2 +-
 .../geos/operation/relateng/AdjacentEdgeLocator.h  | 124 ++++++
 .../geos/operation/relateng/DimensionLocation.h    |  60 +++
 include/geos/operation/relateng/LinearBoundary.h   |  88 ++++
 include/geos/operation/relateng/NodeSection.h      | 163 +++++++
 include/geos/operation/relateng/NodeSections.h     | 102 +++++
 .../geos/operation/relateng/PolygonNodeConverter.h | 112 +++++
 include/geos/operation/relateng/RelateEdge.h       | 176 ++++++++
 .../RelateGeometry.h}                              |  41 +-
 include/geos/operation/relateng/RelateNode.h       | 146 +++++++
 .../geos/operation/relateng/RelatePointLocator.h   | 213 +++++++++
 include/geos/operation/valid/IsSimpleOp.h          |   6 +-
 src/algorithm/PolygonNodeTopology.cpp              |  31 ++
 src/io/WKTWriter.cpp                               |   8 +-
 src/operation/relateng/AdjacentEdgeLocator.cpp     | 159 +++++++
 src/operation/relateng/DimensionLocation.cpp       | 129 ++++++
 src/operation/relateng/LinearBoundary.cpp          | 116 +++++
 src/operation/relateng/NodeSection.cpp             | 243 +++++++++++
 src/operation/relateng/NodeSections.cpp            | 163 +++++++
 src/operation/relateng/PolygonNodeConverter.cpp    | 192 +++++++++
 src/operation/relateng/RelateEdge.cpp              | 476 +++++++++++++++++++++
 src/operation/relateng/RelateNode.cpp              | 323 ++++++++++++++
 src/operation/relateng/RelatePointLocator.cpp      | 330 ++++++++++++++
 .../operation/relateng/AdjacentEdgeLocatorTest.cpp | 129 ++++++
 .../relateng/PolygonNodeConverterTest.cpp          | 172 ++++++++
 .../operation/relateng/RelatePointLocatorTest.cpp  | 149 +++++++
 29 files changed, 3840 insertions(+), 34 deletions(-)
 create mode 100644 include/geos/operation/relateng/AdjacentEdgeLocator.h
 create mode 100644 include/geos/operation/relateng/DimensionLocation.h
 create mode 100644 include/geos/operation/relateng/LinearBoundary.h
 create mode 100644 include/geos/operation/relateng/NodeSection.h
 create mode 100644 include/geos/operation/relateng/NodeSections.h
 create mode 100644 include/geos/operation/relateng/PolygonNodeConverter.h
 create mode 100644 include/geos/operation/relateng/RelateEdge.h
 copy include/geos/operation/{valid/PolygonRingTouch.h => relateng/RelateGeometry.h} (52%)
 create mode 100644 include/geos/operation/relateng/RelateNode.h
 create mode 100644 include/geos/operation/relateng/RelatePointLocator.h
 create mode 100644 src/operation/relateng/AdjacentEdgeLocator.cpp
 create mode 100644 src/operation/relateng/DimensionLocation.cpp
 create mode 100644 src/operation/relateng/LinearBoundary.cpp
 create mode 100644 src/operation/relateng/NodeSection.cpp
 create mode 100644 src/operation/relateng/NodeSections.cpp
 create mode 100644 src/operation/relateng/PolygonNodeConverter.cpp
 create mode 100644 src/operation/relateng/RelateEdge.cpp
 create mode 100644 src/operation/relateng/RelateNode.cpp
 create mode 100644 src/operation/relateng/RelatePointLocator.cpp
 create mode 100644 tests/unit/operation/relateng/AdjacentEdgeLocatorTest.cpp
 create mode 100644 tests/unit/operation/relateng/PolygonNodeConverterTest.cpp
 create mode 100644 tests/unit/operation/relateng/RelatePointLocatorTest.cpp


hooks/post-receive
-- 
GEOS


More information about the geos-commits mailing list