/************************************************************************
 *
 *
 * C-Wrapper for GEOS library
 *
 * Copyright (C) 2010-2012 Sandro Santilli <strk@kbt.io>
 * Copyright (C) 2005-2006 Refractions Research Inc.
 *
 * This is free software; you can redistribute and/or modify it under
 * the terms of the GNU Lesser General Public Licence as published
 * by the Free Software Foundation.
 * See the COPYING file for more information.
 *
 * Author: Sandro Santilli <strk@kbt.io>
 * Thread Safety modifications: Chuck Thibert <charles.thibert@ingres.com>
 *
 ***********************************************************************/

#include <geos/geom/Coordinate.h>
#include <geos/geom/Geometry.h>
#include <geos/geom/prep/PreparedGeometry.h>
#include <geos/geom/prep/PreparedGeometryFactory.h>
#include <geos/geom/GeometryCollection.h>
#include <geos/geom/Polygon.h>
#include <geos/geom/Point.h>
#include <geos/geom/MultiPoint.h>
#include <geos/geom/MultiLineString.h>
#include <geos/geom/MultiPolygon.h>
#include <geos/geom/LinearRing.h>
#include <geos/geom/LineSegment.h>
#include <geos/geom/LineString.h>
#include <geos/geom/PrecisionModel.h>
#include <geos/geom/GeometryFactory.h>
#include <geos/geom/CoordinateSequenceFactory.h>
#include <geos/geom/FixedSizeCoordinateSequence.h>
#include <geos/geom/Coordinate.h>
#include <geos/geom/IntersectionMatrix.h>
#include <geos/geom/Envelope.h>
#include <geos/index/strtree/STRtree.h>
#include <geos/index/strtree/GeometryItemDistance.h>
#include <geos/index/ItemVisitor.h>
#include <geos/io/WKTReader.h>
#include <geos/io/WKBReader.h>
#include <geos/io/WKTWriter.h>
#include <geos/io/WKBWriter.h>
#include <geos/algorithm/BoundaryNodeRule.h>
#include <geos/algorithm/MinimumBoundingCircle.h>
#include <geos/algorithm/MinimumDiameter.h>
#include <geos/algorithm/Orientation.h>
#include <geos/algorithm/distance/DiscreteHausdorffDistance.h>
#include <geos/algorithm/distance/DiscreteFrechetDistance.h>
#include <geos/simplify/DouglasPeuckerSimplifier.h>
#include <geos/simplify/TopologyPreservingSimplifier.h>
#include <geos/noding/GeometryNoder.h>
#include <geos/noding/Noder.h>
#include <geos/operation/buffer/BufferBuilder.h>
#include <geos/operation/buffer/BufferOp.h>
#include <geos/operation/buffer/BufferParameters.h>
#include <geos/operation/distance/DistanceOp.h>
#include <geos/operation/distance/IndexedFacetDistance.h>
#include <geos/operation/linemerge/LineMerger.h>
#include <geos/operation/overlay/OverlayOp.h>
#include <geos/operation/overlay/snap/GeometrySnapper.h>
#include <geos/operation/intersection/Rectangle.h>
#include <geos/operation/intersection/RectangleIntersection.h>
#include <geos/operation/polygonize/Polygonizer.h>
#include <geos/operation/polygonize/BuildArea.h>
#include <geos/operation/relate/RelateOp.h>
#include <geos/operation/sharedpaths/SharedPathsOp.h>
#include <geos/operation/union/CascadedPolygonUnion.h>
#include <geos/operation/union/CoverageUnion.h>
#include <geos/operation/valid/IsValidOp.h>
#include <geos/operation/valid/MakeValid.h>
#include <geos/precision/GeometryPrecisionReducer.h>
#include <geos/linearref/LengthIndexedLine.h>
#include <geos/triangulate/DelaunayTriangulationBuilder.h>
#include <geos/triangulate/VoronoiDiagramBuilder.h>
#include <geos/util.h>
#include <geos/util/IllegalArgumentException.h>
#include <geos/util/Interrupt.h>
#include <geos/util/UniqueCoordinateArrayFilter.h>
#include <geos/util/Machine.h>
#include <geos/version.h>

// This should go away
#include <cmath> // finite
#include <cstdarg>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <memory>

#ifdef _MSC_VER
#pragma warning(disable : 4099)
#endif

// Some extra magic to make type declarations in geos_c.h work -
// for cross-checking of types in header.
#define GEOSGeometry geos::geom::Geometry
#define GEOSPreparedGeometry geos::geom::prep::PreparedGeometry
#define GEOSCoordSequence geos::geom::CoordinateSequence
#define GEOSBufferParams geos::operation::buffer::BufferParameters
#define GEOSSTRtree geos::index::strtree::STRtree
#define GEOSWKTReader_t geos::io::WKTReader
#define GEOSWKTWriter_t geos::io::WKTWriter
#define GEOSWKBReader_t geos::io::WKBReader
#define GEOSWKBWriter_t geos::io::WKBWriter

#include "geos_c.h"

// Intentional, to allow non-standard C elements like C99 functions to be
// imported through C++ headers of C library, like <cmath>.
using namespace std;

/// Define this if you want operations triggering Exceptions to
/// be printed.
/// (will use the NOTIFY channel - only implemented for GEOSUnion so far)
///
#undef VERBOSE_EXCEPTIONS

#include <geos/export.h>
#include <geos/precision/MinimumClearance.h>


// import the most frequently used definitions globally
using geos::geom::Geometry;
using geos::geom::LineString;
using geos::geom::LinearRing;
using geos::geom::MultiLineString;
using geos::geom::MultiPolygon;
using geos::geom::Polygon;
using geos::geom::CoordinateSequence;
using geos::geom::GeometryCollection;
using geos::geom::GeometryFactory;

using geos::io::WKTReader;
using geos::io::WKTWriter;
using geos::io::WKBReader;
using geos::io::WKBWriter;

using geos::operation::overlay::OverlayOp;
using geos::operation::overlay::overlayOp;
using geos::operation::geounion::CascadedPolygonUnion;
using geos::operation::distance::IndexedFacetDistance;
using geos::operation::buffer::BufferParameters;
using geos::operation::buffer::BufferBuilder;
using geos::precision::GeometryPrecisionReducer;
using geos::util::IllegalArgumentException;
using geos::algorithm::distance::DiscreteHausdorffDistance;
using geos::algorithm::distance::DiscreteFrechetDistance;

typedef std::unique_ptr<Geometry> GeomPtr;

typedef struct GEOSContextHandle_HS {
    const GeometryFactory* geomFactory;
    char msgBuffer[1024];
    GEOSMessageHandler noticeMessageOld;
    GEOSMessageHandler_r noticeMessageNew;
    void* noticeData;
    GEOSMessageHandler errorMessageOld;
    GEOSMessageHandler_r errorMessageNew;
    void* errorData;
    int WKBOutputDims;
    int WKBByteOrder;
    int initialized;

    GEOSContextHandle_HS()
        :
        geomFactory(0),
        noticeMessageOld(0),
        noticeMessageNew(0),
        noticeData(0),
        errorMessageOld(0),
        errorMessageNew(0),
        errorData(0)
    {
        memset(msgBuffer, 0, sizeof(msgBuffer));
        geomFactory = GeometryFactory::getDefaultInstance();
        WKBOutputDims = 2;
        WKBByteOrder = getMachineByteOrder();
        setNoticeHandler(NULL);
        setErrorHandler(NULL);
        initialized = 1;
    }

    GEOSMessageHandler
    setNoticeHandler(GEOSMessageHandler nf)
    {
        GEOSMessageHandler f = noticeMessageOld;
        noticeMessageOld = nf;
        noticeMessageNew = NULL;
        noticeData = NULL;

        return f;
    }

    GEOSMessageHandler
    setErrorHandler(GEOSMessageHandler nf)
    {
        GEOSMessageHandler f = errorMessageOld;
        errorMessageOld = nf;
        errorMessageNew = NULL;
        errorData = NULL;

        return f;
    }

    GEOSMessageHandler_r
    setNoticeHandler(GEOSMessageHandler_r nf, void* userData)
    {
        GEOSMessageHandler_r f = noticeMessageNew;
        noticeMessageOld = NULL;
        noticeMessageNew = nf;
        noticeData = userData;

        return f;
    }

    GEOSMessageHandler_r
    setErrorHandler(GEOSMessageHandler_r ef, void* userData)
    {
        GEOSMessageHandler_r f = errorMessageNew;
        errorMessageOld = NULL;
        errorMessageNew = ef;
        errorData = userData;

        return f;
    }

    void
    NOTICE_MESSAGE(string fmt, ...)
    {
        if(NULL == noticeMessageOld && NULL == noticeMessageNew) {
            return;
        }

        va_list args;
        va_start(args, fmt);
        int result = vsnprintf(msgBuffer, sizeof(msgBuffer) - 1, fmt.c_str(), args);
        va_end(args);

        if(result > 0) {
            if(noticeMessageOld) {
                noticeMessageOld("%s", msgBuffer);
            }
            else {
                noticeMessageNew(msgBuffer, noticeData);
            }
        }
    }

    void
    ERROR_MESSAGE(string fmt, ...)
    {
        if(NULL == errorMessageOld && NULL == errorMessageNew) {
            return;
        }

        va_list args;
        va_start(args, fmt);
        int result = vsnprintf(msgBuffer, sizeof(msgBuffer) - 1, fmt.c_str(), args);
        va_end(args);

        if(result > 0) {
            if(errorMessageOld) {
                errorMessageOld("%s", msgBuffer);
            }
            else {
                errorMessageNew(msgBuffer, errorData);
            }
        }
    }
} GEOSContextHandleInternal_t;

// CAPI_ItemVisitor is used internally by the CAPI STRtree
// wrappers. It's defined here just to keep it out of the
// extern "C" block.
class CAPI_ItemVisitor : public geos::index::ItemVisitor {
    GEOSQueryCallback callback;
    void* userdata;
public:
    CAPI_ItemVisitor(GEOSQueryCallback cb, void* ud)
        : ItemVisitor(), callback(cb), userdata(ud) {}
    void
    visitItem(void* item) override
    {
        callback(item, userdata);
    }
};


//## PROTOTYPES #############################################

extern "C" const char GEOS_DLL* GEOSjtsport();
extern "C" char GEOS_DLL* GEOSasText(Geometry* g1);


namespace { // anonymous

char*
gstrdup_s(const char* str, const std::size_t size)
{
    char* out = static_cast<char*>(malloc(size + 1));
    if(0 != out) {
        // as no strlen call necessary, memcpy may be faster than strcpy
        std::memcpy(out, str, size + 1);
    }

    assert(0 != out);

    // we haven't been checking allocation before ticket #371
    if(0 == out) {
        throw(std::runtime_error("Failed to allocate memory for duplicate string"));
    }

    return out;
}

char*
gstrdup(std::string const& str)
{
    return gstrdup_s(str.c_str(), str.size());
}

} // namespace anonymous

extern "C" {

    GEOSContextHandle_t
    initGEOS_r(GEOSMessageHandler nf, GEOSMessageHandler ef)
    {
        GEOSContextHandle_t handle = GEOS_init_r();

        if(0 != handle) {
            GEOSContext_setNoticeHandler_r(handle, nf);
            GEOSContext_setErrorHandler_r(handle, ef);
        }

        return handle;
    }

    GEOSContextHandle_t
    GEOS_init_r()
    {
        GEOSContextHandleInternal_t* handle = new GEOSContextHandleInternal_t();

        geos::util::Interrupt::cancel();

        return static_cast<GEOSContextHandle_t>(handle);
    }

    GEOSMessageHandler
    GEOSContext_setNoticeHandler_r(GEOSContextHandle_t extHandle, GEOSMessageHandler nf)
    {
        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        return handle->setNoticeHandler(nf);
    }

    GEOSMessageHandler
    GEOSContext_setErrorHandler_r(GEOSContextHandle_t extHandle, GEOSMessageHandler nf)
    {
        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        return handle->setErrorHandler(nf);
    }

    GEOSMessageHandler_r
    GEOSContext_setNoticeMessageHandler_r(GEOSContextHandle_t extHandle, GEOSMessageHandler_r nf, void* userData)
    {
        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        return handle->setNoticeHandler(nf, userData);
    }

    GEOSMessageHandler_r
    GEOSContext_setErrorMessageHandler_r(GEOSContextHandle_t extHandle, GEOSMessageHandler_r ef, void* userData)
    {
        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        return handle->setErrorHandler(ef, userData);
    }

    void
    finishGEOS_r(GEOSContextHandle_t extHandle)
    {
        // Fix up freeing handle w.r.t. malloc above
        delete extHandle;
        extHandle = NULL;
    }

    void
    GEOS_finish_r(GEOSContextHandle_t extHandle)
    {
        finishGEOS_r(extHandle);
    }

    void
    GEOSFree_r(GEOSContextHandle_t extHandle, void* buffer)
    {
        assert(0 != extHandle);

        free(buffer);
    }

//-----------------------------------------------------------
// relate()-related functions
//  return 0 = false, 1 = true, 2 = error occurred
//-----------------------------------------------------------

    char
    GEOSDisjoint_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2)
    {
        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(handle->initialized == 0) {
            return 2;
        }

        try {
            bool result = g1->disjoint(g2);
            return result;
        }

        // TODO: mloskot is going to replace these double-catch block
        // with a macro to remove redundant code in this and
        // following functions.
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2;
    }

    char
    GEOSTouches_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2)
    {
        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            bool result = g1->touches(g2);
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2;
    }

    char
    GEOSIntersects_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2)
    {
        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            bool result = g1->intersects(g2);
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2;
    }

    char
    GEOSCrosses_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2)
    {
        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            bool result = g1->crosses(g2);
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2;
    }

    char
    GEOSWithin_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2)
    {
        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            bool result = g1->within(g2);
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2;
    }

