$OpenBSD: patch-src_common_Projection_cpp,v 1.1 2021/04/12 10:17:11 landry Exp $

Index: src/common/Projection.cpp
--- src/common/Projection.cpp.orig
+++ src/common/Projection.cpp
@@ -12,107 +12,50 @@
 #define EQUATORIALMETERHALFCIRCUMFERENCE  20037508.34
 #define EQUATORIALMETERPERDEGREE    222638.981555556
 
-#include "Node.h"
-
-Projection::Projection(void)
+ProjectionBackend::ProjectionBackend(QString initProjection, std::function<QString(QString)> mapProjectionName)
     : ProjectionRevision(0)
     , IsMercator(false)
     , IsLatLong(false)
+    , mapProjectionName(mapProjectionName)
 {
-#if defined(Q_OS_WIN) && !defined(_MOBILE)
+
+
+    projCtx = std::shared_ptr<PJ_CONTEXT>(proj_context_create(), proj_context_destroy);
+#if defined(Q_OS_WIN)
     QString pdir(QDir::toNativeSeparators(qApp->applicationDirPath() + "/" STRINGIFY(SHARE_DIR) "/proj"));
     const char* proj_dir = pdir.toUtf8().constData();
-    //    const char* proj_dir = "E:\\cbro\\src\\merkaartor-devel\\binaries\\bin\\share\\proj";
-    pj_set_searchpath(1, &proj_dir);
+    proj_context_set_search_paths(projCtx.get(), 1, &proj_dir);
 #endif // Q_OS_WIN
-
-
-#ifndef _MOBILE
-    theProj = NULL;
-    theWGS84Proj = Projection::getProjection("+proj=longlat +ellps=WGS84 +datum=WGS84");
-    setProjectionType(M_PREFS->getProjectionType());
-#endif
+    projTransform = std::shared_ptr<PJ>(nullptr);
+    projMutex = std::shared_ptr<QMutex>(new QMutex());
+    setProjectionType(initProjection);
 }
 
-Projection::~Projection(void)
+QPointF ProjectionBackend::project(const QPointF & Map) const
 {
-    /* TODO: pj_free should be called, but it segfaults if two of the same
-     * Projection objects have the same projPJ. A better machinism, perhaps
-     * projPJ caching, should be provided.
-     *
-     * In the meantime, pj_free is not called, which does little harm as it's
-     * usually called at exit only.
-     * */
-#ifndef _MOBILE
-    if (theProj) {
-        //pj_free(theProj);
-    }
-#endif // _MOBILE
-}
-
-QPointF Projection::inverse2Point(const QPointF & Map) const
-{
-    if  (IsLatLong)
-        return latlonInverse(Map);
-    else
-        if  (IsMercator)
-            return mercatorInverse(Map);
-#ifndef _MOBILE
-        else
-            return projInverse(Map);
-#endif
-    return QPointF();
-}
-
-QPointF Projection::project(const QPointF & Map) const
-{
     if  (IsMercator)
         return mercatorProject(Map);
-    else
-        if  (IsLatLong)
-            return latlonProject(Map);
-#ifndef _MOBILE
-        else
-            return projProject(Map);
-#endif
-    return QPointF();
+    if  (IsLatLong)
+        return latlonProject(Map);
+    return projProject(Map);
 }
 
-QPointF Projection::project(Node* aNode) const
+QLineF ProjectionBackend::project(const QLineF & Map) const
 {
-    return project(aNode->position());
+    return QLineF(project(Map.p1()), project(Map.p2()));
 }
 
-QLineF Projection::project(const QLineF & Map) const
-{
-    if  (IsMercator)
-        return QLineF (mercatorProject(Map.p1()), mercatorProject(Map.p2()));
-    else
-        if  (IsLatLong)
-            return QLineF (latlonProject(Map.p1()), latlonProject(Map.p2()));
-#ifndef _MOBILE
-        else
-            return QLineF(projProject(Map.p1()), projProject(Map.p2()));
-#endif
-    return QLineF();
-}
 
-
-Coord Projection::inverse2Coord(const QPointF & projPoint) const
+QPointF ProjectionBackend::inverse(const QPointF & projPoint) const
 {
-    if  (IsLatLong)
+    if (IsLatLong)
         return latlonInverse(projPoint);
-    else
-        if  (IsMercator)
-            return mercatorInverse(projPoint);
-#ifndef _MOBILE
-        else
-            return projInverse(projPoint);
-#endif
-    return Coord();
+    if (IsMercator)
+        return mercatorInverse(projPoint);
+    return projInverse(projPoint);
 }
 
