Files
qt5-legatus-legacy/Lts/Common/LtsCoordinate/lts_coordinate.h
T
2026-08-13 18:33:10 +02:00

418 lines
14 KiB
C++
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @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 &ltsPosRef = 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