// call g1->contains(g2)
// returns 0 = false
//         1 = true
//         2 = error was trapped
    char
    GEOSContains_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2)
    {
        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            bool result = g1->contains(g2);
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2;
    }

    char
    GEOSOverlaps_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2)
    {
        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            bool result = g1->overlaps(g2);
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2;
    }

    char
    GEOSCovers_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2)
    {
        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            bool result = g1->covers(g2);
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2;
    }

    char
    GEOSCoveredBy_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2)
    {
        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            bool result = g1->coveredBy(g2);
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2;
    }


//-------------------------------------------------------------------
// low-level relate functions
//------------------------------------------------------------------

    char
    GEOSRelatePattern_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2, const char* pat)
    {
        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            std::string s(pat);
            bool result = g1->relate(g2, s);
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2;
    }

    char
    GEOSRelatePatternMatch_r(GEOSContextHandle_t extHandle, const char* mat,
                             const char* pat)
    {
        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            using geos::geom::IntersectionMatrix;

            std::string m(mat);
            std::string p(pat);
            IntersectionMatrix im(m);

            bool result = im.matches(p);
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2;
    }

    char*
    GEOSRelate_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            using geos::geom::IntersectionMatrix;

            auto im = g1->relate(g2);
            if(im == nullptr) {
                return 0;
            }

            char* result = gstrdup(im->toString());

            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    char*
    GEOSRelateBoundaryNodeRule_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2, int bnr)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            using geos::operation::relate::RelateOp;
            using geos::geom::IntersectionMatrix;
            using geos::algorithm::BoundaryNodeRule;

            std::unique_ptr<IntersectionMatrix> im;
            switch(bnr) {
            case GEOSRELATE_BNR_MOD2: /* same as OGC */
                im = RelateOp::relate(g1, g2,
                                      BoundaryNodeRule::getBoundaryRuleMod2());
                break;
            case GEOSRELATE_BNR_ENDPOINT:
                im = RelateOp::relate(g1, g2,
                                      BoundaryNodeRule::getBoundaryEndPoint());
                break;
            case GEOSRELATE_BNR_MULTIVALENT_ENDPOINT:
                im = RelateOp::relate(g1, g2,
                                      BoundaryNodeRule::getBoundaryMultivalentEndPoint());
                break;
            case GEOSRELATE_BNR_MONOVALENT_ENDPOINT:
                im = RelateOp::relate(g1, g2,
                                      BoundaryNodeRule::getBoundaryMonovalentEndPoint());
                break;
            default:
                handle->ERROR_MESSAGE("Invalid boundary node rule %d", bnr);
                return 0;
                break;
            }

            if(0 == im) {
                return 0;
            }

            char* result = gstrdup(im->toString());

            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }



//-----------------------------------------------------------------
// isValid
//-----------------------------------------------------------------


    char
    GEOSisValid_r(GEOSContextHandle_t extHandle, const Geometry* g1)
    {
        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            using geos::operation::valid::IsValidOp;
            using geos::operation::valid::TopologyValidationError;

            IsValidOp ivo(g1);
            TopologyValidationError* err = ivo.getValidationError();
            if(err) {
                handle->NOTICE_MESSAGE("%s", err->toString().c_str());
                return 0;
            }
            else {
                return 1;
            }
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2;
    }

    char*
    GEOSisValidReason_r(GEOSContextHandle_t extHandle, const Geometry* g1)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            using geos::operation::valid::IsValidOp;
            using geos::operation::valid::TopologyValidationError;

            char* result = 0;
            char const* const validstr = "Valid Geometry";

            IsValidOp ivo(g1);
            TopologyValidationError* err = ivo.getValidationError();
            if(0 != err) {
                std::ostringstream ss;
                ss.precision(15);
                ss << err->getCoordinate();
                const std::string errloc = ss.str();
                std::string errmsg(err->getMessage());
                errmsg += "[" + errloc + "]";
                result = gstrdup(errmsg);
            }
            else {
                result = gstrdup(std::string(validstr));
            }

            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    char
    GEOSisValidDetail_r(GEOSContextHandle_t extHandle, const Geometry* g,
                        int flags, char** reason, Geometry** location)
    {
        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            using geos::operation::valid::IsValidOp;
            using geos::operation::valid::TopologyValidationError;

            IsValidOp ivo(g);
            if(flags & GEOSVALID_ALLOW_SELFTOUCHING_RING_FORMING_HOLE) {
                ivo.setSelfTouchingRingFormingHoleValid(true);
            }
            TopologyValidationError* err = ivo.getValidationError();
            if(0 != err) {
                if(location) {
                    *location = handle->geomFactory->createPoint(err->getCoordinate());
                }
                if(reason) {
                    std::string errmsg(err->getMessage());
                    *reason = gstrdup(errmsg);
                }
                return 0;
            }

            if(location) {
                *location = 0;
            }
            if(reason) {
                *reason = 0;
            }
            return 1; /* valid */

        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2; /* exception */
    }