-QRectF Projection::toProjectedRectF(const QRectF& Viewport, const QRect& screen) const
+QRectF ProjectionBackend::toProjectedRectF(const QRectF& Viewport, const QRect& screen) const
 {
     QPointF tl, br;
     QRectF pViewport;
@@ -143,88 +86,55 @@ QRectF Projection::toProjectedRectF(const QRectF& View
     return pViewport;
 }
 
-CoordBox Projection::fromProjectedRectF(const QRectF& Viewport) const
+CoordBox ProjectionBackend::fromProjectedRectF(const QRectF& Viewport) const
 {
     Coord tl, br;
     CoordBox bbox;
 
-    tl = inverse2Coord(Viewport.topLeft());
-    br = inverse2Coord(Viewport.bottomRight());
+    tl = inverse(Viewport.topLeft());
+    br = inverse(Viewport.bottomRight());
     bbox = CoordBox(tl, br);
 
     return bbox;
 }
 
-#ifndef _MOBILE
-
-void Projection::projTransform(ProjProjection srcdefn,
-                               ProjProjection dstdefn,
-                               long point_count, int point_offset, qreal *x, qreal *y, qreal *z )
+QPointF ProjectionBackend::projProject(const QPointF & Map) const
 {
-    pj_transform(srcdefn, dstdefn, point_count, point_offset, (double *)x, (double *)y, (double *)z);
+    QMutexLocker locker(projMutex.get());
+    auto trans = proj_trans(projTransform.get(), PJ_DIRECTION::PJ_FWD, {{Map.x(), Map.y(), 0}});
+    //qDebug() << "Project(fromWSG84, " << getProjectionType() << "): " << Map << " -> " << qSetRealNumberPrecision(20) << x << "," << y;
+    return QPointF(trans.xy.x, trans.xy.y);
 }
 
-void Projection::projTransformFromWGS84(long point_count, int point_offset, qreal *x, qreal *y, qreal *z ) const
+Coord ProjectionBackend::projInverse(const QPointF & pProj) const
 {
-    pj_transform (theWGS84Proj, theProj, point_count, point_offset, (double *)x, (double *)y, (double *)z);
+    QMutexLocker locker(projMutex.get());
+    auto trans = proj_trans(projTransform.get(), PJ_DIRECTION::PJ_INV, {{pProj.x(), pProj.y(), 0}});
+    return Coord(trans.xy.x, trans.xy.y);
 }
 
-void Projection::projTransformToWGS84(long point_count, int point_offset, qreal *x, qreal *y, qreal *z ) const
+bool ProjectionBackend::projIsLatLong() const
 {
-    pj_transform(theProj, theWGS84Proj, point_count, point_offset, (double *)x, (double *)y, (double *)z);
-}
-
-QPointF Projection::projProject(const QPointF & Map) const
-{
-    qreal x = angToRad(Map.x());
-    qreal y = angToRad(Map.y());
-
-    projTransformFromWGS84(1, 0, &x, &y, NULL);
-
-    return QPointF(x, y);
-}
-
-Coord Projection::projInverse(const QPointF & pProj) const
-{
-    qreal x = pProj.x();
-    qreal y = pProj.y();
-
-    projTransformToWGS84(1, 0, &x, &y, NULL);
-
-    return Coord(radToAng(x), radToAng(y));
-}
-#endif // _MOBILE
-
-bool Projection::projIsLatLong() const
-{
     return IsLatLong;
 }
 
-//bool Projection::projIsMercator()
-//{
-//    return IsMercator;
-//}
-
-
-#ifndef _MOBILE
-ProjProjection Projection::getProjection(QString projString)
+PJ* ProjectionBackend::getProjection(QString projString)
 {
-    ProjProjection theProj = pj_init_plus(QString("%1 +over").arg(projString).toLatin1());
-    return theProj;
+    QString WGS84("+proj=longlat +ellps=WGS84 +datum=WGS84 +xy_in=deg");
+    PJ* proj = proj_create_crs_to_crs(projCtx.get(), WGS84.toLatin1(), projString.toLatin1(), 0);
+    if (!proj) {
+            qDebug() << "Failed to initialize projection" << WGS84 << "to" << projString << "with error:" << proj_errno_string(proj_errno(nullptr));
+    }
+    return proj;
 }
-#endif // _MOBILE
 
-bool Projection::setProjectionType(QString aProjectionType)
+bool ProjectionBackend::setProjectionType(QString aProjectionType)
 {
+    QMutexLocker locker(projMutex.get());
     if (aProjectionType == projType)
         return true;
 
-#ifndef _MOBILE
-    if (theProj) {
-        pj_free(theProj);
-        theProj = NULL;
-    }
-#endif // _MOBILE
+    projTransform = nullptr;
 
     ProjectionRevision++;
     projType = aProjectionType;
@@ -246,77 +156,60 @@ bool Projection::setProjectionType(QString aProjection
         return true;
     }
     // Hardcode "lat/long " projection
-    if (
-            projType.toUpper().contains("EPSG:4326")
-            )
+    if ( projType.toUpper() == "EPSG:4326" )
     {
         IsLatLong = true;
         projType = "EPSG:4326";
         return true;
     }
 
-#ifndef _MOBILE
-    try {
-        projProj4 = M_PREFS->getProjection(aProjectionType).projection;
-        theProj = getProjection(projProj4);
-        if (!theProj) {
-            projType = "EPSG:3857";
-            IsMercator = true;
-            return false;
-        }
-        //        else {
-        //            if (pj_is_latlong(theProj))
-        //                projType = "EPSG:4326";
-        //                IsLatLong = true;
-        //        }
-    } catch (...) {
+    projProj4 = mapProjectionName(aProjectionType);
+    projTransform = std::shared_ptr<PJ>(getProjection(projProj4), proj_destroy);
+    if (!projTransform) {
+        // Fall back to the EPSG:3857 and return false. getProjection already logged the error into qDebug().
+        projType = "EPSG:3857";
+        IsMercator = true;
         return false;
     }
-    return (theProj != NULL || IsLatLong || IsMercator);
-#else
-    return false;
-#endif // _MOBILE
+    return (projTransform != NULL || IsLatLong || IsMercator);
 }
 
-QString Projection::getProjectionType() const
+QString ProjectionBackend::getProjectionType() const
 {
     return projType;
 }
 
-QString Projection::getProjectionProj4() const
+QString ProjectionBackend::getProjectionProj4() const
 {
-    if  (IsLatLong)
+  QMutexLocker locker(projMutex.get());
+    if (IsLatLong)
         return "+init=EPSG:4326";
-    else if  (IsMercator)
+    if (IsMercator)
         return "+init=EPSG:3857";
-#ifndef _MOBILE
-    else
-        return QString(pj_get_def(theProj, 0));
-#endif
-    return QString();
+    return QString(proj_pj_info(projTransform.get()).definition);
 }
 
-int Projection::projectionRevision() const
+int ProjectionBackend::projectionRevision() const
 {
     return ProjectionRevision;
 }
 
 // Common routines
 
-qreal Projection::latAnglePerM() const
+qreal ProjectionBackend::latAnglePerM() const
 {
     qreal LengthOfOneDegreeLat = EQUATORIALRADIUS * M_PI / 180;
     return 1 / LengthOfOneDegreeLat;
 }
 
-qreal Projection::lonAnglePerM(qreal Lat) const
+qreal ProjectionBackend::lonAnglePerM(qreal Lat) const
 {
     qreal LengthOfOneDegreeLat = EQUATORIALRADIUS * M_PI / 180;
     qreal LengthOfOneDegreeLon = LengthOfOneDegreeLat * fabs(cos(Lat));
     return 1 / LengthOfOneDegreeLon;
 }
 
-bool Projection::toXML(QXmlStreamWriter& stream)
+bool ProjectionBackend::toXML(QXmlStreamWriter& stream)
 {
     bool OK = true;
 
@@ -331,7 +224,7 @@ bool Projection::toXML(QXmlStreamWriter& stream)
     return OK;
 }
 
-void Projection::fromXML(QXmlStreamReader& stream)
+void ProjectionBackend::fromXML(QXmlStreamReader& stream)
 {
     if (stream.name() == "Projection") {
         QString proj;
@@ -349,7 +242,7 @@ void Projection::fromXML(QXmlStreamReader& stream)
     }
 }
 
-QPointF Projection::mercatorProject(const QPointF& c) const
+QPointF ProjectionBackend::mercatorProject(const QPointF& c) const
 {
     qreal x = c.x() / 180. * EQUATORIALMETERHALFCIRCUMFERENCE;
     qreal y = log(tan(angToRad(c.y())) + 1/cos(angToRad(c.y()))) / M_PI * (EQUATORIALMETERHALFCIRCUMFERENCE);
@@ -357,7 +250,7 @@ QPointF Projection::mercatorProject(const QPointF& c) 
     return QPointF(x, y);
 }
 
-Coord Projection::mercatorInverse(const QPointF& point) const
+Coord ProjectionBackend::mercatorInverse(const QPointF& point) const
 {
     qreal longitude = point.x()*180.0/EQUATORIALMETERHALFCIRCUMFERENCE;
     qreal latitude = radToAng(atan(sinh(point.y()/EQUATORIALMETERHALFCIRCUMFERENCE*M_PI)));
