418 lines
14 KiB
C++
Executable File
418 lines
14 KiB
C++
Executable File
/**
|
||
* @RDE_HEADER@
|
||
* LtsCoordinate
|
||
* $Id$
|
||
* $HeadURL$
|
||
* $Author$
|
||
* Created by Norbert Thoden, ESVG, 2024-08-27
|
||
*/
|
||
|
||
#pragma once
|
||
|
||
#include <LtsCoordinate/ltscoordinate_export.h>
|
||
|
||
#include <Core/core.hpp> // Core::GeoPosition
|
||
#include <LtsCoordTrans/lts_coord_trans.h> // UtmCoordinate
|
||
#include <LtsUtils/LtsUtilsCore/qpoint3d.h> // Point3D
|
||
|
||
#include <QPointF>
|
||
#include <QDebug>
|
||
|
||
#include <functional> // std::function
|
||
#include <iosfwd>
|
||
#include <type_traits> // 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<typename T>
|
||
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<QPointF>(); }
|
||
QPoint3D toUtmPoint3D() const { return toUtm<QPoint3D>(); }
|
||
|
||
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<double(double, double, double, bool*)>; // (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<QPoint3D>(); // implicit m_utm2D.zone will be set
|
||
const QPoint3D &otherUtm3D = other.toUtm<QPoint3D>(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<typename T_OUT, typename T_IN>
|
||
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
|
||
|