/** * @RDE_HEADER@ * LtsCoordinate * $Id$ * $HeadURL$ * $Author$ * Created by Norbert Thoden, ESVG, 2024-08-27 */ #pragma once #include #include // Core::GeoPosition #include // UtmCoordinate #include // Point3D #include #include #include // std::function #include #include // std::is_same namespace LtsCoordTrans { using UtmCoordinate2D = LtsCoordTrans::UtmCoordinate; using QPoint3D = LtsUtils::QPoint3D; //------------------------------------------------------------------------------ /*! * \brief The LtsCoordinate class combines the DDS class Core::GeoPosition and LtsCoordTrans::UtmCoordinate * \section secMotivation Motivation * In several circumstances the required data is available as geo (lat/lon in radiant). * For calculation and (visual) verification purposes, a conversion to utm is really often required or at least helpful. * The amount for code to achieve this, should be reduced. An maybe another benefit is to reduce the number of conversion to/from Utm. * * \section secApproach Approach * The LtsCoordinate should be constructed using Core::GeoPosition and LtsCoordTrans::UtmCoordinate. * Efficient methods enable access to both member data (Core::GeoPosition and LtsCoordTrans::UtmCoordinate). * Additionally methods enable conversion to common data types like QPointF and QPoint3D. * * \section secConstruction Construction with UtmCoordinate * A UtmCoordinate could arise in it´s original zone, but also in a different zone. * (For this keep area effect simulations a the edge of an utm zone in mind.) * To avoid the main utm member to be in the 'wrong' zone, the ctor for UtmCoordinate ist deleted. * For this case the static fromUtmCoordinate is recommeded. * * \section secElevation Elevation specific * One static callback enable access to the LtsElvationManager. * After a proper initialisation the elevation value could be retrived (and implicitly cached) * Be aware that the z-value of the LtsCoordinate stays untouched! * * To update the z-value, setZValueAccordingElevation may be helpful. * Of cource, explicit access is enabled by rz(), z(), setZ(); * \section secConvenience More Convenience * Is given by heightOverElevation(), distance2D(), distance3D(). * * \section secPerf Performance considerations * The operator<< is defined to improve logging. * But be aware that this one writes the current data, _without_ executing any data conversion. * toString() acts similar. * If you need full data, you could use toString(true); * In that case you get something like: \n * "LtsCoordinate(m_geo=[[lon: 0.200706392814992, lat: 0.914792952478694]z: 123], m_utm2D.x 670000.499999998, m_utm2D.y 5810000.5, m_utm2D.zone 32, m_utm2D.mgrs '32U PD 70000 10000', elevation 75.000)" * * Please see also the UnitTest UnitTestCoordinate. */ class LTSCOORDINATE_EXPORT LtsCoordinate { public: /*! \name Creation */ //@{ // For elevation a value of '0' should be valid. // So an invalid zValue has to be different: choose NaN // To be consistent to default constructor of Core::GeoPosition, the (invalid) default value for lon, lat is '0': explicit LtsCoordinate(double lon = 0, double lat = 0, double z = std::nan("")) : m_geo(Core::GeoPosition(lon, lat, z)) {} explicit LtsCoordinate(const QPointF &lonLat, double z = std::nan("")) : m_geo(Core::GeoPosition(lonLat.x(), lonLat.y(), z)) {} explicit LtsCoordinate(const Core::GeoPosition &geoPos) : m_geo(geoPos) {} explicit LtsCoordinate(const Core::GeoPosition2D &geoPos2D) : m_geo(geoPos2D.lon(), geoPos2D.lat(), std::nan("")) {} // if the overload causes problem later on, it will be hard to find // So use ::fromUtmCoordinate() LtsCoordinate(const LtsCoordTrans::UtmCoordinate &utm2D, double zValue) = delete; static LtsCoordinate fromGeoPosition(double lon, double lat, double z = std::nan("")) { return LtsCoordinate(lon, lat, z); } // utm2D should be set!! static LtsCoordinate fromUtmCoordinate(const LtsCoordTrans::UtmCoordinate &utm2D, double zValue) { Q_ASSERT_X(utm2D.zone > 0, "LtsCoordinate", "zone has to be set"); // maybe utm2D is not in the 'default' zone. // so convert to geo... const QPointF &geoPnt = utm2D.geographic(); // ... and initialize with geo: return LtsCoordinate(geoPnt.x(), geoPnt.y(), zValue); } static LtsCoordinate fromUtm(const QPointF &utm2d, int zone, double zValue) { const LtsCoordTrans::UtmCoordinate utmCoord(zone, utm2d.x(), utm2d.y()); const QPointF &geoPnt = utmCoord.geographic(); return LtsCoordinate(geoPnt.x(), geoPnt.y(), zValue); } //@} /*! \name Helper */ //@{ bool isValid() const { return hasGeo() || hasUtm(); } bool hasGeo() const { return !(valueIsNullOrNan(m_geo.lon()) || valueIsNullOrNan(m_geo.lat())); } bool hasUtm() const { return m_utm2D.isValid(); } static bool valueIsNullOrNan(double value) { return qFuzzyIsNull(value) || std::isnan(value); } QString mgrs(int prec = 5, bool withSpaces = true) const; QString mgrsWithZ(int prec = 5, bool withSpaces = true) const; QString toString(bool fullAndExpensive = false) const; // for debugging and unit test purpose: static void resetConversionCounter(); static long getConversionCounter(); //@} /*! \name Access, part 1 - maybe with Conversion */ //@{ const Core::GeoPosition &geoPosition() const { return m_geo; } const Core::GeoPosition2D &geoPosition2D() const { return m_geo; // implicit downcast } const LtsCoordTrans::UtmCoordinate &utmCoordinate() const { if (!hasUtm()) convertGeoToUtm(); return m_utm2D; } // access z: double &rz() { return m_geo.z(); } double z() const { return m_geo.z(); } void setZ(double value) { m_geo.z(value); } LtsCoordTrans::UtmCoordinate utmCoordinateDiffZone(int zone) const { // act defensive: to not change m_utm2D to another zone implicit. // In case of different zone, cache that zone: m_utm2D_differentZone = LtsCoordTrans::UtmCoordinate::fromGeographic(m_geo.lon(), m_geo.lat(), zone); return m_utm2D_differentZone; } //@} /*! \name Access, part 2 - with Conversion */ //@{ template T toUtm(int zone = -1) const { T obj; helperToUtm(obj, zone); return obj; // https://developers.redhat.com/blog/2019/04/12/understanding-when-not-to-stdmove-in-c // return std::move(obj); // t.C:8:20: warning: moving a local object in a return statement prevents copy elision } QPointF toUtmPointF() const { return toUtm(); } QPoint3D toUtmPoint3D() const { return toUtm(); } QPointF toGeoPointF() const { return QPointF (m_geo.lon(), m_geo.lat()); } QPoint3D toGeoPoint3D() const { return QPoint3D(m_geo.lon(), m_geo.lat(), m_geo.z() ); } UtmCoordinate3D toUtmCoordinate3D() const { return UtmCoordinate3D(utmCoordinate(), m_geo.z()); } //@} /*! \name Elevation specific*/ //@{ using elevationCbFunc = std::function; // (lon_rad, double lat_rad, defaultValue, bool*) // // preperation: // static void setElevationCallback(const elevationCbFunc &elevationCB); static bool hasElevationCallback(); // // unconditional: // double elevationAt(double defaultElevation = 0.0, bool *bIsCovered = nullptr) const; // maybe: double elevationAt(bool *b_dataAvailable) const; void setZValueAccordingElevation(double defaultElevation = 0.0); static double elevationAt(const LtsCoordinate &coordinate, double defaultElevation = 0.0, bool *bIsCovered = nullptr); // // conditional: // // szenario: // you have many (participant)positions; // Some already have a zValue (airCraft: 'height' is equal to 'above baseSurface') // but some stuck at 0.0 (e.g. human, vehicle in the field, not in the city) // code my look like: // LtsCoordinate <sPosRef = partPos.LtsCoordinate(); // ltsPosRef.replaceZValueByElevationIfValue(0.0); void replaceZValueByElevationIfValue(double zValueToBeReplaced = std::nan(""), double defaultElevation = 0.0); //@} /*! \name Calculation */ //@{ double heightOverElevation() const { return m_geo.z() - elevationAt(); } double distanceUtm2D(const LtsCoordinate &other) const { // pay attention to the utm zone: // maybe zones are '-1' but not withn the SAME zone!! const LtsCoordTrans::UtmCoordinate &thisUtm2D = utmCoordinate(); // implicit m_utm2D.zone will be set const LtsCoordTrans::UtmCoordinate &otherUtm2D = other.utmCoordinateDiffZone(m_utm2D.zone); return LtsCoordTrans::distance(thisUtm2D, otherUtm2D); } double distanceUtm3D(const LtsCoordinate &other) const { // pay attention to the utm zone: // maybe zones are '-1' but not withn the SAME zone!! const QPoint3D &thisUtm3D = toUtm(); // implicit m_utm2D.zone will be set const QPoint3D &otherUtm3D = other.toUtm(m_utm2D.zone); const double dist3D = (thisUtm3D - otherUtm3D).length(); Q_ASSERT_X(!std::isnan(dist3D), "LtsCoordinate", "ERROR, distance3D is NaN"); return dist3D; } // angle in degree and compass (North is 0, clockwise) // 0 // | // | // 270 --------- 90 // | // | // 180 static double angleDegreeUtm2D(const LtsCoordinate &coord1, const LtsCoordinate &coord2); //@} protected: // you should know, what you are doing: void clearGeo() const { clearGeoLonLat(); m_geo.z(std::nan("")); } void clearGeoLonLat() const { m_geo.lon(0); m_geo.lat(0); } void clearUtm() const { m_utm2D = m_utm2D_differentZone = UtmCoordinate2D(); } // both!! void clearElevation() const { m_elevation = std::nan(""); } void clear() const { clearGeo(); clearUtm(); clearElevation(); } const LtsCoordTrans::UtmCoordinate &utmCoordinateInternal(int zone = -1) const; void convertGeoToUtm() const; protected: // helper for template <> utm() void helperToUtm(QPointF &pntRef, int zone = -1) const { const auto &utmTemp = utmCoordinateInternal(zone); pntRef.rx() = utmTemp.x; pntRef.ry() = utmTemp.y; } void helperToUtm(QPoint3D &pntRef, int zone = -1) const { const auto &utmTemp = utmCoordinateInternal(zone); pntRef.rx() = utmTemp.x; pntRef.ry() = utmTemp.y; pntRef.rz() = m_geo.z(); } protected: mutable Core::GeoPosition m_geo; mutable UtmCoordinate2D m_utm2D; mutable UtmCoordinate2D m_utm2D_differentZone; mutable double m_elevation{std::nan("")}; // means: above msl or in other words: baseSurface static elevationCbFunc m_getElevationCB; // elevationAsReturn (lon_rad, double lat_rad, defaultValue) }; /*! * \brief The LtsCoordinateNoImplicitCopy class is designed for a position storage * Maybe a handler storing a lot of participant positions. * To guarantee the caching to be used, copying must be avoid. * \note * see also https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Non-copyable_Mixin */ class LTSCOORDINATE_EXPORT LtsCoordinateNoImplicitCopy : public LtsCoordinate { public: using BaseClass = LtsCoordinate; // Rule of three: dtor, copy, assignment: LtsCoordinateNoImplicitCopy() = default; // see below explicit LtsCoordinateNoImplicitCopy (const LtsCoordinateNoImplicitCopy &) = default; // ^^^^^^ // An explicit copy-constructor will prevent the compiler from calling // a copy-constructor implicitly. // I.e., passing an object by value and returning an object by value // will be disabled for the class having an explicit copy-constructor. // However, making explicit copies is allowed. // see also https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Non-copyable_Mixin ~LtsCoordinateNoImplicitCopy() = default; // ctor like BaseClass: // explicit LtsCoordinateNoImplicitCopy(double lon = 0, double lat = 0, double z = std::nan("")) // : BaseClass(lon, lat, z) // {} explicit LtsCoordinateNoImplicitCopy(const QPointF &lonLat, double z = std::nan("")) : BaseClass(Core::GeoPosition(lonLat.x(), lonLat.y(), z)) {} explicit LtsCoordinateNoImplicitCopy(const Core::GeoPosition &geoPos) : BaseClass(geoPos) {} explicit LtsCoordinateNoImplicitCopy(const Core::GeoPosition2D &geoPos2D) : BaseClass(geoPos2D) {} /*! * \brief updateGeoPosition is a replacement for simple line 'm_geo = LtsCoordinate(lat, lon, 0)' * \param geoPos coordinate to be used. */ void updateGeoPosition(const Core::GeoPosition &geoPos) const { clear(); m_geo = geoPos; } }; // NoImplicitCopy // see https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Non-copyable_Mixin // LtsCoordinateNoImplicitCopy foo() // Compiler error because copy-constructor must be invoked _implicitly_ to return by value. // { // LtsCoordinateNoImplicitCopy n; // return n; // } // void bar(LtsCoordinateNoImplicitCopy n) {} // Compiler error because copy-constructor must be invoked _implicitly_ to pass by value. // void dummy() // { // LtsCoordinateNoImplicitCopy local; // bar(local); // } // see also : https://stackoverflow.com/questions/42972455/disable-implicit-object-copy-selecively template T_OUT convert(const T_IN& contIn) { T_OUT out; out.reserve(contIn.size()); for (const Core::GeoPosition &posGeo : qAsConst(contIn)) out.push_back(LtsCoordinate(posGeo)); return out; } // output operator: LTSCOORDINATE_EXPORT std::ostream &operator<< (std::ostream &stream, const LtsCoordinate &value); } // namespace LtsCoordTrans