//-----------------------------------------------------------------
// general purpose
//-----------------------------------------------------------------

    char
    GEOSEquals_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2)
    {
        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            bool result = g1->equals(g2);
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2;
    }

    char
    GEOSEqualsExact_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2, double tolerance)
    {
        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            bool result = g1->equalsExact(g2, tolerance);
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2;
    }

    int
    GEOSDistance_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2, double* dist)
    {
        assert(0 != dist);

        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            *dist = g1->distance(g2);
            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    int
    GEOSDistanceIndexed_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2, double* dist)
    {
        assert(0 != dist);

        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            *dist = IndexedFacetDistance::distance(g1, g2);
            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    int
    GEOSHausdorffDistance_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2, double* dist)
    {
        assert(0 != dist);

        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            *dist = DiscreteHausdorffDistance::distance(*g1, *g2);
            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    int
    GEOSHausdorffDistanceDensify_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2,
                                   double densifyFrac, double* dist)
    {
        assert(0 != dist);

        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            *dist = DiscreteHausdorffDistance::distance(*g1, *g2, densifyFrac);
            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    int
    GEOSFrechetDistance_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2, double* dist)
    {
        assert(0 != dist);

        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            *dist = DiscreteFrechetDistance::distance(*g1, *g2);
            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    int
    GEOSFrechetDistanceDensify_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2, double densifyFrac,
                                 double* dist)
    {
        assert(0 != dist);

        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            *dist = DiscreteFrechetDistance::distance(*g1, *g2, densifyFrac);
            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    int
    GEOSArea_r(GEOSContextHandle_t extHandle, const Geometry* g, double* area)
    {
        assert(0 != area);

        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            *area = g->getArea();
            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    int
    GEOSLength_r(GEOSContextHandle_t extHandle, const Geometry* g, double* length)
    {
        assert(0 != length);

        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            *length = g->getLength();
            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    CoordinateSequence*
    GEOSNearestPoints_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            if(g1->isEmpty() || g2->isEmpty()) {
                return 0;
            }
            return geos::operation::distance::DistanceOp::nearestPoints(g1, g2).release();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }


    Geometry*
    GEOSGeomFromWKT_r(GEOSContextHandle_t extHandle, const char* wkt)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            const std::string wktstring(wkt);
            WKTReader r(static_cast<GeometryFactory const*>(handle->geomFactory));

            auto g = r.read(wktstring);
            return g.release();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    char*
    GEOSGeomToWKT_r(GEOSContextHandle_t extHandle, const Geometry* g1)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {

            char* result = gstrdup(g1->toString());
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }
        return NULL;
    }

// Remember to free the result!
    unsigned char*
    GEOSGeomToWKB_buf_r(GEOSContextHandle_t extHandle, const Geometry* g, size_t* size)
    {
        assert(0 != size);

        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        using geos::io::WKBWriter;
        try {
            int byteOrder = handle->WKBByteOrder;
            WKBWriter w(handle->WKBOutputDims, byteOrder);
            std::ostringstream os(std::ios_base::binary);
            w.write(*g, os);
            std::string wkbstring(os.str());
            const std::size_t len = wkbstring.length();

            unsigned char* result = 0;
            result = static_cast<unsigned char*>(malloc(len));
            if(0 != result) {
                std::memcpy(result, wkbstring.c_str(), len);
                *size = len;
            }
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    Geometry*
    GEOSGeomFromWKB_buf_r(GEOSContextHandle_t extHandle, const unsigned char* wkb, size_t size)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        using geos::io::WKBReader;
        try {
            std::string wkbstring(reinterpret_cast<const char*>(wkb), size); // make it binary !
            WKBReader r(*(static_cast<GeometryFactory const*>(handle->geomFactory)));
            std::istringstream is(std::ios_base::binary);
            is.str(wkbstring);
            is.seekg(0, std::ios::beg); // rewind reader pointer
            auto g = r.read(is);
            return g.release();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    /* Read/write wkb hex values.  Returned geometries are
       owned by the caller.*/
    unsigned char*
    GEOSGeomToHEX_buf_r(GEOSContextHandle_t extHandle, const Geometry* g, size_t* size)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        using geos::io::WKBWriter;
        try {
            int byteOrder = handle->WKBByteOrder;
            WKBWriter w(handle->WKBOutputDims, byteOrder);
            std::ostringstream os(std::ios_base::binary);
            w.writeHEX(*g, os);
            std::string hexstring(os.str());

            char* result = gstrdup(hexstring);
            if(0 != result) {
                *size = hexstring.length();
            }

            return reinterpret_cast<unsigned char*>(result);
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    Geometry*
    GEOSGeomFromHEX_buf_r(GEOSContextHandle_t extHandle, const unsigned char* hex, size_t size)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        using geos::io::WKBReader;
        try {
            std::string hexstring(reinterpret_cast<const char*>(hex), size);
            WKBReader r(*(static_cast<GeometryFactory const*>(handle->geomFactory)));
            std::istringstream is(std::ios_base::binary);
            is.str(hexstring);
            is.seekg(0, std::ios::beg); // rewind reader pointer

            auto g = r.readHEX(is);
            return g.release();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    char
    GEOSisEmpty_r(GEOSContextHandle_t extHandle, const Geometry* g1)
    {
        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            return g1->isEmpty();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2;
    }

    char
    GEOSisSimple_r(GEOSContextHandle_t extHandle, const Geometry* g1)
    {
        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            return g1->isSimple();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
            return 2;
        }

        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
            return 2;
        }
    }

    char
    GEOSisRing_r(GEOSContextHandle_t extHandle, const Geometry* g)
    {
        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            const LineString* ls = dynamic_cast<const LineString*>(g);
            if(ls) {
                return (ls->isRing());
            }
            else {
                return 0;
            }
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
            return 2;
        }

        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
            return 2;
        }
    }



//free the result of this
    char*
    GEOSGeomType_r(GEOSContextHandle_t extHandle, const Geometry* g1)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            std::string s = g1->getGeometryType();

            char* result = gstrdup(s);
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

// Return postgis geometry type index
    int
    GEOSGeomTypeId_r(GEOSContextHandle_t extHandle, const Geometry* g1)
    {
        if(nullptr == extHandle) {
            return -1;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return -1;
        }

        try {
            return g1->getGeometryTypeId();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return -1;
    }

//-------------------------------------------------------------------
// GEOS functions that return geometries
//-------------------------------------------------------------------

    Geometry*
    GEOSEnvelope_r(GEOSContextHandle_t extHandle, const Geometry* g1)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            Geometry* g3 = g1->getEnvelope().release();
            g3->setSRID(g1->getSRID());
            return g3;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    Geometry*
    GEOSIntersection_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            Geometry* g3 = g1->intersection(g2).release();
            g3->setSRID(g1->getSRID());
            return g3;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    Geometry*
    GEOSBuffer_r(GEOSContextHandle_t extHandle, const Geometry* g1, double width, int quadrantsegments)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            Geometry* g3 = g1->buffer(width, quadrantsegments).release();
            g3->setSRID(g1->getSRID());
            return g3;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    Geometry*
    GEOSBufferWithStyle_r(GEOSContextHandle_t extHandle, const Geometry* g1, double width, int quadsegs, int endCapStyle,
                          int joinStyle, double mitreLimit)
    {
        using geos::operation::buffer::BufferParameters;
        using geos::operation::buffer::BufferOp;
        using geos::util::IllegalArgumentException;

        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            BufferParameters bp;
            bp.setQuadrantSegments(quadsegs);

            if(endCapStyle > BufferParameters::CAP_SQUARE) {
                throw IllegalArgumentException("Invalid buffer endCap style");
            }
            bp.setEndCapStyle(
                static_cast<BufferParameters::EndCapStyle>(endCapStyle)
            );

            if(joinStyle > BufferParameters::JOIN_BEVEL) {
                throw IllegalArgumentException("Invalid buffer join style");
            }
            bp.setJoinStyle(
                static_cast<BufferParameters::JoinStyle>(joinStyle)
            );
            bp.setMitreLimit(mitreLimit);
            BufferOp op(g1, bp);
            Geometry* g3 = op.getResultGeometry(width);
            g3->setSRID(g1->getSRID());
            return g3;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    Geometry*
    GEOSOffsetCurve_r(GEOSContextHandle_t extHandle, const Geometry* g1, double width, int quadsegs, int joinStyle,
                      double mitreLimit)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            BufferParameters bp;
            bp.setEndCapStyle(BufferParameters::CAP_FLAT);
            bp.setQuadrantSegments(quadsegs);

            if(joinStyle > BufferParameters::JOIN_BEVEL) {
                throw IllegalArgumentException("Invalid buffer join style");
            }
            bp.setJoinStyle(
                static_cast<BufferParameters::JoinStyle>(joinStyle)
            );
            bp.setMitreLimit(mitreLimit);

            bool isLeftSide = true;
            if(width < 0) {
                isLeftSide = false;
                width = -width;
            }
            BufferBuilder bufBuilder(bp);
            Geometry* g3 = bufBuilder.bufferLineSingleSided(g1, width, isLeftSide);
            g3->setSRID(g1->getSRID());
            return g3;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    /* @deprecated in 3.3.0 */
    Geometry*
    GEOSSingleSidedBuffer_r(GEOSContextHandle_t extHandle, const Geometry* g1, double width, int quadsegs, int joinStyle,
                            double mitreLimit, int leftSide)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            BufferParameters bp;
            bp.setEndCapStyle(BufferParameters::CAP_FLAT);
            bp.setQuadrantSegments(quadsegs);

            if(joinStyle > BufferParameters::JOIN_BEVEL) {
                throw IllegalArgumentException("Invalid buffer join style");
            }
            bp.setJoinStyle(
                static_cast<BufferParameters::JoinStyle>(joinStyle)
            );
            bp.setMitreLimit(mitreLimit);

            bool isLeftSide = leftSide == 0 ? false : true;
            BufferBuilder bufBuilder(bp);
            Geometry* g3 = bufBuilder.bufferLineSingleSided(g1, width, isLeftSide);
            g3->setSRID(g1->getSRID());
            return g3;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    Geometry*
    GEOSConvexHull_r(GEOSContextHandle_t extHandle, const Geometry* g1)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            Geometry* g3 = g1->convexHull().release();
            g3->setSRID(g1->getSRID());
            return g3;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }


    Geometry*
    GEOSMinimumRotatedRectangle_r(GEOSContextHandle_t extHandle, const Geometry* g)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            geos::algorithm::MinimumDiameter m(g);
            Geometry* g3 = m.getMinimumRectangle().release();
            g3->setSRID(g->getSRID());
            return g3;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    Geometry*
    GEOSMinimumWidth_r(GEOSContextHandle_t extHandle, const Geometry* g)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            geos::algorithm::MinimumDiameter m(g);
            Geometry* g3 = m.getDiameter().release();
            g3->setSRID(g->getSRID());
            return g3;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    Geometry*
    GEOSMinimumClearanceLine_r(GEOSContextHandle_t extHandle, const Geometry* g)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            geos::precision::MinimumClearance mc(g);
            Geometry *g3 = mc.getLine().release();
            g3->setSRID(g->getSRID());
            return g3;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    int
    GEOSMinimumClearance_r(GEOSContextHandle_t extHandle, const Geometry* g, double* d)
    {
        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            geos::precision::MinimumClearance mc(g);
            double res = mc.getDistance();
            *d = res;
            return 0;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2;
    }


    Geometry*
    GEOSDifference_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            Geometry *g3 = g1->difference(g2).release();
            g3->setSRID(g1->getSRID());
            return g3;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    Geometry*
    GEOSBoundary_r(GEOSContextHandle_t extHandle, const Geometry* g1)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            Geometry* g3 = g1->getBoundary().release();
            g3->setSRID(g1->getSRID());
            return g3;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    Geometry*
    GEOSSymDifference_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            Geometry *g3 = g1->symDifference(g2).release();
            g3->setSRID(g1->getSRID());
            return g3;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
            return NULL;
        }

        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
            return NULL;
        }
    }

    Geometry*
    GEOSUnion_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* g2)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            Geometry *g3 = g1->Union(g2).release();
            g3->setSRID(g1->getSRID());
            return g3;
        }
        catch(const std::exception& e) {
#if VERBOSE_EXCEPTIONS
            std::ostringstream s;
            s << "Exception on GEOSUnion_r with following inputs:" << std::endl;
            s << "A: " << g1->toString() << std::endl;
            s << "B: " << g2->toString() << std::endl;
            handle->NOTICE_MESSAGE("%s", s.str().c_str());
#endif // VERBOSE_EXCEPTIONS
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    Geometry*
    GEOSCoverageUnion_r(GEOSContextHandle_t extHandle, const Geometry* g)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            Geometry *g3 = geos::operation::geounion::CoverageUnion::Union(g).release();
            g3->setSRID(g->getSRID());
            return g3;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    Geometry*
    GEOSUnaryUnion_r(GEOSContextHandle_t extHandle, const Geometry* g)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            GeomPtr g3(g->Union());
            g3->setSRID(g->getSRID());
            return g3.release();
        }
        catch(const std::exception& e) {
#if VERBOSE_EXCEPTIONS
            std::ostringstream s;
            s << "Exception on GEOSUnaryUnion_r with following inputs:" << std::endl;
            s << "A: " << g1->toString() << std::endl;
            s << "B: " << g2->toString() << std::endl;
            handle->NOTICE_MESSAGE("%s", s.str().c_str());
#endif // VERBOSE_EXCEPTIONS
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    Geometry*
    GEOSNode_r(GEOSContextHandle_t extHandle, const Geometry* g)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            std::unique_ptr<Geometry> g3 = geos::noding::GeometryNoder::node(*g);
            g3->setSRID(g->getSRID());
            return g3.release();
        }
        catch(const std::exception& e) {
#if VERBOSE_EXCEPTIONS
            std::ostringstream s;
            s << "Exception on GEOSNode_r with following inputs:" << std::endl;
            s << "A: " << g1->toString() << std::endl;
            s << "B: " << g2->toString() << std::endl;
            handle->NOTICE_MESSAGE("%s", s.str().c_str());
#endif // VERBOSE_EXCEPTIONS
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    Geometry*
    GEOSUnionCascaded_r(GEOSContextHandle_t extHandle, const Geometry* g1)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            const geos::geom::MultiPolygon* p = dynamic_cast<const geos::geom::MultiPolygon*>(g1);
            if(! p) {
                handle->ERROR_MESSAGE("Invalid argument (must be a MultiPolygon)");
                return NULL;
            }

            using geos::operation::geounion::CascadedPolygonUnion;
            Geometry *g3 = CascadedPolygonUnion::Union(p);
            g3->setSRID(g1->getSRID());
            return g3;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    Geometry*
    GEOSPointOnSurface_r(GEOSContextHandle_t extHandle, const Geometry* g1)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            auto ret = g1->getInteriorPoint();
            if(ret == nullptr) {
                const GeometryFactory* gf = handle->geomFactory;
                // return an empty point
                return gf->createPoint().release();
            }
            ret->setSRID(g1->getSRID());
            return ret.release();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    Geometry*
    GEOSClipByRect_r(GEOSContextHandle_t extHandle, const Geometry* g, double xmin, double ymin, double xmax, double ymax)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            using geos::operation::intersection::Rectangle;
            using geos::operation::intersection::RectangleIntersection;
            Rectangle rect(xmin, ymin, xmax, ymax);
            std::unique_ptr<Geometry> g3 = RectangleIntersection::clip(*g, rect);
            g3->setSRID(g->getSRID());
            return g3.release();
        }
        catch(const std::exception& e) {
#if VERBOSE_EXCEPTIONS
            std::ostringstream s;
            s << "Exception on GEOSClipByRect_r with following inputs:" << std::endl;
            s << "A: " << g1->toString() << std::endl;
            s << "B: " << g2->toString() << std::endl;
            handle->NOTICE_MESSAGE("%s", s.str().c_str());
#endif // VERBOSE_EXCEPTIONS
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

//-------------------------------------------------------------------
// memory management functions
//------------------------------------------------------------------

    void
    GEOSGeom_destroy_r(GEOSContextHandle_t extHandle, Geometry* a)
    {
        GEOSContextHandleInternal_t* handle = 0;

        // FIXME: mloskot: Does this try-catch around delete means that
        // destructors in GEOS may throw? If it does, this is a serious
        // violation of "never throw an exception from a destructor" principle

        try {
            delete a;
        }
        catch(const std::exception& e) {
            if(nullptr == extHandle) {
                return;
            }

            handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
            if(0 == handle->initialized) {
                return;
            }

            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            if(nullptr == extHandle) {
                return;
            }

            handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
            if(0 == handle->initialized) {
                return;
            }

            handle->ERROR_MESSAGE("Unknown exception thrown");
        }
    }

    void
    GEOSGeom_setUserData_r(GEOSContextHandle_t extHandle, Geometry* g, void* userData)
    {
        assert(0 != g);

        if(nullptr == extHandle) {
            return;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return;
        }

        g->setUserData(userData);
    }

    void
    GEOSSetSRID_r(GEOSContextHandle_t extHandle, Geometry* g, int srid)
    {
        assert(0 != g);

        if(nullptr == extHandle) {
            return;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return;
        }

        g->setSRID(srid);
    }


    int
    GEOSGetNumCoordinates_r(GEOSContextHandle_t extHandle, const Geometry* g)
    {
        assert(0 != g);

        if(nullptr == extHandle) {
            return -1;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return -1;
        }

        try {
            return static_cast<int>(g->getNumPoints());
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return -1;
    }

    /*
     * Return -1 on exception, 0 otherwise.
     * Converts Geometry to normal form (or canonical form).
     */
    int
    GEOSNormalize_r(GEOSContextHandle_t extHandle, Geometry* g)
    {
        assert(0 != g);

        if(nullptr == extHandle) {
            return -1;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return -1;
        }

        try {
            g->normalize();
            return 0; // SUCCESS
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return -1;
    }

    int
    GEOSGetNumInteriorRings_r(GEOSContextHandle_t extHandle, const Geometry* g1)
    {
        if(nullptr == extHandle) {
            return -1;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return -1;
        }

        try {
            const Polygon* p = dynamic_cast<const Polygon*>(g1);
            if(! p) {
                handle->ERROR_MESSAGE("Argument is not a Polygon");
                return -1;
            }
            return static_cast<int>(p->getNumInteriorRing());
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return -1;
    }


// returns -1 on error and 1 for non-multi geometries
    int
    GEOSGetNumGeometries_r(GEOSContextHandle_t extHandle, const Geometry* g1)
    {
        if(nullptr == extHandle) {
            return -1;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return -1;
        }

        try {
            return static_cast<int>(g1->getNumGeometries());
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return -1;
    }


    /*
     * Call only on GEOMETRYCOLLECTION or MULTI*.
     * Return a pointer to the internal Geometry.
     */
    const Geometry*
    GEOSGetGeometryN_r(GEOSContextHandle_t extHandle, const Geometry* g1, int n)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            return g1->getGeometryN(n);
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    /*
     * Call only on LINESTRING
     * Returns NULL on exception
     */
    Geometry*
    GEOSGeomGetPointN_r(GEOSContextHandle_t extHandle, const Geometry* g1, int n)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            using geos::geom::LineString;
            const LineString* ls = dynamic_cast<const LineString*>(g1);
            if(! ls) {
                handle->ERROR_MESSAGE("Argument is not a LineString");
                return NULL;
            }
            return ls->getPointN(n).release();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    /*
     * Call only on LINESTRING
     */
    Geometry*
    GEOSGeomGetStartPoint_r(GEOSContextHandle_t extHandle, const Geometry* g1)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            using geos::geom::LineString;
            const LineString* ls = dynamic_cast<const LineString*>(g1);
            if(! ls) {
                handle->ERROR_MESSAGE("Argument is not a LineString");
                return NULL;
            }
            return ls->getStartPoint().release();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    /*
     * Call only on LINESTRING
     */
    Geometry*
    GEOSGeomGetEndPoint_r(GEOSContextHandle_t extHandle, const Geometry* g1)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            using geos::geom::LineString;
            const LineString* ls = dynamic_cast<const LineString*>(g1);
            if(! ls) {
                handle->ERROR_MESSAGE("Argument is not a LineString");
                return NULL;
            }
            return ls->getEndPoint().release();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    /*
     * Call only on LINESTRING or MULTILINESTRING
     * return 2 on exception, 1 on true, 0 on false
     */
    char
    GEOSisClosed_r(GEOSContextHandle_t extHandle, const Geometry* g1)
    {
        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            using geos::geom::LineString;
            using geos::geom::MultiLineString;

            const LineString* ls = dynamic_cast<const LineString*>(g1);
            if(ls) {
                return ls->isClosed();
            }

            const MultiLineString* mls = dynamic_cast<const MultiLineString*>(g1);
            if(mls) {
                return mls->isClosed();
            }

            handle->ERROR_MESSAGE("Argument is not a LineString or MultiLineString");
            return 2;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2;
    }

    /*
     * Call only on LINESTRING
     * return 0 on exception, otherwise 1
     */
    int
    GEOSGeomGetLength_r(GEOSContextHandle_t extHandle, const Geometry* g1, double* length)
    {
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            using geos::geom::LineString;
            const LineString* ls = dynamic_cast<const LineString*>(g1);
            if(! ls) {
                handle->ERROR_MESSAGE("Argument is not a LineString");
                return 0;
            }
            *length = ls->getLength();
            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    /*
     * Call only on LINESTRING
     */
    int
    GEOSGeomGetNumPoints_r(GEOSContextHandle_t extHandle, const Geometry* g1)
    {
        if(nullptr == extHandle) {
            return -1;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return -1;
        }

        try {
            using geos::geom::LineString;
            const LineString* ls = dynamic_cast<const LineString*>(g1);
            if(! ls) {
                handle->ERROR_MESSAGE("Argument is not a LineString");
                return -1;
            }
            return static_cast<int>(ls->getNumPoints());
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return -1;
    }

    /*
     * For POINT
     * returns 0 on exception, otherwise 1
     */
    int
    GEOSGeomGetX_r(GEOSContextHandle_t extHandle, const Geometry* g1, double* x)
    {
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            using geos::geom::Point;
            const Point* po = dynamic_cast<const Point*>(g1);
            if(! po) {
                handle->ERROR_MESSAGE("Argument is not a Point");
                return 0;
            }
            *x = po->getX();
            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    /*
     * For POINT
     * returns 0 on exception, otherwise 1
     */
    int
    GEOSGeomGetY_r(GEOSContextHandle_t extHandle, const Geometry* g1, double* y)
    {
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            using geos::geom::Point;
            const Point* po = dynamic_cast<const Point*>(g1);
            if(! po) {
                handle->ERROR_MESSAGE("Argument is not a Point");
                return 0;
            }
            *y = po->getY();
            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    /*
     * For POINT
     * returns 0 on exception, otherwise 1
     */
    int
    GEOSGeomGetZ_r(GEOSContextHandle_t extHandle, const Geometry* g1, double* z)
    {
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            using geos::geom::Point;
            const Point* po = dynamic_cast<const Point*>(g1);
            if(! po) {
                handle->ERROR_MESSAGE("Argument is not a Point");
                return 0;
            }
            *z = po->getZ();
            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    /*
     * Call only on polygon
     * Return a copy of the internal Geometry.
     */
    const Geometry*
    GEOSGetExteriorRing_r(GEOSContextHandle_t extHandle, const Geometry* g1)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            const Polygon* p = dynamic_cast<const Polygon*>(g1);
            if(! p) {
                handle->ERROR_MESSAGE("Invalid argument (must be a Polygon)");
                return NULL;
            }
            return p->getExteriorRing();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    /*
     * Call only on polygon
     * Return a pointer to internal storage, do not destroy it.
     */
    const Geometry*
    GEOSGetInteriorRingN_r(GEOSContextHandle_t extHandle, const Geometry* g1, int n)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            const Polygon* p = dynamic_cast<const Polygon*>(g1);
            if(! p) {
                handle->ERROR_MESSAGE("Invalid argument (must be a Polygon)");
                return NULL;
            }
            return p->getInteriorRingN(n);
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    Geometry*
    GEOSGetCentroid_r(GEOSContextHandle_t extHandle, const Geometry* g)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            Geometry* ret = g->getCentroid().release();
            if(0 == ret) {
                const GeometryFactory* gf = handle->geomFactory;
                return gf->createPoint().release();
            }
            ret->setSRID(g->getSRID());
            return ret;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    Geometry*
    GEOSMinimumBoundingCircle_r(GEOSContextHandle_t extHandle, const Geometry* g,
        double* radius, Geometry** center)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            geos::algorithm::MinimumBoundingCircle mc(g);
            std::unique_ptr<Geometry> ret = mc.getCircle();
            const GeometryFactory* gf = handle->geomFactory;
            if(!ret) {
                if (center) *center = NULL;
                if (radius) *radius = 0.0;
                return gf->createPolygon().release();
            }
            if (center) *center = static_cast<Geometry*>(gf->createPoint(mc.getCentre()));
            if (radius) *radius = mc.getRadius();
            ret->setSRID(g->getSRID());
            return ret.release();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    Geometry*
    GEOSGeom_createEmptyCollection_r(GEOSContextHandle_t extHandle, int type)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

#ifdef GEOS_DEBUG
        char buf[256];
        sprintf(buf, "createCollection: requested type %d", type);
        handle->NOTICE_MESSAGE("%s", buf);// TODO: Can handle->NOTICE_MESSAGE format that directly?
#endif

        try {
            const GeometryFactory* gf = handle->geomFactory;

            std::unique_ptr<Geometry> g = 0;
            switch(type) {
            case GEOS_GEOMETRYCOLLECTION:
                g = gf->createGeometryCollection();
                break;
            case GEOS_MULTIPOINT:
                g = gf->createMultiPoint();
                break;
            case GEOS_MULTILINESTRING:
                g = gf->createMultiLineString();
                break;
            case GEOS_MULTIPOLYGON:
                g = gf->createMultiPolygon();
                break;
            default:
                handle->ERROR_MESSAGE("Unsupported type request for GEOSGeom_createEmptyCollection_r");
                g = 0;

            }

            return g.release();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    Geometry*
    GEOSGeom_createCollection_r(GEOSContextHandle_t extHandle, int type, Geometry** geoms, unsigned int ngeoms)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

#ifdef GEOS_DEBUG
        char buf[256];
        sprintf(buf, "PostGIS2GEOS_collection: requested type %d, ngeoms: %d",
                type, ngeoms);
        handle->NOTICE_MESSAGE("%s", buf);// TODO: Can handle->NOTICE_MESSAGE format that directly?
#endif

        try {
            const GeometryFactory* gf = handle->geomFactory;
            std::vector<Geometry*>* vgeoms = new std::vector<Geometry*>(geoms, geoms + ngeoms);

            Geometry* g = 0;
            switch(type) {
            case GEOS_GEOMETRYCOLLECTION:
                g = gf->createGeometryCollection(vgeoms);
                break;
            case GEOS_MULTIPOINT:
                g = gf->createMultiPoint(vgeoms);
                break;
            case GEOS_MULTILINESTRING:
                g = gf->createMultiLineString(vgeoms);
                break;
            case GEOS_MULTIPOLYGON:
                g = gf->createMultiPolygon(vgeoms);
                break;
            default:
                handle->ERROR_MESSAGE("Unsupported type request for PostGIS2GEOS_collection");
                delete vgeoms;
                g = 0;

            }

            return g;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    Geometry*
    GEOSPolygonize_r(GEOSContextHandle_t extHandle, const Geometry* const* g, unsigned int ngeoms)
    {
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        Geometry* out = 0;

        try {
            // Polygonize
            using geos::operation::polygonize::Polygonizer;
            Polygonizer plgnzr;
            for(std::size_t i = 0; i < ngeoms; ++i) {
                plgnzr.add(g[i]);
            }

#if GEOS_DEBUG
            handle->NOTICE_MESSAGE("geometry vector added to polygonizer");
#endif

            auto polys = plgnzr.getPolygons();
            assert(0 != polys);

#if GEOS_DEBUG
            handle->NOTICE_MESSAGE("output polygons got");
#endif

            // We need a vector of Geometry pointers, not Polygon pointers.
            // STL vector doesn't allow transparent upcast of this
            // nature, so we explicitly convert.
            // (it's just a waste of processor and memory, btw)
            //
            // XXX mloskot: Why not to extent GeometryFactory to accept
            // vector of polygons or extend Polygonizer to return list of Geometry*
            // or add a wrapper which semantic is similar to:
            // std::vector<as_polygon<Geometry*> >
            std::vector<Geometry*>* polyvec = new std::vector<Geometry*>(polys->size());

            for(std::size_t i = 0; i < polys->size(); ++i) {
                (*polyvec)[i] = (*polys)[i].release();
            }
            polys = 0;

            const GeometryFactory* gf = handle->geomFactory;

            // The below takes ownership of the passed vector,
            // so we must *not* delete it
            out = gf->createGeometryCollection(polyvec);
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return out;
    }

    Geometry*
    GEOSPolygonize_valid_r(GEOSContextHandle_t extHandle, const Geometry* const* g, unsigned int ngeoms)
    {
        if(nullptr == extHandle) {
            return nullptr;
        }

        GEOSContextHandleInternal_t* handle = nullptr;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return nullptr;
        }

        Geometry* out = nullptr;

        try {
            // Polygonize
            using geos::operation::polygonize::Polygonizer;
            Polygonizer plgnzr(true);
            int srid = 0;
            for(std::size_t i = 0; i < ngeoms; ++i) {
                plgnzr.add(g[i]);
                srid = g[i]->getSRID();
            }

            auto polys = plgnzr.getPolygons();
            if (polys->empty()) {
                out = handle->geomFactory->createGeometryCollection().release();
            } else if (polys->size() == 1) {
                out = (*polys)[0].release();
            } else {
                auto geoms = new std::vector<Geometry *>(polys->size());
                for (size_t i = 0; i < polys->size(); i++) {
                    (*geoms)[i] = (*polys)[i].release();
                }

                out = handle->geomFactory->createMultiPolygon(geoms);
            }
            out->setSRID(srid);
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return out;
    }


    Geometry*
    GEOSBuildArea_r(GEOSContextHandle_t extHandle, const Geometry* g)
    {
        if(nullptr == extHandle) {
            return nullptr;
        }

        GEOSContextHandleInternal_t* handle = nullptr;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return nullptr;
        }

        Geometry* out = nullptr;

        try {
            using geos::operation::polygonize::BuildArea;
            BuildArea builder;
            out = builder.build(g).release();
            out->setSRID(g->getSRID());
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return out;
    }

    Geometry*
    GEOSMakeValid_r(GEOSContextHandle_t extHandle, const Geometry* g)
    {
        if(nullptr == extHandle) {
            return nullptr;
        }

        GEOSContextHandleInternal_t* handle = nullptr;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return nullptr;
        }

        Geometry* out = nullptr;

        try {
            // BuildArea
            using geos::operation::valid::MakeValid;
            MakeValid makeValid;
            out = makeValid.build(g).release();
            out->setSRID(g->getSRID());
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return out;
    }

    Geometry*
    GEOSPolygonizer_getCutEdges_r(GEOSContextHandle_t extHandle, const Geometry* const* g, unsigned int ngeoms)
    {
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        Geometry* out = 0;

        try {
            // Polygonize
            using geos::operation::polygonize::Polygonizer;
            Polygonizer plgnzr;
            int srid = 0;
            for(std::size_t i = 0; i < ngeoms; ++i) {
                plgnzr.add(g[i]);
                srid = g[i]->getSRID();
            }

#if GEOS_DEBUG
            handle->NOTICE_MESSAGE("geometry vector added to polygonizer");
#endif

            const std::vector<const LineString*>& lines = plgnzr.getCutEdges();

#if GEOS_DEBUG
            handle->NOTICE_MESSAGE("output polygons got");
#endif

            // We need a vector of Geometry pointers, not Polygon pointers.
            // STL vector doesn't allow transparent upcast of this
            // nature, so we explicitly convert.
            // (it's just a waste of processor and memory, btw)
            // XXX mloskot: See comment for GEOSPolygonize_r
            std::vector<Geometry*>* linevec = new std::vector<Geometry*>(lines.size());

            for(std::size_t i = 0, n = lines.size(); i < n; ++i) {
                (*linevec)[i] = lines[i]->clone().release();
            }

            const GeometryFactory* gf = handle->geomFactory;

            // The below takes ownership of the passed vector,
            // so we must *not* delete it
            out = gf->createGeometryCollection(linevec);
            out->setSRID(srid);
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return out;
    }

    Geometry*
    GEOSPolygonize_full_r(GEOSContextHandle_t extHandle, const Geometry* g,
                          Geometry** cuts, Geometry** dangles, Geometry** invalid)
    {
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            // Polygonize
            using geos::operation::polygonize::Polygonizer;
            Polygonizer plgnzr;
            for(std::size_t i = 0; i < g->getNumGeometries(); ++i) {
                plgnzr.add(g->getGeometryN(i));
            }

#if GEOS_DEBUG
            handle->NOTICE_MESSAGE("geometry vector added to polygonizer");
#endif
            const GeometryFactory* gf = handle->geomFactory;

            if(cuts) {

                const std::vector<const LineString*>& lines = plgnzr.getCutEdges();
                std::vector<Geometry*>* linevec = new std::vector<Geometry*>(lines.size());
                for(std::size_t i = 0, n = lines.size(); i < n; ++i) {
                    (*linevec)[i] = lines[i]->clone().release();
                }

                // The below takes ownership of the passed vector,
                // so we must *not* delete it
                *cuts = gf->createGeometryCollection(linevec);
            }

            if(dangles) {

                const std::vector<const LineString*>& lines = plgnzr.getDangles();
                std::vector<Geometry*>* linevec = new std::vector<Geometry*>(lines.size());
                for(std::size_t i = 0, n = lines.size(); i < n; ++i) {
                    (*linevec)[i] = lines[i]->clone().release();
                }

                // The below takes ownership of the passed vector,
                // so we must *not* delete it
                *dangles = gf->createGeometryCollection(linevec);
            }

            if(invalid) {

                const std::vector<std::unique_ptr<LineString>>& lines = plgnzr.getInvalidRingLines();
                std::vector<Geometry*>* linevec = new std::vector<Geometry*>(lines.size());
                for(std::size_t i = 0, n = lines.size(); i < n; ++i) {
                    (*linevec)[i] = lines[i]->clone().release();
                }

                // The below takes ownership of the passed vector,
                // so we must *not* delete it
                *invalid = gf->createGeometryCollection(linevec);
            }

            auto polys = plgnzr.getPolygons();
            std::vector<Geometry*>* polyvec = new std::vector<Geometry*>(polys->size());
            for(std::size_t i = 0; i < polys->size(); ++i) {
                (*polyvec)[i] = (*polys)[i].release();
            }

            Geometry* out = gf->createGeometryCollection(polyvec);
            out->setSRID(g->getSRID());
            return out;

        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
            return 0;
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
            return 0;
        }
    }

    Geometry*
    GEOSLineMerge_r(GEOSContextHandle_t extHandle, const Geometry* g)
    {
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        Geometry* out = 0;

        try {
            using geos::operation::linemerge::LineMerger;
            LineMerger lmrgr;
            lmrgr.add(g);

            std::vector<LineString*>* lines = lmrgr.getMergedLineStrings();
            assert(0 != lines);

#if GEOS_DEBUG
            handle->NOTICE_MESSAGE("output lines got");
#endif

            std::vector<Geometry*>* geoms = new std::vector<Geometry*>(lines->size());
            for(std::vector<Geometry*>::size_type i = 0; i < lines->size(); ++i) {
                (*geoms)[i] = (*lines)[i];
            }
            delete lines;
            lines = 0;

            const GeometryFactory* gf = handle->geomFactory;
            out = gf->buildGeometry(geoms);
            out->setSRID(g->getSRID());

            // XXX: old version
            //out = gf->createGeometryCollection(geoms);
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return out;
    }

    Geometry*
    GEOSReverse_r(GEOSContextHandle_t extHandle, const Geometry* g)
    {
        assert(0 != g);

        if(nullptr == extHandle) {
            return nullptr;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return nullptr;
        }

        try {
            Geometry* g3 = g->reverse().release();
            g3->setSRID(g->getSRID());
            return g3;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return nullptr;
    }

    void*
    GEOSGeom_getUserData_r(GEOSContextHandle_t extHandle, const Geometry* g)
    {
        assert(0 != g);

        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            return g->getUserData();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    int
    GEOSGetSRID_r(GEOSContextHandle_t extHandle, const Geometry* g)
    {
        assert(0 != g);

        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            return g->getSRID();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    const char* GEOSversion()
    {
        static char version[256];
        sprintf(version, "%s", GEOS_CAPI_VERSION);
        return version;
    }

    const char* GEOSjtsport()
    {
        return GEOS_JTS_PORT;
    }

    char
    GEOSHasZ_r(GEOSContextHandle_t extHandle, const Geometry* g)
    {
        assert(0 != g);

        if(nullptr == extHandle) {
            return -1;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return -1;
        }

        if(g->isEmpty()) {
            return false;
        }
        assert(0 != g->getCoordinate());

        double az = g->getCoordinate()->z;
        //handle->ERROR_MESSAGE("ZCoord: %g", az);

        return static_cast<char>(std::isfinite(az));
    }

    int
    GEOS_getWKBOutputDims_r(GEOSContextHandle_t extHandle)
    {
        if(nullptr == extHandle) {
            return -1;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return -1;
        }

        return handle->WKBOutputDims;
    }

    int
    GEOS_setWKBOutputDims_r(GEOSContextHandle_t extHandle, int newdims)
    {
        if(nullptr == extHandle) {
            return -1;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return -1;
        }

        if(newdims < 2 || newdims > 3) {
            handle->ERROR_MESSAGE("WKB output dimensions out of range 2..3");
        }

        const int olddims = handle->WKBOutputDims;
        handle->WKBOutputDims = newdims;

        return olddims;
    }

    int
    GEOS_getWKBByteOrder_r(GEOSContextHandle_t extHandle)
    {
        if(nullptr == extHandle) {
            return -1;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return -1;
        }

        return handle->WKBByteOrder;
    }

    int
    GEOS_setWKBByteOrder_r(GEOSContextHandle_t extHandle, int byteOrder)
    {
        if(nullptr == extHandle) {
            return -1;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return -1;
        }

        const int oldByteOrder = handle->WKBByteOrder;
        handle->WKBByteOrder = byteOrder;

        return oldByteOrder;
    }


    CoordinateSequence*
    GEOSCoordSeq_create_r(GEOSContextHandle_t extHandle, unsigned int size, unsigned int dims)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            switch (size) {
                case 1:
                    return new geos::geom::FixedSizeCoordinateSequence<1>(dims);
                case 2:
                    return new geos::geom::FixedSizeCoordinateSequence<2>(dims);
                default: {
                    const GeometryFactory *gf = handle->geomFactory;
                    return gf->getCoordinateSequenceFactory()->create(size, dims).release();
                }
            }
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    int
    GEOSCoordSeq_setOrdinate_r(GEOSContextHandle_t extHandle, CoordinateSequence* cs,
                               unsigned int idx, unsigned int dim, double val)
    {
        assert(0 != cs);
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            cs->setOrdinate(idx, dim, val);
            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    int
    GEOSCoordSeq_setX_r(GEOSContextHandle_t extHandle, CoordinateSequence* s, unsigned int idx, double val)
    {
        return GEOSCoordSeq_setOrdinate_r(extHandle, s, idx, 0, val);
    }

    int
    GEOSCoordSeq_setY_r(GEOSContextHandle_t extHandle, CoordinateSequence* s, unsigned int idx, double val)
    {
        return GEOSCoordSeq_setOrdinate_r(extHandle, s, idx, 1, val);
    }

    int
    GEOSCoordSeq_setZ_r(GEOSContextHandle_t extHandle, CoordinateSequence* s, unsigned int idx, double val)
    {
        return GEOSCoordSeq_setOrdinate_r(extHandle, s, idx, 2, val);
    }

    int
    GEOSCoordSeq_setXY_r(GEOSContextHandle_t extHandle, CoordinateSequence* cs, unsigned int idx, double x, double y)
    {
        assert(0 != cs);
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            cs->setAt({x, y}, idx);
            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    int
    GEOSCoordSeq_setXYZ_r(GEOSContextHandle_t extHandle, CoordinateSequence* cs, unsigned int idx, double x, double y, double z)
    {
        assert(0 != cs);
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            cs->setAt({x, y, z}, idx);
            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    CoordinateSequence*
    GEOSCoordSeq_clone_r(GEOSContextHandle_t extHandle, const CoordinateSequence* cs)
    {
        assert(0 != cs);

        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            return cs->clone().release();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    int
    GEOSCoordSeq_getOrdinate_r(GEOSContextHandle_t extHandle, const CoordinateSequence* cs,
                               unsigned int idx, unsigned int dim, double* val)
    {
        assert(0 != cs);
        assert(0 != val);

        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            double d = cs->getOrdinate(idx, dim);
            *val = d;

            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    int
    GEOSCoordSeq_getX_r(GEOSContextHandle_t extHandle, const CoordinateSequence* s, unsigned int idx, double* val)
    {
        return GEOSCoordSeq_getOrdinate_r(extHandle, s, idx, 0, val);
    }

    int
    GEOSCoordSeq_getY_r(GEOSContextHandle_t extHandle, const CoordinateSequence* s, unsigned int idx, double* val)
    {
        return GEOSCoordSeq_getOrdinate_r(extHandle, s, idx, 1, val);
    }

    int
    GEOSCoordSeq_getZ_r(GEOSContextHandle_t extHandle, const CoordinateSequence* s, unsigned int idx, double* val)
    {
        return GEOSCoordSeq_getOrdinate_r(extHandle, s, idx, 2, val);
    }

    int
    GEOSCoordSeq_getXY_r(GEOSContextHandle_t extHandle, const CoordinateSequence* cs, unsigned int idx, double* x, double* y)
    {
        assert(0 != cs);
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            auto& c = cs->getAt(idx);
            *x = c.x;
            *y = c.y;
            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    int
    GEOSCoordSeq_getXYZ_r(GEOSContextHandle_t extHandle, const CoordinateSequence* cs, unsigned int idx, double* x, double* y, double* z)
    {
        assert(0 != cs);
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            auto& c = cs->getAt(idx);
            *x = c.x;
            *y = c.y;
            *z = c.z;
            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    int
    GEOSCoordSeq_getSize_r(GEOSContextHandle_t extHandle, const CoordinateSequence* cs, unsigned int* size)
    {
        assert(0 != cs);
        assert(0 != size);

        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            const std::size_t sz = cs->getSize();
            *size = static_cast<unsigned int>(sz);
            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    int
    GEOSCoordSeq_getDimensions_r(GEOSContextHandle_t extHandle, const CoordinateSequence* cs, unsigned int* dims)
    {
        assert(0 != cs);
        assert(0 != dims);

        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            const std::size_t dim = cs->getDimension();
            *dims = static_cast<unsigned int>(dim);

            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }

        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    int
    GEOSCoordSeq_isCCW_r(GEOSContextHandle_t extHandle, const CoordinateSequence* cs, char* val)
    {
        assert(cs != nullptr);
        assert(val != nullptr);

        if(extHandle == nullptr) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = nullptr;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }
        try {
            *val = geos::algorithm::Orientation::isCCW(cs);
            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }

        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    void
    GEOSCoordSeq_destroy_r(GEOSContextHandle_t extHandle, CoordinateSequence* s)
    {
        GEOSContextHandleInternal_t* handle = 0;

        try {
            delete s;
        }
        catch(const std::exception& e) {
            if(nullptr == extHandle) {
                return;
            }

            handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
            if(0 == handle->initialized) {
                return;
            }

            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            if(nullptr == extHandle) {
                return;
            }

            handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
            if(0 == handle->initialized) {
                return;
            }

            handle->ERROR_MESSAGE("Unknown exception thrown");
        }
    }

    const CoordinateSequence*
    GEOSGeom_getCoordSeq_r(GEOSContextHandle_t extHandle, const Geometry* g)
    {
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            using geos::geom::Point;

            const LineString* ls = dynamic_cast<const LineString*>(g);
            if(ls) {
                return ls->getCoordinatesRO();
            }

            const Point* p = dynamic_cast<const Point*>(g);
            if(p) {
                return p->getCoordinatesRO();
            }

            handle->ERROR_MESSAGE("Geometry must be a Point or LineString");
            return 0;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    Geometry*
    GEOSGeom_createEmptyPoint_r(GEOSContextHandle_t extHandle)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            const GeometryFactory* gf = handle->geomFactory;
            return gf->createPoint().release();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    Geometry*
    GEOSGeom_createPoint_r(GEOSContextHandle_t extHandle, CoordinateSequence* cs)
    {
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            const GeometryFactory* gf = handle->geomFactory;
            return gf->createPoint(cs);
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    Geometry*
    GEOSGeom_createPointFromXY_r(GEOSContextHandle_t extHandle, double x, double y)
    {
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            const GeometryFactory* gf = handle->geomFactory;
            geos::geom::Coordinate c(x, y);
            return gf->createPoint(c);
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    Geometry*
    GEOSGeom_createLinearRing_r(GEOSContextHandle_t extHandle, CoordinateSequence* cs)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            const GeometryFactory* gf = handle->geomFactory;

            return gf->createLinearRing(cs);
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    Geometry*
    GEOSGeom_createEmptyLineString_r(GEOSContextHandle_t extHandle)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            const GeometryFactory* gf = handle->geomFactory;

            return gf->createLineString().release();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    Geometry*
    GEOSGeom_createLineString_r(GEOSContextHandle_t extHandle, CoordinateSequence* cs)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            const GeometryFactory* gf = handle->geomFactory;

            return gf->createLineString(cs);
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    Geometry*
    GEOSGeom_createEmptyPolygon_r(GEOSContextHandle_t extHandle)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            const GeometryFactory* gf = handle->geomFactory;
            return gf->createPolygon().release();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    Geometry*
    GEOSGeom_createPolygon_r(GEOSContextHandle_t extHandle, Geometry* shell, Geometry** holes, unsigned int nholes)
    {
        // FIXME: holes must be non-nullptr or may be nullptr?
        //assert(0 != holes);

        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            using geos::geom::LinearRing;

            auto vholes = geos::detail::make_unique<std::vector<LinearRing*>>(nholes);

            for (size_t i = 0; i < nholes; i++) {
                (*vholes)[i] = dynamic_cast<LinearRing*>(holes[i]);
                if ((*vholes)[i] == nullptr) {
                    handle->ERROR_MESSAGE("Hole is not a LinearRing");
                    return NULL;
                }
            }

            LinearRing* nshell = dynamic_cast<LinearRing*>(shell);
            if(! nshell) {
                handle->ERROR_MESSAGE("Shell is not a LinearRing");
                return NULL;
            }
            const GeometryFactory* gf = handle->geomFactory;

            return gf->createPolygon(nshell, vholes.release());
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    Geometry*
    GEOSGeom_clone_r(GEOSContextHandle_t extHandle, const Geometry* g)
    {
        assert(0 != g);

        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            return g->clone().release();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    GEOSGeometry*
    GEOSGeom_setPrecision_r(GEOSContextHandle_t extHandle, const GEOSGeometry* g,
                            double gridSize, int flags)
    {
        using namespace geos::geom;

        assert(0 != g);

        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            const PrecisionModel* pm = g->getPrecisionModel();
            double cursize = pm->isFloating() ? 0 : 1.0 / pm->getScale();
            std::unique_ptr<PrecisionModel> newpm;
            if(gridSize != 0) {
                newpm.reset(new PrecisionModel(1.0 / gridSize));
            }
            else {
                newpm.reset(new PrecisionModel());
            }
            GeometryFactory::Ptr gf =
                GeometryFactory::create(newpm.get(), g->getSRID());
            Geometry* ret;
            if(gridSize != 0 && cursize != gridSize) {
                // We need to snap the geometry
                GeometryPrecisionReducer reducer(*gf);
                reducer.setPointwise(flags & GEOS_PREC_NO_TOPO);
                reducer.setRemoveCollapsedComponents(!(flags & GEOS_PREC_KEEP_COLLAPSED));
                ret = reducer.reduce(*g).release();
            }
            else {
                // No need or willing to snap, just change the factory
                ret = gf->createGeometry(g);
            }
            return ret;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    double
    GEOSGeom_getPrecision_r(GEOSContextHandle_t extHandle, const GEOSGeometry* g)
    {
        using namespace geos::geom;

        assert(0 != g);

        if(nullptr == extHandle) {
            return -1;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return -1;
        }

        try {
            const PrecisionModel* pm = g->getPrecisionModel();
            double cursize = pm->isFloating() ? 0 : 1.0 / pm->getScale();
            return cursize;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return -1;
    }

    int
    GEOSGeom_getDimensions_r(GEOSContextHandle_t extHandle, const Geometry* g)
    {
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            return (int) g->getDimension();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    int
    GEOSGeom_getCoordinateDimension_r(GEOSContextHandle_t extHandle, const Geometry* g)
    {
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            return g->getCoordinateDimension();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    int
    GEOSGeom_getXMin_r(GEOSContextHandle_t extHandle, const Geometry* g, double* value)
    {
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            if(g->isEmpty()) {
                return 0;
            }

            *value = g->getEnvelopeInternal()->getMinX();
            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    int
    GEOSGeom_getXMax_r(GEOSContextHandle_t extHandle, const Geometry* g, double* value)
    {
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            if(g->isEmpty()) {
                return 0;
            }

            *value = g->getEnvelopeInternal()->getMaxX();
            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    int
    GEOSGeom_getYMin_r(GEOSContextHandle_t extHandle, const Geometry* g, double* value)
    {
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            if(g->isEmpty()) {
                return 0;
            }

            *value = g->getEnvelopeInternal()->getMinY();
            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    int
    GEOSGeom_getYMax_r(GEOSContextHandle_t extHandle, const Geometry* g, double* value)
    {
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            if(g->isEmpty()) {
                return 0;
            }

            *value = g->getEnvelopeInternal()->getMaxY();
            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    Geometry*
    GEOSSimplify_r(GEOSContextHandle_t extHandle, const Geometry* g1, double tolerance)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            using namespace geos::simplify;
            Geometry::Ptr g3(DouglasPeuckerSimplifier::simplify(g1, tolerance));
            g3->setSRID(g1->getSRID());
            return g3.release();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    Geometry*
    GEOSTopologyPreserveSimplify_r(GEOSContextHandle_t extHandle, const Geometry* g1, double tolerance)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            using namespace geos::simplify;
            Geometry::Ptr g3(TopologyPreservingSimplifier::simplify(g1, tolerance));
            g3->setSRID(g1->getSRID());
            return g3.release();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }


    /* WKT Reader */
    WKTReader*
    GEOSWKTReader_create_r(GEOSContextHandle_t extHandle)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            using geos::io::WKTReader;
            return new WKTReader((GeometryFactory*)handle->geomFactory);
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    void
    GEOSWKTReader_destroy_r(GEOSContextHandle_t extHandle, WKTReader* reader)
    {
        GEOSContextHandleInternal_t* handle = 0;

        try {
            delete reader;
        }
        catch(const std::exception& e) {
            if(nullptr == extHandle) {
                return;
            }

            handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
            if(0 == handle->initialized) {
                return;
            }

            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            if(nullptr == extHandle) {
                return;
            }

            handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
            if(0 == handle->initialized) {
                return;
            }

            handle->ERROR_MESSAGE("Unknown exception thrown");
        }
    }


    Geometry*
    GEOSWKTReader_read_r(GEOSContextHandle_t extHandle, WKTReader* reader, const char* wkt)
    {
        assert(0 != reader);

        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            const std::string wktstring(wkt);
            return reader->read(wktstring).release();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    /* WKT Writer */
    WKTWriter*
    GEOSWKTWriter_create_r(GEOSContextHandle_t extHandle)
    {
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            using geos::io::WKTWriter;
            return new WKTWriter();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    void
    GEOSWKTWriter_destroy_r(GEOSContextHandle_t extHandle, WKTWriter* Writer)
    {

        GEOSContextHandleInternal_t* handle = 0;

        try {
            delete Writer;
        }
        catch(const std::exception& e) {
            if(nullptr == extHandle) {
                return;
            }

            handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
            if(0 == handle->initialized) {
                return;
            }

            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            if(nullptr == extHandle) {
                return;
            }

            handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
            if(0 == handle->initialized) {
                return;
            }

            handle->ERROR_MESSAGE("Unknown exception thrown");
        }
    }


    char*
    GEOSWKTWriter_write_r(GEOSContextHandle_t extHandle, WKTWriter* writer, const Geometry* geom)
    {
        assert(0 != writer);

        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            std::string sgeom(writer->write(geom));
            char* result = gstrdup(sgeom);
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    void
    GEOSWKTWriter_setTrim_r(GEOSContextHandle_t extHandle, WKTWriter* writer, char trim)
    {
        assert(0 != writer);

        if(nullptr == extHandle) {
            return;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return;
        }

        writer->setTrim(0 != trim);
    }

    void
    GEOSWKTWriter_setRoundingPrecision_r(GEOSContextHandle_t extHandle, WKTWriter* writer, int precision)
    {
        assert(0 != writer);

        if(nullptr == extHandle) {
            return;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return;
        }

        writer->setRoundingPrecision(precision);
    }

    void
    GEOSWKTWriter_setOutputDimension_r(GEOSContextHandle_t extHandle, WKTWriter* writer, int dim)
    {
        assert(0 != writer);

        if(nullptr == extHandle) {
            return;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return;
        }

        try {
            writer->setOutputDimension(dim);
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }
    }

    int
    GEOSWKTWriter_getOutputDimension_r(GEOSContextHandle_t extHandle, WKTWriter* writer)
    {
        assert(0 != writer);

        if(nullptr == extHandle) {
            return -1;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return -1;
        }

        int  dim = -1;

        try {
            dim = writer->getOutputDimension();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return dim;
    }

    void
    GEOSWKTWriter_setOld3D_r(GEOSContextHandle_t extHandle, WKTWriter* writer, int useOld3D)
    {
        assert(0 != writer);

        if(nullptr == extHandle) {
            return;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return;
        }

        writer->setOld3D(0 != useOld3D);
    }

    /* WKB Reader */
    WKBReader*
    GEOSWKBReader_create_r(GEOSContextHandle_t extHandle)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        using geos::io::WKBReader;
        try {
            return new WKBReader(*(GeometryFactory*)handle->geomFactory);
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    void
    GEOSWKBReader_destroy_r(GEOSContextHandle_t extHandle, WKBReader* reader)
    {
        GEOSContextHandleInternal_t* handle = 0;

        try {
            delete reader;
        }
        catch(const std::exception& e) {
            if(nullptr == extHandle) {
                return;
            }

            handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
            if(0 == handle->initialized) {
                return;
            }

            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            if(nullptr == extHandle) {
                return;
            }

            handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
            if(0 == handle->initialized) {
                return;
            }

            handle->ERROR_MESSAGE("Unknown exception thrown");
        }
    }

    struct membuf : public std::streambuf {
        membuf(char* s, std::size_t n)
        {
            setg(s, s, s + n);
        }
    };

    Geometry*
    GEOSWKBReader_read_r(GEOSContextHandle_t extHandle, WKBReader* reader, const unsigned char* wkb, size_t size)
    {
        assert(0 != reader);
        assert(0 != wkb);

        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            //std::string wkbstring(reinterpret_cast<const char*>(wkb), size); // make it binary !
            //std::istringstream is(std::ios_base::binary);
            //is.str(wkbstring);
            //is.seekg(0, std::ios::beg); // rewind reader pointer

            // http://stackoverflow.com/questions/2079912/simpler-way-to-create-a-c-memorystream-from-char-size-t-without-copying-t
            membuf mb((char*)wkb, size);
            istream is(&mb);

            return reader->read(is).release();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    Geometry*
    GEOSWKBReader_readHEX_r(GEOSContextHandle_t extHandle, WKBReader* reader, const unsigned char* hex, size_t size)
    {
        assert(0 != reader);
        assert(0 != hex);

        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            std::string hexstring(reinterpret_cast<const char*>(hex), size);
            std::istringstream is(std::ios_base::binary);
            is.str(hexstring);
            is.seekg(0, std::ios::beg); // rewind reader pointer

            return reader->readHEX(is).release();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    /* WKB Writer */
    WKBWriter*
    GEOSWKBWriter_create_r(GEOSContextHandle_t extHandle)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            using geos::io::WKBWriter;
            return new WKBWriter();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    void
    GEOSWKBWriter_destroy_r(GEOSContextHandle_t extHandle, WKBWriter* Writer)
    {
        GEOSContextHandleInternal_t* handle = 0;

        try {
            delete Writer;
        }
        catch(const std::exception& e) {
            if(nullptr == extHandle) {
                return;
            }

            handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
            if(0 == handle->initialized) {
                return;
            }

            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            if(nullptr == extHandle) {
                return;
            }

            handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
            if(0 == handle->initialized) {
                return;
            }

            handle->ERROR_MESSAGE("Unknown exception thrown");
        }
    }


    /* The caller owns the result */
    unsigned char*
    GEOSWKBWriter_write_r(GEOSContextHandle_t extHandle, WKBWriter* writer, const Geometry* geom, size_t* size)
    {
        assert(0 != writer);
        assert(0 != geom);
        assert(0 != size);

        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            std::ostringstream os(std::ios_base::binary);
            writer->write(*geom, os);

            const std::string& wkbstring = os.str();
            const std::size_t len = wkbstring.length();

            unsigned char* result = NULL;
            result = (unsigned char*) malloc(len);
            std::memcpy(result, wkbstring.c_str(), len);
            *size = len;
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }
        return NULL;
    }

    /* The caller owns the result */
    unsigned char*
    GEOSWKBWriter_writeHEX_r(GEOSContextHandle_t extHandle, WKBWriter* writer, const Geometry* geom, size_t* size)
    {
        assert(0 != writer);
        assert(0 != geom);
        assert(0 != size);

        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            std::ostringstream os(std::ios_base::binary);
            writer->writeHEX(*geom, os);
            std::string wkbstring(os.str());
            const std::size_t len = wkbstring.length();

            unsigned char* result = NULL;
            result = (unsigned char*) malloc(len);
            std::memcpy(result, wkbstring.c_str(), len);
            *size = len;
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    int
    GEOSWKBWriter_getOutputDimension_r(GEOSContextHandle_t extHandle, const GEOSWKBWriter* writer)
    {
        assert(0 != writer);

        if(nullptr == extHandle) {
            return 0;
        }

        int ret = 0;

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 != handle->initialized) {
            try {
                ret = writer->getOutputDimension();
            }
            catch(...) {
                handle->ERROR_MESSAGE("Unknown exception thrown");
            }
        }

        return ret;
    }

    void
    GEOSWKBWriter_setOutputDimension_r(GEOSContextHandle_t extHandle, GEOSWKBWriter* writer, int newDimension)
    {
        assert(0 != writer);

        if(nullptr == extHandle) {
            return;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 != handle->initialized) {
            try {
                writer->setOutputDimension(newDimension);
            }
            catch(const std::exception& e) {
                handle->ERROR_MESSAGE("%s", e.what());
            }
            catch(...) {
                handle->ERROR_MESSAGE("Unknown exception thrown");
            }
        }
    }

    int
    GEOSWKBWriter_getByteOrder_r(GEOSContextHandle_t extHandle, const GEOSWKBWriter* writer)
    {
        assert(0 != writer);

        if(nullptr == extHandle) {
            return 0;
        }

        int ret = 0;

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 != handle->initialized) {
            try {
                ret = writer->getByteOrder();
            }

            catch(...) {
                handle->ERROR_MESSAGE("Unknown exception thrown");
            }
        }

        return ret;
    }

    void
    GEOSWKBWriter_setByteOrder_r(GEOSContextHandle_t extHandle, GEOSWKBWriter* writer, int newByteOrder)
    {
        assert(0 != writer);

        if(nullptr == extHandle) {
            return;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 != handle->initialized) {
            try {
                writer->setByteOrder(newByteOrder);
            }
            catch(const std::exception& e) {
                handle->ERROR_MESSAGE("%s", e.what());
            }
            catch(...) {
                handle->ERROR_MESSAGE("Unknown exception thrown");
            }
        }
    }

    char
    GEOSWKBWriter_getIncludeSRID_r(GEOSContextHandle_t extHandle, const GEOSWKBWriter* writer)
    {
        assert(0 != writer);

        if(nullptr == extHandle) {
            return -1;
        }

        int ret = -1;

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 != handle->initialized) {
            try {
                int srid = writer->getIncludeSRID();
                ret = srid;
            }
            catch(...) {
                handle->ERROR_MESSAGE("Unknown exception thrown");
            }
        }

        return static_cast<char>(ret);
    }

    void
    GEOSWKBWriter_setIncludeSRID_r(GEOSContextHandle_t extHandle, GEOSWKBWriter* writer, const char newIncludeSRID)
    {
        assert(0 != writer);

        if(nullptr == extHandle) {
            return;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 != handle->initialized) {
            try {
                writer->setIncludeSRID(newIncludeSRID);
            }
            catch(...) {
                handle->ERROR_MESSAGE("Unknown exception thrown");
            }
        }
    }


//-----------------------------------------------------------------
// Prepared Geometry
//-----------------------------------------------------------------

    const geos::geom::prep::PreparedGeometry*
    GEOSPrepare_r(GEOSContextHandle_t extHandle, const Geometry* g)
    {
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        const geos::geom::prep::PreparedGeometry* prep = 0;

        try {
            prep = geos::geom::prep::PreparedGeometryFactory::prepare(g).release();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return prep;
    }

    void
    GEOSPreparedGeom_destroy_r(GEOSContextHandle_t extHandle, const geos::geom::prep::PreparedGeometry* a)
    {
        GEOSContextHandleInternal_t* handle = 0;

        try {
            delete a;
        }
        catch(const std::exception& e) {
            if(nullptr == extHandle) {
                return;
            }

            handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
            if(0 == handle->initialized) {
                return;
            }

            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            if(nullptr == extHandle) {
                return;
            }

            handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
            if(0 == handle->initialized) {
                return;
            }

            handle->ERROR_MESSAGE("Unknown exception thrown");
        }
    }

    char
    GEOSPreparedContains_r(GEOSContextHandle_t extHandle,
                           const geos::geom::prep::PreparedGeometry* pg, const Geometry* g)
    {
        assert(0 != pg);
        assert(0 != g);

        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            bool result = pg->contains(g);
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2;
    }

    char
    GEOSPreparedContainsProperly_r(GEOSContextHandle_t extHandle,
                                   const geos::geom::prep::PreparedGeometry* pg, const Geometry* g)
    {
        assert(0 != pg);
        assert(0 != g);

        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            bool result = pg->containsProperly(g);
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2;
    }

    char
    GEOSPreparedCoveredBy_r(GEOSContextHandle_t extHandle,
                            const geos::geom::prep::PreparedGeometry* pg, const Geometry* g)
    {
        assert(0 != pg);
        assert(0 != g);

        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            bool result = pg->coveredBy(g);
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2;
    }

    char
    GEOSPreparedCovers_r(GEOSContextHandle_t extHandle,
                         const geos::geom::prep::PreparedGeometry* pg, const Geometry* g)
    {
        assert(0 != pg);
        assert(0 != g);

        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            bool result = pg->covers(g);
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2;
    }

    char
    GEOSPreparedCrosses_r(GEOSContextHandle_t extHandle,
                          const geos::geom::prep::PreparedGeometry* pg, const Geometry* g)
    {
        assert(0 != pg);
        assert(0 != g);

        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            bool result = pg->crosses(g);
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2;
    }

    char
    GEOSPreparedDisjoint_r(GEOSContextHandle_t extHandle,
                           const geos::geom::prep::PreparedGeometry* pg, const Geometry* g)
    {
        assert(0 != pg);
        assert(0 != g);

        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            bool result = pg->disjoint(g);
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2;
    }

    char
    GEOSPreparedIntersects_r(GEOSContextHandle_t extHandle,
                             const geos::geom::prep::PreparedGeometry* pg, const Geometry* g)
    {
        assert(0 != pg);
        assert(0 != g);

        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            bool result = pg->intersects(g);
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2;
    }

    char
    GEOSPreparedOverlaps_r(GEOSContextHandle_t extHandle,
                           const geos::geom::prep::PreparedGeometry* pg, const Geometry* g)
    {
        assert(0 != pg);
        assert(0 != g);

        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            bool result = pg->overlaps(g);
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2;
    }

    char
    GEOSPreparedTouches_r(GEOSContextHandle_t extHandle,
                          const geos::geom::prep::PreparedGeometry* pg, const Geometry* g)
    {
        assert(0 != pg);
        assert(0 != g);

        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            bool result = pg->touches(g);
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2;
    }

    char
    GEOSPreparedWithin_r(GEOSContextHandle_t extHandle,
                         const geos::geom::prep::PreparedGeometry* pg, const Geometry* g)
    {
        assert(0 != pg);
        assert(0 != g);

        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            bool result = pg->within(g);
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2;
    }

//-----------------------------------------------------------------
// STRtree
//-----------------------------------------------------------------

    geos::index::strtree::STRtree*
    GEOSSTRtree_create_r(GEOSContextHandle_t extHandle,
                         size_t nodeCapacity)
    {
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        geos::index::strtree::STRtree* tree = 0;

        try {
            tree = new geos::index::strtree::STRtree(nodeCapacity);
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return tree;
    }

    void
    GEOSSTRtree_insert_r(GEOSContextHandle_t extHandle,
                         geos::index::strtree::STRtree* tree,
                         const geos::geom::Geometry* g,
                         void* item)
    {
        GEOSContextHandleInternal_t* handle = 0;
        assert(tree != 0);
        assert(g != 0);

        try {
            tree->insert(g->getEnvelopeInternal(), item);
        }
        catch(const std::exception& e) {
            if(nullptr == extHandle) {
                return;
            }

            handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
            if(0 == handle->initialized) {
                return;
            }

            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            if(nullptr == extHandle) {
                return;
            }

            handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
            if(0 == handle->initialized) {
                return;
            }

            handle->ERROR_MESSAGE("Unknown exception thrown");
        }
    }

    void
    GEOSSTRtree_query_r(GEOSContextHandle_t extHandle,
                        geos::index::strtree::STRtree* tree,
                        const geos::geom::Geometry* g,
                        GEOSQueryCallback callback,
                        void* userdata)
    {
        GEOSContextHandleInternal_t* handle = 0;
        assert(tree != 0);
        assert(g != 0);
        assert(callback != 0);

        try {
            CAPI_ItemVisitor visitor(callback, userdata);
            tree->query(g->getEnvelopeInternal(), visitor);
        }
        catch(const std::exception& e) {
            if(nullptr == extHandle) {
                return;
            }

            handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
            if(0 == handle->initialized) {
                return;
            }

            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            if(nullptr == extHandle) {
                return;
            }

            handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
            if(0 == handle->initialized) {
                return;
            }

            handle->ERROR_MESSAGE("Unknown exception thrown");
        }
    }

    const GEOSGeometry*
    GEOSSTRtree_nearest_r(GEOSContextHandle_t extHandle,
                          geos::index::strtree::STRtree* tree,
                          const geos::geom::Geometry* geom)
    {
        return (const GEOSGeometry*) GEOSSTRtree_nearest_generic_r(extHandle, tree, geom, geom, nullptr, nullptr);
    }

    const void*
    GEOSSTRtree_nearest_generic_r(GEOSContextHandle_t extHandle,
                                  geos::index::strtree::STRtree* tree,
                                  const void* item,
                                  const geos::geom::Geometry* itemEnvelope,
                                  GEOSDistanceCallback distancefn,
                                  void* userdata)
    {
        using namespace geos::index::strtree;

        GEOSContextHandleInternal_t* handle = 0;

        struct CustomItemDistance : public ItemDistance {
            CustomItemDistance(GEOSDistanceCallback p_distancefn, void* p_userdata)
                : m_distancefn(p_distancefn), m_userdata(p_userdata) {}

            GEOSDistanceCallback m_distancefn;
            void* m_userdata;

            double
            distance(const ItemBoundable* item1, const ItemBoundable* item2) override
            {
                const void* a = item1->getItem();
                const void* b = item2->getItem();
                double d;

                if(!m_distancefn(a, b, &d, m_userdata)) {
                    throw std::runtime_error(std::string("Failed to compute distance."));
                }

                return d;
            }
        };

        try {
            if(distancefn) {
                CustomItemDistance itemDistance(distancefn, userdata);
                return tree->nearestNeighbour(itemEnvelope->getEnvelopeInternal(), item, &itemDistance);
            }
            else {
                GeometryItemDistance itemDistance = GeometryItemDistance();
                return tree->nearestNeighbour(itemEnvelope->getEnvelopeInternal(), item, &itemDistance);
            }
        }
        catch(const std::exception& e) {
            if(nullptr == extHandle) {
                return NULL;
            }

            handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
            if(0 == handle->initialized) {
                return NULL;
            }

            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            if(nullptr == extHandle) {
                return NULL;
            }

            handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
            if(0 == handle->initialized) {
                return NULL;
            }

            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    void
    GEOSSTRtree_iterate_r(GEOSContextHandle_t extHandle,
                          geos::index::strtree::STRtree* tree,
                          GEOSQueryCallback callback,
                          void* userdata)
    {
        GEOSContextHandleInternal_t* handle = 0;
        assert(tree != 0);
        assert(callback != 0);

        try {
            CAPI_ItemVisitor visitor(callback, userdata);
            tree->iterate(visitor);
        }
        catch(const std::exception& e) {
            if(nullptr == extHandle) {
                return;
            }

            handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
            if(0 == handle->initialized) {
                return;
            }

            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            if(nullptr == extHandle) {
                return;
            }

            handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
            if(0 == handle->initialized) {
                return;
            }

            handle->ERROR_MESSAGE("Unknown exception thrown");
        }
    }

    char
    GEOSSTRtree_remove_r(GEOSContextHandle_t extHandle,
                         geos::index::strtree::STRtree* tree,
                         const geos::geom::Geometry* g,
                         void* item)
    {
        assert(0 != tree);
        assert(0 != g);

        if(nullptr == extHandle) {
            return 2;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            bool result = tree->remove(g->getEnvelopeInternal(), item);
            return result;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 2;
    }

    void
    GEOSSTRtree_destroy_r(GEOSContextHandle_t extHandle,
                          geos::index::strtree::STRtree* tree)
    {
        GEOSContextHandleInternal_t* handle = 0;

        try {
            delete tree;
        }
        catch(const std::exception& e) {
            if(nullptr == extHandle) {
                return;
            }

            handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
            if(0 == handle->initialized) {
                return;
            }

            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            if(nullptr == extHandle) {
                return;
            }

            handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
            if(0 == handle->initialized) {
                return;
            }

            handle->ERROR_MESSAGE("Unknown exception thrown");
        }
    }

    double
    GEOSProject_r(GEOSContextHandle_t extHandle,
                  const Geometry* g,
                  const Geometry* p)
    {
        if(nullptr == extHandle) {
            return -1.0;
        }
        GEOSContextHandleInternal_t* handle =
            reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(handle->initialized == 0) {
            return -1.0;
        }

        const geos::geom::Point* point = dynamic_cast<const geos::geom::Point*>(p);
        if(!point) {
            handle->ERROR_MESSAGE("third argument of GEOSProject_r must be Point*");
            return -1.0;
        }

        const geos::geom::Coordinate* inputPt = p->getCoordinate();

        try {
            return geos::linearref::LengthIndexedLine(g).project(*inputPt);
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
            return -1.0;
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
            return -1.0;
        }
    }


    Geometry*
    GEOSInterpolate_r(GEOSContextHandle_t extHandle, const Geometry* g, double d)
    {
        if(nullptr == extHandle) {
            return 0;
        }
        GEOSContextHandleInternal_t* handle =
            reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(handle->initialized == 0) {
            return 0;
        }

        try {
            geos::linearref::LengthIndexedLine lil(g);
            geos::geom::Coordinate coord = lil.extractPoint(d);
            const GeometryFactory* gf = handle->geomFactory;
            Geometry* point = gf->createPoint(coord);
            point->setSRID(g->getSRID());
            return point;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
            return 0;
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
            return 0;
        }
    }


    double
    GEOSProjectNormalized_r(GEOSContextHandle_t extHandle, const Geometry* g,
                            const Geometry* p)
    {

        double length;
        GEOSLength_r(extHandle, g, &length);
        return GEOSProject_r(extHandle, g, p) / length;
    }


    Geometry*
    GEOSInterpolateNormalized_r(GEOSContextHandle_t extHandle, const Geometry* g,
                                double d)
    {
        double length;
        GEOSLength_r(extHandle, g, &length);
        return GEOSInterpolate_r(extHandle, g, d * length);
    }

    GEOSGeometry*
    GEOSGeom_extractUniquePoints_r(GEOSContextHandle_t extHandle,
                                   const GEOSGeometry* g)
    {
        if(nullptr == extHandle) {
            return 0;
        }
        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(handle->initialized == 0) {
            return 0;
        }

        using namespace geos::geom;
        using namespace geos::util;

        try {

            /* 1: extract points */
            std::vector<const Coordinate*> coords;
            UniqueCoordinateArrayFilter filter(coords);
            g->apply_ro(&filter);

            /* 2: for each point, create a geometry and put into a vector */
            std::vector<Geometry*>* points = new std::vector<Geometry*>();
            points->reserve(coords.size());
            const GeometryFactory* factory = g->getFactory();
            for(std::vector<const Coordinate*>::iterator it = coords.begin(),
                    itE = coords.end();
                    it != itE; ++it) {
                Geometry* point = factory->createPoint(*(*it));
                points->push_back(point);
            }

            /* 3: create a multipoint */
            Geometry* out = factory->createMultiPoint(points);
            out->setSRID(g->getSRID());
            return out;

        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
            return 0;
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
            return 0;
        }
    }

    int GEOSOrientationIndex_r(GEOSContextHandle_t extHandle,
                               double Ax, double Ay, double Bx, double By, double Px, double Py)
    {
        GEOSContextHandleInternal_t* handle = 0;

        using geos::geom::Coordinate;
        using geos::algorithm::Orientation;

        if(nullptr == extHandle) {
            return 2;
        }

        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 2;
        }

        try {
            Coordinate A(Ax, Ay);
            Coordinate B(Bx, By);
            Coordinate P(Px, Py);
            return Orientation::index(A, B, P);
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
            return 2;
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
            return 2;
        }
    }

    GEOSGeometry*
    GEOSSharedPaths_r(GEOSContextHandle_t extHandle, const GEOSGeometry* g1, const GEOSGeometry* g2)
    {
        using namespace geos::operation::sharedpaths;

        if(nullptr == extHandle) {
            return 0;
        }
        GEOSContextHandleInternal_t* handle =
            reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(handle->initialized == 0) {
            return 0;
        }

        SharedPathsOp::PathList forw, back;
        try {
            SharedPathsOp::sharedPathsOp(*g1, *g2, forw, back);
        }
        catch(const std::exception& e) {
            SharedPathsOp::clearEdges(forw);
            SharedPathsOp::clearEdges(back);
            handle->ERROR_MESSAGE("%s", e.what());
            return 0;
        }
        catch(...) {
            SharedPathsOp::clearEdges(forw);
            SharedPathsOp::clearEdges(back);
            handle->ERROR_MESSAGE("Unknown exception thrown");
            return 0;
        }

        // Now forw and back have the geoms we want to use to construct
        // our output GeometryCollections...

        const GeometryFactory* factory = g1->getFactory();
        size_t count;

        std::unique_ptr< std::vector<Geometry*> > out1(
            new std::vector<Geometry*>()
        );
        count = forw.size();
        out1->reserve(count);
        for(size_t i = 0; i < count; ++i) {
            out1->push_back(forw[i]);
        }
        std::unique_ptr<Geometry> out1g(
            factory->createMultiLineString(out1.release())
        );

        std::unique_ptr< std::vector<Geometry*> > out2(
            new std::vector<Geometry*>()
        );
        count = back.size();
        out2->reserve(count);
        for(size_t i = 0; i < count; ++i) {
            out2->push_back(back[i]);
        }
        std::unique_ptr<Geometry> out2g(
            factory->createMultiLineString(out2.release())
        );

        std::unique_ptr< std::vector<Geometry*> > out(
            new std::vector<Geometry*>()
        );
        out->reserve(2);
        out->push_back(out1g.release());
        out->push_back(out2g.release());

        std::unique_ptr<Geometry> outg(
            factory->createGeometryCollection(out.release())
        );

        outg->setSRID(g1->getSRID());
        return outg.release();

    }

    GEOSGeometry*
    GEOSSnap_r(GEOSContextHandle_t extHandle, const GEOSGeometry* g1,
               const GEOSGeometry* g2, double tolerance)
    {
        using namespace geos::operation::overlay::snap;

        if(nullptr == extHandle) {
            return 0;
        }
        GEOSContextHandleInternal_t* handle =
            reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(handle->initialized == 0) {
            return 0;
        }

        try {
            GeometrySnapper snapper(*g1);
            std::unique_ptr<Geometry> ret = snapper.snapTo(*g2, tolerance);
            ret->setSRID(g1->getSRID());
            return ret.release();
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
            return 0;
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
            return 0;
        }
    }

    BufferParameters*
    GEOSBufferParams_create_r(GEOSContextHandle_t extHandle)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            BufferParameters* p = new BufferParameters();
            return p;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    void
    GEOSBufferParams_destroy_r(GEOSContextHandle_t extHandle, BufferParameters* p)
    {
        (void)extHandle;
        delete p;
    }

    int
    GEOSBufferParams_setEndCapStyle_r(GEOSContextHandle_t extHandle,
                                      GEOSBufferParams* p, int style)
    {
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            if(style > BufferParameters::CAP_SQUARE) {
                throw IllegalArgumentException("Invalid buffer endCap style");
            }
            p->setEndCapStyle(static_cast<BufferParameters::EndCapStyle>(style));
            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    int
    GEOSBufferParams_setJoinStyle_r(GEOSContextHandle_t extHandle,
                                    GEOSBufferParams* p, int style)
    {
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            if(style > BufferParameters::JOIN_BEVEL) {
                throw IllegalArgumentException("Invalid buffer join style");
            }
            p->setJoinStyle(static_cast<BufferParameters::JoinStyle>(style));
            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    int
    GEOSBufferParams_setMitreLimit_r(GEOSContextHandle_t extHandle,
                                     GEOSBufferParams* p, double limit)
    {
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            p->setMitreLimit(limit);
            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    int
    GEOSBufferParams_setQuadrantSegments_r(GEOSContextHandle_t extHandle,
                                           GEOSBufferParams* p, int segs)
    {
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            p->setQuadrantSegments(segs);
            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    int
    GEOSBufferParams_setSingleSided_r(GEOSContextHandle_t extHandle,
                                      GEOSBufferParams* p, int ss)
    {
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            p->setSingleSided((ss != 0));
            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

    Geometry*
    GEOSBufferWithParams_r(GEOSContextHandle_t extHandle, const Geometry* g1, const BufferParameters* bp, double width)
    {
        using geos::operation::buffer::BufferOp;

        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        try {
            BufferOp op(g1, *bp);
            Geometry* g3 = op.getResultGeometry(width);
            g3->setSRID(g1->getSRID());
            return g3;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    Geometry*
    GEOSDelaunayTriangulation_r(GEOSContextHandle_t extHandle, const Geometry* g1, double tolerance, int onlyEdges)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        using geos::triangulate::DelaunayTriangulationBuilder;

        try {
            DelaunayTriangulationBuilder builder;
            builder.setTolerance(tolerance);
            builder.setSites(*g1);

            if(onlyEdges) {
                Geometry* out = builder.getEdges(*g1->getFactory()).release();
                out->setSRID(g1->getSRID());
                return out;
            }
            else {
                Geometry* out = builder.getTriangles(*g1->getFactory()).release();
                out->setSRID(g1->getSRID());
                return out;
            }

        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }
    Geometry*
    GEOSVoronoiDiagram_r(GEOSContextHandle_t extHandle, const Geometry* g1, const Geometry* env, double tolerance,
                         int onlyEdges)
    {
        if(nullptr == extHandle) {
            return NULL;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return NULL;
        }

        using geos::triangulate::VoronoiDiagramBuilder;

        try {
            VoronoiDiagramBuilder builder;
            builder.setSites(*g1);
            builder.setTolerance(tolerance);
            if(env) {
                builder.setClipEnvelope(env->getEnvelopeInternal());
            }
            if(onlyEdges) {
                Geometry* out = builder.getDiagramEdges(*g1->getFactory()).release();
                out->setSRID(g1->getSRID());
                return out;
            }
            else {
                Geometry* out = builder.getDiagram(*g1->getFactory()).release();
                out->setSRID(g1->getSRID());
                return out;
            }
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return NULL;
    }

    int
    GEOSSegmentIntersection_r(GEOSContextHandle_t extHandle,
                              double ax0, double ay0, double ax1, double ay1,
                              double bx0, double by0, double bx1, double by1,
                              double* cx, double* cy)
    {
        if(nullptr == extHandle) {
            return 0;
        }

        GEOSContextHandleInternal_t* handle = 0;
        handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
        if(0 == handle->initialized) {
            return 0;
        }

        try {
            geos::geom::LineSegment a(ax0, ay0, ax1, ay1);
            geos::geom::LineSegment b(bx0, by0, bx1, by1);
            geos::geom::Coordinate isect = a.intersection(b);

            if(isect.isNull()) {
                return -1;
            }

            *cx = isect.x;
            *cy = isect.y;

            return 1;
        }
        catch(const std::exception& e) {
            handle->ERROR_MESSAGE("%s", e.what());
        }
        catch(...) {
            handle->ERROR_MESSAGE("Unknown exception thrown");
        }

        return 0;
    }

} /* extern "C" */

