Initial commit

This commit is contained in:
torsten committed 2026-08-13 18:33:10 +02:00
commit b7d90b9afe
16245 files changed
+3396666

No files matched your search

+26
View File
@@ -0,0 +1,26 @@
#
# $Id$
# $HeadURL$
# $Author$
#
NGL_ADD_LIBRARY2(WS SV HLA LtsCoordinate SHARED
lts_coordinate.h
lts_coordinate.cpp
lts_coord_vector.h
lts_coord_vector.cpp
)
NGL_TARGET_INCLUDE_DIRECTORIES(LtsCoordinate
INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/../
)
NGL_TARGET_LINK_LIBRARIES(LtsCoordinate
PRIVATE
LtsCoordTrans
PUBLIC
ddscore
LtsUtilsCoreHeaderOnly
Qt5::Core
)
ADD_SUBDIRECTORY(UnitTest_Coordinate)
@@ -0,0 +1,30 @@
#
# @RDE_HEADER@
# $Id$
# $HeadURL$
# $Author$
#
SET(UnitTestSources
lts_coordinate_unit_test.cpp
lts_coordinate_unit_test.h
)
IF(BUILD_TESTING)
ADD_EXECUTABLE(commonUnitTestCoordinate ${LinkedSources} ${UnitTestSources} )
TARGET_LINK_LIBRARIES(commonUnitTestCoordinate
NGLLogger
nglcoreapplication
LtsCoordinate
LtsElevation
LtsCoordTrans
situationMapCommon
situationMapElevationMap
Qt5::Core
Qt5::Gui
Qt5::Test)
ADD_TEST(NAME commonUnitTestCoordinate COMMAND $<TARGET_FILE:commonUnitTestCoordinate>)
ENDIF()
@@ -0,0 +1,976 @@
/**
* @RDE_HEADER@
* Unittest
* $Id$
* $HeadURL$
* $Author$
*/
#include "lts_coordinate_unit_test.h"
#include <LtsCoordTrans/lts_coord_trans.h>
#include <LtsCoordinate/lts_coordinate.h>
#include <LtsCoordinate/lts_coord_vector.h>
#include <ElevationMap/elevation_map_manager.h>
#include <nglapplication/nglcoreapplication.h>
#include <NGLLogger/NGLLoggerBootstrap.h>
#include <QtTest/QTest>
Q_DECLARE_METATYPE(LtsUtils::QPoint3D);
#include <iostream>
#include <iomanip>
static const int s_id{4711};
static LtsElevation::LtsElevationManager *s_eleMgr{nullptr};
//------------------------------------------------------------------------------
// class MyLtsElevationManagerBootStrap
//------------------------------------------------------------------------------
// hack: do not use dtcs.
// Use this class to get access to ElevationMapManager::init();
class MyLtsElevationManagerBootstrap : public LtsElevation::LtsElevationManager
{
using BaseClass = LtsElevation::LtsElevationManager;
public:
static LtsElevationManager *createInstanceUnitTest(int id, const QStringList &filePaths_rough_to_fine)
{
return BaseClass::createInstanceUnitTest(id, filePaths_rough_to_fine);
}
};
//------------------------------------------------------------------------------
// class UnitTestCoordinate
//------------------------------------------------------------------------------
UnitTestCoordinate::UnitTestCoordinate()
: m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("UnitTestCoordinate"))
{}
void UnitTestCoordinate::initTestCase()
{
// ll /usr/share/arcgis/resources/elevation/
QString filePath(QStringLiteral("/usr/share/arcgis/resources/elevation/Altmark_dt1.tif"));
s_eleMgr = MyLtsElevationManagerBootstrap::createInstanceUnitTest(s_id, {filePath});
const bool bIsValid = s_eleMgr->isValid();
std::cerr << "myBootstrapMgr->isValid() 1 = " << bIsValid << std::endl;
// we have to wait for loading to be finished:
{
s_eleMgr->waitForInitialization();
const bool bIsValid = s_eleMgr->isValid();
std::cerr << "myBootstrapMgr->isValid() 2 = " << bIsValid << std::endl;
}
QVERIFY2(s_eleMgr , "static LtsElevationManager does not exist");
//
// set elevationCbFunc at LtsCoordinate
// using elevationCbFunc = std::function<double(double, double, double, bool*)>;
using namespace std::placeholders;
LtsCoordTrans::LtsCoordinate::elevationCbFunc elevationCB = std::bind(&LtsElevation::LtsElevationManager::elevation,
s_eleMgr, _1, _2, _3, _4);
LtsCoordTrans::LtsCoordinate::setElevationCallback(elevationCB);
QVERIFY2(LtsCoordTrans::LtsCoordinate::hasElevationCallback() , "setElevationCallback failed");
}
void UnitTestCoordinate::init()
{
QVERIFY2(s_eleMgr, "static LtsElevationManager does not exist");
QVERIFY2(LtsCoordTrans::LtsCoordinate::hasElevationCallback() , "setElevationCallback failed");
}
void UnitTestCoordinate::test_NaN()
{
bool equal = qFuzzyCompare(std::nan(""), std::nan(""));
NOTICE_LOG(m_logger) << " qFuzzyCompare(NaN, NaN) = " << equal;
// surprise: equal is false:
QVERIFY2(equal == false, "qFuzzyCompare(NaN, NaN) failed");
}
void UnitTestCoordinate::test_coordinate_simple1()
{
LtsCoordTrans::LtsCoordinate ltsCoord;
QVERIFY2(ltsCoord.isValid() == false, "isValid failed");
const Core::GeoPosition &geoPos = ltsCoord.geoPosition();
NOTICE_LOG(m_logger) << " geoPosition = " << geoPos;
QVERIFY2(qFuzzyIsNull(geoPos.lon()) , "geoPosition failed");
QVERIFY2(qFuzzyIsNull(geoPos.lat()) , "geoPosition failed");
QVERIFY2(std::isnan( geoPos.z() ) , "geoPosition failed");
const QPointF &utm = ltsCoord.toUtmPointF();
const QPointF &utm2 = ltsCoord.toUtm<QPointF>();
const LtsCoordTrans::UtmCoordinate3D &utm3D = ltsCoord.toUtmCoordinate3D();
NOTICE_LOG(m_logger) << " utmCoordinateAsPointF = " << utm;
NOTICE_LOG(m_logger) << " utmCoordinateAsPointF = " << utm2;
NOTICE_LOG(m_logger) << " UtmCoordinate3D = " << utm3D;
QVERIFY2(qFuzzyCompare(utm3D.x, 166021.44308053952), "data no equal");
QVERIFY2(qFuzzyCompare(utm3D.y, 0.0 ), "data no equal");
QVERIFY2( utm3D.zone == 31 , "data no equal"); // 0.0, 0.0 -> Greenwhich -> zone 31
QVERIFY2(utm == utm2 , "data no equal");
const Core::GeoPosition &geoPos2 = ltsCoord.geoPosition();
NOTICE_LOG(m_logger) << " geoPosition2 = " << geoPos2;
QVERIFY2(qFuzzyIsNull(geoPos2.lon()) , "geoPosition2 failed");
QVERIFY2(qFuzzyIsNull(geoPos2.lat()) , "geoPosition2 failed");
QVERIFY2(std::isnan( geoPos2.z() ) , "geoPosition2 failed");
}
void UnitTestCoordinate::test_coordinate_simple2()
{
Core::GeoPosition defaultGeoPos;
LtsCoordTrans::LtsCoordinate ltsCoord(defaultGeoPos);
QVERIFY2(ltsCoord.isValid() == false, "isValid failed");
const Core::GeoPosition &geoPos = ltsCoord.geoPosition();
NOTICE_LOG(m_logger) << " geoPosition = " << geoPos;
QVERIFY2(qFuzzyIsNull(geoPos.lon()) , "geoPosition failed");
QVERIFY2(qFuzzyIsNull(geoPos.lat()) , "geoPosition failed");
QVERIFY2(qFuzzyIsNull(geoPos.z() ) , "geoPosition failed");
const QPointF &utm = ltsCoord.toUtm<QPointF>();
NOTICE_LOG(m_logger) << " utmCoordinateAsPointF = " << utm;
// ist geoPosition NACH .toUtm() unverändert?
const Core::GeoPosition &geoPos2 = ltsCoord.geoPosition();
NOTICE_LOG(m_logger) << " geoPosition2 = " << geoPos2;
QVERIFY2(qFuzzyIsNull(geoPos2.lon()) , "geoPosition2 failed");
QVERIFY2(qFuzzyIsNull(geoPos2.lat()) , "geoPosition2 failed");
QVERIFY2(qFuzzyIsNull(geoPos2.z() ) , "geoPosition2 failed");
}
void UnitTestCoordinate::test_coordinate_simple3()
{
static const Core::GeoPosition s_invalidPos(std::nan(""), std::nan(""), std::nan(""));
// check NaN
LtsCoordTrans::LtsCoordinate ltsCoord(s_invalidPos);
QVERIFY2(ltsCoord.isValid() == false, "isValid failed");
const Core::GeoPosition &geoPos = ltsCoord.geoPosition();
NOTICE_LOG(m_logger) << " geoPosition = " << geoPos;
QVERIFY2(std::isnan(geoPos.lon()) , "geoPosition failed");
QVERIFY2(std::isnan(geoPos.lat()) , "geoPosition failed");
QVERIFY2(std::isnan(geoPos.z()) , "geoPosition failed");
const QPointF &utm = ltsCoord.toUtm<QPointF>();
NOTICE_LOG(m_logger) << " utmCoordinateAsPointF = " << utm;
QVERIFY2(utm.isNull() , "utm failed");
}
void UnitTestCoordinate::test_coordinate_simple4()
{
LtsCoordTrans::LtsCoordinate::resetConversionCounter();
QVERIFY2(LtsCoordTrans::LtsCoordinate::getConversionCounter() == 0, "getConversionCounter failed");
const LtsCoordTrans::UtmCoordinate &utmIn = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 10000"));
const double zValue = 2.3;
const LtsCoordTrans::LtsCoordinate ltsCoord(utmIn.geographic(), zValue); // explicit zValue
// How often the expensive conversion to utm takes place?
const LtsCoordTrans::UtmCoordinate &utmCoord_FirstTry = ltsCoord.utmCoordinate();
Q_UNUSED(utmCoord_FirstTry);
QVERIFY2(LtsCoordTrans::LtsCoordinate::getConversionCounter() == 1, "getConversionCounter failed");
const LtsCoordTrans::UtmCoordinate &utmCoord_SecondTry = ltsCoord.utmCoordinate();
Q_UNUSED(utmCoord_SecondTry);
QVERIFY2(LtsCoordTrans::LtsCoordinate::getConversionCounter() == 1, "getConversionCounter failed");
}
void UnitTestCoordinate::test_coordinate_fromUtm()
{
const LtsCoordTrans::UtmCoordinate &utmIn = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 10000"));
const double zValue = 2.3;
NOTICE_LOG(m_logger) << " utmIn = " << utmIn;
// utm -> geo -> ltsCoord
LtsCoordTrans::LtsCoordinate ltsCoord(utmIn.geographic(), zValue); // explicit zValue
const LtsCoordTrans::UtmCoordinate &utmCoord = ltsCoord.utmCoordinate();
NOTICE_LOG(m_logger) << " utmCoord = " << utmCoord;
NOTICE_LOG(m_logger) << " utmCoord = " << utmCoord.mgrs();
QVERIFY2(utmCoord.mgrs() == QStringLiteral("32U PD 70000 10000") , "utmCoordinate failed");
// ltsCoord back to geoPos
const Core::GeoPosition &geoPos = ltsCoord.geoPosition();
NOTICE_LOG(m_logger) << " geoPosition = " << geoPos;
QVERIFY2(qFuzzyCompare(geoPos.lon(), 0.200706392814992), "geoPosition failed");
QVERIFY2(qFuzzyCompare(geoPos.lat(), 0.914792952478694), "geoPosition failed");
QVERIFY2(qFuzzyCompare(geoPos.z() , zValue ) , "geoPosition failed");
// ltsCoord back to utm
const QPointF &utmP2D = ltsCoord.toUtm<QPointF>();
NOTICE_LOG(m_logger) << " utmP2D = " << utmP2D;
QVERIFY2(qFuzzyCompare(utmP2D.x(), 670000.5), "utm1 failed");
QVERIFY2(qFuzzyCompare(utmP2D.y(), 5810000.5), "utm2 failed");
const LtsUtils::QPoint3D &utmP3D = ltsCoord.toUtm<LtsUtils::QPoint3D>();
NOTICE_LOG(m_logger) << " utmP3D = " << utmP3D;
QVERIFY2(qFuzzyCompare(utmP3D.x(), 670000.5), "utm1 failed");
QVERIFY2(qFuzzyCompare(utmP3D.y(), 5810000.5), "utm2 failed");
QVERIFY2(qFuzzyCompare(utmP3D.z() , zValue ), "utm3 failed");
const LtsCoordTrans::UtmCoordinate3D &utmCoord3D = ltsCoord.toUtmCoordinate3D();
NOTICE_LOG(m_logger) << " UtmCoordinate3D = " << utmCoord3D;
QVERIFY2(qFuzzyCompare(utmCoord3D.x, 670000.5), "utmCoord3D failed");
QVERIFY2(qFuzzyCompare(utmCoord3D.y, 5810000.5), "utmCoord3D failed");
QVERIFY2(qFuzzyCompare(utmCoord3D.z(), zValue ), "utmCoord3D failed");
// check downcast:
const LtsCoordTrans::UtmCoordinate &utmCoord2D = utmCoord3D;
NOTICE_LOG(m_logger) << " utmCoord2D = " << utmCoord2D;
QVERIFY2(qFuzzyCompare(utmCoord2D.x, 670000.5), "utmCoord2D failed");
QVERIFY2(qFuzzyCompare(utmCoord2D.y, 5810000.5), "utmCoord2D failed");
// to mgrs
const LtsCoordTrans::UtmCoordinate &utm = ltsCoord.utmCoordinate();
QVERIFY2(utm.mgrs() == QStringLiteral("32U PD 70000 10000") , "utmCoordinate failed");
}
void UnitTestCoordinate::test_coordinate_fromUtm2()
{
const LtsCoordTrans::UtmCoordinate &utmInZone32 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U LA 10000 00000"));
const double zValue = 2.3;
NOTICE_LOG(m_logger) << " utmInZone32 = " << utmInZone32;
const QPointF &utmInDefQPointF = utmInZone32.geographic();
const int zone31 = 31;
const LtsCoordTrans::UtmCoordinate &utmInZone31 = LtsCoordTrans::UtmCoordinate::fromGeographic(utmInDefQPointF, zone31);
NOTICE_LOG(m_logger) << " utmInZone31 = " << utmInZone31;
NOTICE_LOG(m_logger) << " utmInZone31.geographic() = " << utmInZone31.geographic();
NOTICE_LOG(m_logger) << " utmInZone32.geographic() = " << utmInZone32.geographic();
QVERIFY2(utmInZone31.geographic() == utmInZone32.geographic(), "geographic failed");
{
// From different zones via geographic
LtsCoordTrans::LtsCoordinate ltsFromUtm31(utmInZone31.geographic(), zValue); // explicit zValue
LtsCoordTrans::LtsCoordinate ltsFromUtm32(utmInZone32.geographic(), zValue); // explicit zValue
double dist = ltsFromUtm31.distanceUtm3D(ltsFromUtm32);
NOTICE_LOG(m_logger) << " dist1 = " << dist;
QVERIFY2(qFuzzyCompare(dist, 0), "distance3D");
}
{
// From different zones via geographic
LtsCoordTrans::LtsCoordinate ltsFromUtm31(utmInZone31.geographic() , zValue); // explicit zValue
LtsCoordTrans::LtsCoordinate ltsFromUtm32(utmInZone32.geographic(1, 0), zValue); // explicit zValue
double dist = ltsFromUtm31.distanceUtm3D(ltsFromUtm32);
NOTICE_LOG(m_logger) << " dist2 = " << dist;
QVERIFY2(fabs(dist-1) < 0.00001, "distance3D"); // -5.82077e-11
}
{
// From different zones via fromUtmCoordinate
// From different zones via fromUtmCoordinate
// From different zones via fromUtmCoordinate
LtsCoordTrans::LtsCoordinate ltsFromUtm31 = LtsCoordTrans::LtsCoordinate::fromUtmCoordinate(utmInZone31, zValue); // explicit zValue
LtsCoordTrans::LtsCoordinate ltsFromUtm32 = LtsCoordTrans::LtsCoordinate::fromUtmCoordinate(utmInZone32, zValue); // explicit zValue
double dist = ltsFromUtm31.distanceUtm3D(ltsFromUtm32);
NOTICE_LOG(m_logger) << " dist3 = " << dist;
QVERIFY2(qFuzzyCompare(dist, 0), "distance3D");
}
{
LtsCoordTrans::LtsCoordinate ltsFromUtm32(utmInZone32.geographic(), zValue); // explicit zValue
const QPointF &utmP2D_def = ltsFromUtm32.toUtm<QPointF>();
const QPointF &utmP2D_31 = ltsFromUtm32.toUtm<QPointF>(31);
NOTICE_LOG(m_logger) << " utmP2D_def = " << utmP2D_def;
NOTICE_LOG(m_logger) << " utmP2D_31 = " << utmP2D_31;
LtsCoordTrans::LtsCoordinate lts1 = LtsCoordTrans::LtsCoordinate::fromUtm(utmP2D_def, 32, 0.0);
LtsCoordTrans::LtsCoordinate lts2 = LtsCoordTrans::LtsCoordinate::fromUtm(utmP2D_31, 31, 0.0);
NOTICE_LOG(m_logger) << " lts1 = " << lts1;
NOTICE_LOG(m_logger) << " lts2 = " << lts2;
double dist = lts1.distanceUtm3D(lts2);
NOTICE_LOG(m_logger) << " dist4 = " << dist;
QVERIFY2(qFuzzyCompare(dist, 0), "distance3D");
}
}
void UnitTestCoordinate::test_coordinate_elevationAt()
{
const double zValue = 1.23;
const LtsCoordTrans::UtmCoordinate &utmIn = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 10000"));
NOTICE_LOG(m_logger) << " utmIn = " << utmIn;
LtsCoordTrans::LtsCoordinate ltsFromUtm(utmIn.geographic(), zValue); // explicit zValue
NOTICE_LOG(m_logger) << " ltsFromUtm1 = " << ltsFromUtm;
QVERIFY2(qFuzzyCompare(ltsFromUtm.geoPosition().z(), zValue) , "geoPosition failed");
// Note: elevationAt will NOT modify ltsFromUtm !!!
const double ele = ltsFromUtm.elevationAt();
QVERIFY2(qFuzzyCompare(ele, 75), "elevationAt failed");
NOTICE_LOG(m_logger) << " ltsFromUtm2 = " << ltsFromUtm;
QVERIFY2(qFuzzyCompare(ltsFromUtm.geoPosition().z(), zValue) , "geoPosition failed");
// If you like to modify ltsFromUtm: use setZValueAccordingElevation!!!
ltsFromUtm.setZValueAccordingElevation();
NOTICE_LOG(m_logger) << " ltsFromUtm3 = " << ltsFromUtm;
QVERIFY2(qFuzzyCompare(ltsFromUtm.geoPosition().z(), 75) , "setZValueAccordingElevation failed");
// QPoint3D
const LtsUtils::QPoint3D &utmP3D = ltsFromUtm.toUtm<LtsUtils::QPoint3D>();
NOTICE_LOG(m_logger) << " utmP3D_a = " << utmP3D;
QVERIFY2(qFuzzyCompare(utmP3D.z(), 75 ) , "utmCoordinateAsPoint3D failed");
{
// try static:
const double ele3 = LtsCoordTrans::LtsCoordinate::elevationAt(ltsFromUtm);
NOTICE_LOG(m_logger) << " ele3 = " << ele3;
// has to be identical to ele
QVERIFY2(qFuzzyCompare(ele, ele3 ), "LtsCoordinate::elevationAt failed");
}
//
// _change_ ltsFromUtm to reset the cached elevation:
// LtsCoordinate::clearElevation()
//
// move some meters
LtsCoordTrans::UtmCoordinate utmMoved = ltsFromUtm.utmCoordinate();
utmMoved.x += 11;
utmMoved.y += 22;
const int zValueNew = 123;
ltsFromUtm = LtsCoordTrans::LtsCoordinate(utmMoved.geographic(), zValueNew);
NOTICE_LOG(m_logger) << " ltsFromUtm4 = " << ltsFromUtm;
// call elevationAt a 2nd time:
const double ele2 = ltsFromUtm.elevationAt();
NOTICE_LOG(m_logger) << " ltsFromUtm5 = " << ltsFromUtm;
QVERIFY2(ele2 != 75 , "setZValueAccordingElevation failed");
}
void UnitTestCoordinate::test_coordinate_elevationAt_isCovered()
{
const LtsCoordTrans::UtmCoordinate &utmIn1 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 10000"));
const LtsCoordTrans::UtmCoordinate &utmIn2 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U MD 10000 10000"));
LtsCoordTrans::LtsCoordinate ltsFromUtm1(utmIn1.geographic(), 11); // explicit zValue
LtsCoordTrans::LtsCoordinate ltsFromUtm2(utmIn2.geographic(), 11); // explicit zValue
{
bool bIsCovered = false;
double ele = ltsFromUtm1.elevationAt(-123, &bIsCovered);
Q_UNUSED(ele);
QVERIFY2(bIsCovered == true , "elevationAt1a failed");
// retrieve 2nd:
bIsCovered = false;
ele = ltsFromUtm1.elevationAt(-123, &bIsCovered);
Q_UNUSED(ele);
QVERIFY2(bIsCovered == true , "elevationAt1b failed");
// static version:
bIsCovered = false;
ele = LtsCoordTrans::LtsCoordinate::elevationAt(ltsFromUtm1, -123, &bIsCovered);
Q_UNUSED(ele);
QVERIFY2(bIsCovered == true , "elevationAt2a failed");
// retrieve 2nd:
bIsCovered = false;
ele = LtsCoordTrans::LtsCoordinate::elevationAt(ltsFromUtm1, -123, &bIsCovered);
Q_UNUSED(ele);
QVERIFY2(bIsCovered == true , "elevationAt2b failed");
}
{
bool bIsCovered = true;
double ele = ltsFromUtm2.elevationAt(-123, &bIsCovered);
Q_UNUSED(ele);
QVERIFY2(bIsCovered == false , "elevationAt2 failed");
// static version:
bIsCovered = true;
ele = LtsCoordTrans::LtsCoordinate::elevationAt(ltsFromUtm2, -123, &bIsCovered);
Q_UNUSED(ele);
QVERIFY2(bIsCovered == false , "elevationAt1 failed");
}
}
void UnitTestCoordinate::test_coordinate_elevation_data()
{
QTest::addColumn<QString>("mgrs");
QTest::addColumn<double>("elevationIn");
QTest::addColumn<double>("elevationRef");
// Altmark_dt1.tif:
QTest::newRow("elevation 10 ") << QStringLiteral("32 U PD 70000 10000") << std::nan("") << 75.0;
QTest::newRow("elevation 11 ") << QStringLiteral("32 U PD 70000 10000") << 0.0 << 75.0;
QTest::newRow("elevation 20 ") << QStringLiteral("32 U PD 72000 13000") << std::nan("") << 80.0;
QTest::newRow("elevation 21 ") << QStringLiteral("32 U PD 72000 13000") << 0.340 << 80.0;
}
void UnitTestCoordinate::test_coordinate_elevation()
{
QFETCH(QString, mgrs);
QFETCH(double, elevationIn);
QFETCH(double, elevationRef);
const LtsCoordTrans::UtmCoordinate &utmIn = LtsCoordTrans::UtmCoordinate::fromMgrs(mgrs);
NOTICE_LOG(m_logger) << " utmIn = " << utmIn;
LtsCoordTrans::LtsCoordinate ltsFromUtm(utmIn.geographic(), elevationIn); // explicit zValue
NOTICE_LOG(m_logger) << " ltsFromUtm = " << ltsFromUtm;
// before
NOTICE_LOG(m_logger) << " geoPosition = " << ltsFromUtm.geoPosition();
ltsFromUtm.replaceZValueByElevationIfValue(elevationIn);
// after
NOTICE_LOG(m_logger) << " geoPosition = " << ltsFromUtm.geoPosition();
QVERIFY2(qFuzzyCompare(ltsFromUtm.geoPosition().z(), elevationRef), "elevation failed");
// QPoint3D
const LtsUtils::QPoint3D &utmP3D = ltsFromUtm.toUtm<LtsUtils::QPoint3D>();
NOTICE_LOG(m_logger) << " utmP3D = " << utmP3D;
QVERIFY2(qFuzzyCompare(utmP3D.z(), elevationRef) , "utmCoordinateAsPoint3D failed");
}
void UnitTestCoordinate::test_coordinate_mgrs()
{
const LtsCoordTrans::UtmCoordinate &utmIn = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 10000"));
NOTICE_LOG(m_logger) << " utmIn = " << utmIn.mgrs();
LtsCoordTrans::LtsCoordinate ltsFromUtm(utmIn.geographic());
{
const QString asMgrs = ltsFromUtm.mgrs();
NOTICE_LOG(m_logger) << " asMgrs = " << asMgrs;
QVERIFY2(asMgrs.compare(QStringLiteral("32U PD 70000 10000")) == 0, "UtmCoordinate::mgrs failed");
}
{
const QString asMgrs = ltsFromUtm.mgrsWithZ();
NOTICE_LOG(m_logger) << " asMgrs = " << asMgrs;
QVERIFY2(asMgrs.compare(QStringLiteral("32U PD 70000 10000 z=75.00")) == 0, "UtmCoordinate::mgrs failed");
}
{
const LtsCoordTrans::LtsCoordinate coord(0.200706392814992, 0.914792952478694, 123);
// 1st: write just the already available member....
// Note: m_utm2D is '0'
const QString str1 = coord.toString();
NOTICE_LOG(m_logger) << " str1 cheap = " << str1;
QVERIFY2(str1.compare(QStringLiteral("LtsCoordinate(m_geo=[[lon: 0.200706392814992, lat: 0.914792952478694]z: 123], m_utm2D.x 0, m_utm2D.y 0, m_utm2D.zone -1, elevation nan)")) == 0, "toString failed");
// 2nd: write more of less complete set of member
// means:
// a) calculate utmCoordinateInternal(); // ensure m_utm2D
// b) calculate elevationAt(); // ensure m_elevation
const bool fullAndExpensive = true;
const QString str2 = coord.toString(fullAndExpensive);
NOTICE_LOG(m_logger) << " str2 expensive = " << str2;
QVERIFY2(str2.compare(QStringLiteral("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)")) == 0, "toString failed");
}
}
void UnitTestCoordinate::test_coordinate_distance()
{
const LtsCoordTrans::UtmCoordinate &utmIn1 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 10000"));
const LtsCoordTrans::UtmCoordinate &utmIn2 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70010 10100"));
NOTICE_LOG(m_logger) << " XXutmIn1 = " << utmIn1;
NOTICE_LOG(m_logger) << " XXutmIn2 = " << utmIn2;
QVERIFY2(qFuzzyCompare((utmIn2.x - utmIn1.x), 10) , "coord failed");
QVERIFY2(qFuzzyCompare((utmIn2.y - utmIn1.y), 100) , "coord failed");
const LtsCoordTrans::LtsCoordinate lts1(utmIn1.geographic(), 11); // explicit zValue
const LtsCoordTrans::LtsCoordinate lts2(utmIn2.geographic(), 111); // explicit zValue
{
double dist2D = lts1.distanceUtm2D(lts2);
NOTICE_LOG(m_logger) << " dist2D = " << dist2D;
QVERIFY2(std::fabs(dist2D-100.499) < 0.01, "distanceUtm2D failed");
}
{
double dist3D = lts1.distanceUtm3D(lts2);
NOTICE_LOG(m_logger) << " dist3D = " << dist3D;
QVERIFY2(std::fabs(dist3D-141.774) < 0.01, "distanceUtm3D failed");
}
}
void UnitTestCoordinate::test_coordinate_distanceDiffZones()
{
const LtsCoordTrans::UtmCoordinate &utmIn1 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("31 U FR 99999 00000"));
const LtsCoordTrans::UtmCoordinate &utmIn2 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U LA 00000 00000"));
NOTICE_LOG(m_logger) << " XXutmIn1 = " << utmIn1;
NOTICE_LOG(m_logger) << " XXutmIn2 = " << utmIn2;
// LtsCoordTrans::LtsCoordinate lts1(utmIn1, 0);
// LtsCoordTrans::LtsCoordinate lts2(utmIn2, 0);
LtsCoordTrans::LtsCoordinate lts1(utmIn1.geographic(), 0);
LtsCoordTrans::LtsCoordinate lts2(utmIn2.geographic(), 0);
NOTICE_LOG(m_logger) << " lts1 = " << lts1;
NOTICE_LOG(m_logger) << " lts2 = " << lts2;
double dist = lts1.distanceUtm3D(lts2);
NOTICE_LOG(m_logger) << " dist = " << dist;
QVERIFY2(std::fabs(dist-33382.7) < 0.1, "distance3D failed");
}
void UnitTestCoordinate::test_squareInDifferentZone()
{
const LtsCoordTrans::UtmCoordinate &utm_bl = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 10000"));
const LtsCoordTrans::UtmCoordinate &utm_br = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 11000"));
const LtsCoordTrans::UtmCoordinate &utm_tl = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 71000 10000"));
const LtsCoordTrans::UtmCoordinate &utm_tr = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 71000 11000"));
const LtsCoordTrans::LtsCoordinate lts_bl(utm_bl.geographic(), 0);
const LtsCoordTrans::LtsCoordinate lts_br(utm_br.geographic(), 0);
const LtsCoordTrans::LtsCoordinate lts_tl(utm_tl.geographic(), 0);
const LtsCoordTrans::LtsCoordinate lts_tr(utm_tr.geographic(), 0);
{
// check square and diagonal:
QVERIFY2(qFuzzyCompare(lts_bl.distanceUtm2D(lts_br), 1000.0), "distance3D 1 failed");
QVERIFY2(qFuzzyCompare(lts_tl.distanceUtm2D(lts_tr), 1000.0), "distance3D 2 failed");
QVERIFY2(qFuzzyCompare(lts_br.distanceUtm2D(lts_tr), 1000.0), "distance3D 3 failed");
QVERIFY2(qFuzzyCompare(lts_bl.distanceUtm2D(lts_tl), 1000.0), "distance3D 4 failed");
// check diagonal:
QVERIFY2(qFuzzyCompare(lts_bl.distanceUtm2D(lts_tr), 1414.213562373177), "distance3D 5 failed");
QVERIFY2(qFuzzyCompare(lts_br.distanceUtm2D(lts_tl), 1414.213562373177), "distance3D 6 failed");
}
// old approach:
// calculate in a different utm zone:
{
const LtsCoordTrans::UtmCoordinate &utm31_bl = LtsCoordTrans::UtmCoordinate(31, utm_bl.x, utm_bl.y);
const LtsCoordTrans::UtmCoordinate &utm31_br = LtsCoordTrans::UtmCoordinate(31, utm_br.x, utm_br.y);
const LtsCoordTrans::UtmCoordinate &utm31_tl = LtsCoordTrans::UtmCoordinate(31, utm_tl.x, utm_tl.y);
const LtsCoordTrans::UtmCoordinate &utm31_tr = LtsCoordTrans::UtmCoordinate(31, utm_tr.x, utm_tr.y);
// check square and diagonal:
QVERIFY2(qFuzzyCompare(LtsCoordTrans::distance(utm31_bl, utm31_br), 1000.0), "LtsCoordTrans::distance 1 failed");
QVERIFY2(qFuzzyCompare(LtsCoordTrans::distance(utm31_tl, utm31_tr), 1000.0), "LtsCoordTrans::distance 2 failed");
QVERIFY2(qFuzzyCompare(LtsCoordTrans::distance(utm31_br, utm31_tr), 1000.0), "LtsCoordTrans::distance 3 failed");
QVERIFY2(qFuzzyCompare(LtsCoordTrans::distance(utm31_bl, utm31_tl), 1000.0), "LtsCoordTrans::distance 4 failed");
// check diagonal:
QVERIFY2(qFuzzyCompare(LtsCoordTrans::distance(utm31_bl, utm31_tr), 1414.213562373095), "LtsCoordTrans::distance 5 failed");
QVERIFY2(qFuzzyCompare(LtsCoordTrans::distance(utm31_br, utm31_tl), 1414.213562373095), "LtsCoordTrans::distance 6 failed");
}
// Fazit:
// Für das in Zone 32 erstellte Quadrat wird für beide Diagonalen ein identischer Abstand ermittelt.
// Wird die Berechnung in einer anderen Zone (hier 31) durchgeführt, gilt dasselbe.
// Der absolute Werte differiert nur marginal und dürfte eher auf Rechenungenauigkeiten zurückzuführen sein.
// Zone 32: 1414.213562373177
// Zone 32: 1414.213562373095
// ^
}
void UnitTestCoordinate::test_coordinate_angle()
{
// MGRS: 32 U PD EAST NORTH
// MGRS: 32 U PD X Y
const LtsCoordTrans::UtmCoordinate &utm_ul = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 10000"));
const LtsCoordTrans::LtsCoordinate lts_ul(utm_ul.geographic(), 0);
{
// 0 Uhr
const LtsCoordTrans::UtmCoordinate &utm_2 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 11000"));
const LtsCoordTrans::LtsCoordinate lts_2(utm_2.geographic(), 0);
double angle = LtsCoordTrans::LtsCoordinate::angleDegreeUtm2D(lts_ul, lts_2);
QVERIFY2(qFuzzyCompare(angle, 0.0), "angleUtm2D 1 failed");
}
{
// 3 Uhr
const LtsCoordTrans::UtmCoordinate &utm_2 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 71000 10000"));
const LtsCoordTrans::LtsCoordinate lts_2(utm_2.geographic(), 0);
double angle = LtsCoordTrans::LtsCoordinate::angleDegreeUtm2D(lts_ul, lts_2);
QVERIFY2(qFuzzyCompare(angle, 90.0), "angleUtm2D 1 failed");
}
{
// 6 Uhr
const LtsCoordTrans::UtmCoordinate &utm_2 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 09000"));
const LtsCoordTrans::LtsCoordinate lts_2(utm_2.geographic(), 0);
double angle = LtsCoordTrans::LtsCoordinate::angleDegreeUtm2D(lts_ul, lts_2);
QVERIFY2(qFuzzyCompare(angle, 180.0), "angleUtm2D 1 failed");
}
{
// 9 Uhr
const LtsCoordTrans::UtmCoordinate &utm_2 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 69000 10000"));
const LtsCoordTrans::LtsCoordinate lts_2(utm_2.geographic(), 0);
double angle = LtsCoordTrans::LtsCoordinate::angleDegreeUtm2D(lts_ul, lts_2);
QVERIFY2(qFuzzyCompare(angle, 270.0), "angleUtm2D 1 failed");
}
// ~1 Uhr
{
const LtsCoordTrans::UtmCoordinate &utm_or = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 71000 12000"));
const LtsCoordTrans::LtsCoordinate lts_or(utm_or.geographic(), 0);
double angle = LtsCoordTrans::LtsCoordinate::angleDegreeUtm2D(lts_ul, lts_or);
// NOTICE_LOG(m_logger) << " angle = " << std::setprecision(20) << angle;
QVERIFY2(qFuzzyCompare(angle, 26.565051177056659526), "angleUtm2D 1 failed");
QVERIFY2((angle-26.5651 < 0.001), "angleUtm2D 1 failed");
}
}
void UnitTestCoordinate::test_coordinate_cont()
{
std::vector<Core::GeoPosition> stdVec;
stdVec.push_back(Core::GeoPosition(0.1, 0.11, 1));
stdVec.push_back(Core::GeoPosition(0.2, 0.22, 2));
stdVec.push_back(Core::GeoPosition(0.3, 0.33, 3));
const QVector<LtsCoordTrans::LtsCoordinate> &newCont = LtsCoordTrans::convert<QVector<LtsCoordTrans::LtsCoordinate>>(stdVec);
NOTICE_LOG(m_logger) << " newCont.size() = " << newCont.size();
QVERIFY2(stdVec.size() == static_cast<size_t>(newCont.size()), "convert failed");
}
using namespace LtsCoordTrans;
class ParticpantPositionStore
{
public:
ParticpantPositionStore() = default;
~ParticpantPositionStore() = default;
void setGeoPosition(const Core::GeoPosition &newGeoPos)
{
m_ltsCoord = LtsCoordinate(newGeoPos);
m_ltsCoord_NoImplicitCopy.updateGeoPosition(newGeoPos);
}
// simple, but coord ist copied
// Any calls to methods which internal carry out
// the (expensive) conversions like utmCoordinate(), elevationAt() are executed at the copy!
// The ParticpantPositionStore itself does _not_ benefit!
LtsCoordinate getLtsCoord_copy()
{
return m_ltsCoord;
}
// return by const reference:
// by doing: 'Class(const Class &) = delete;'
const LtsCoordinateNoImplicitCopy& getLtsCoord_noCopy() // return just a const ref of internal data. No copying! Fine :-)
{
return m_ltsCoord_NoImplicitCopy;
}
// compiler error because copying is requested
// LtsCoordinateNoImplicitCopy getLtsCoord_noCopy()
// {
// return m_ltsCoord_NoImplicitCopy; // error: no matching function for call to ‘LtsCoordTrans::LtsCoordinateNoImplicitCopy::LtsCoordinateNoImplicitCopy(LtsCoordTrans::LtsCoordinateNoImplicitCopy&)’
// }
LtsCoordinate m_ltsCoord;
LtsCoordinateNoImplicitCopy m_ltsCoord_NoImplicitCopy;
};
void UnitTestCoordinate::test_coordinate_avoid_copy()
{
LtsCoordTrans::LtsCoordinate::resetConversionCounter();
QVERIFY2(LtsCoordTrans::LtsCoordinate::getConversionCounter() == 0, "getConversionCounter failed");
ParticpantPositionStore store;
//
// WRITE: set data
//
const UtmCoordinate &utmIn = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 10000"));
const LtsCoordinate ltsIn(utmIn.geographic(), 11);
store.getLtsCoord_noCopy().updateGeoPosition(ltsIn.geoPosition());
//
// READ
//
// advise:
// get data (and benefit from cache)
const LtsCoordinateNoImplicitCopy &justReference = store.getLtsCoord_noCopy();
NOTICE_LOG(m_logger) << " justReference = " << justReference.mgrs(); // operator of baseclass suits
const LtsCoordTrans::UtmCoordinate &convertedToUtm = justReference.utmCoordinate();
NOTICE_LOG(m_logger) << " convertedToUtm = " << convertedToUtm;
QVERIFY2(qFuzzyCompare(convertedToUtm.x, 670000.5 ), "convert failed");
QVERIFY2(qFuzzyCompare(convertedToUtm.y, 5810000.5), "convert failed");
QVERIFY2(LtsCoordTrans::LtsCoordinate::getConversionCounter() == 1, "getConversionCounter failed");
//
// Check how often conversion takes place:
//
const LtsCoordTrans::UtmCoordinate &convertedToUtmSecondTry = justReference.utmCoordinate();
Q_UNUSED(convertedToUtmSecondTry)
QVERIFY2(LtsCoordTrans::LtsCoordinate::getConversionCounter() == 1, "getConversionCounter failed");
// push to vector...
std::vector<LtsCoordinateNoImplicitCopy> myVec;
myVec.push_back(justReference);
// retrieve from vector...
const LtsCoordTrans::LtsCoordinateNoImplicitCopy &retrievedUtmRef = myVec.front();
NOTICE_LOG(m_logger) << " retrievedUtm = " << retrievedUtmRef.mgrs(); // operator of baseclass suits
// Of course, values have to be identical:
QVERIFY2(justReference.mgrs() == retrievedUtmRef.mgrs(), "getConversionCounter failed");
// again, copying is not allowd:
// LtsCoordTrans::LtsCoordinateNoImplicitCopy copy = myVec.front();
// final check:
QVERIFY2(LtsCoordTrans::LtsCoordinate::getConversionCounter() == 1, "getConversionCounter failed");
// error: no matching function for call to ‘LtsCoordTrans::LtsCoordinateNoImplicitCopy::LtsCoordinateNoImplicitCopy(const LtsCoordTrans::LtsCoordinateNoImplicitCopy&)’
// LtsCoordinateNoImplicitCopy unwantedCopy1 = store.getLtsCoord_noCopy();
// const LtsCoordinateNoImplicitCopy unwantedCopy2 = store.getLtsCoord_noCopy();
}
//
// helper for test_references
//
template <typename T>
void removeItem(T& map, int key) { map.erase(key); }
// template specialization
template <>
void removeItem(QMap<int, int>& map, int key) { map.remove(key); }
// template specialization
template <>
void removeItem(QHash<int, int>& map, int key) { map.remove(key); }
template <typename T>
void test_references(T& map)
{
for (int i=2; i<10; ++i)
map[i] = i;
const int &ref2 = map[2];
const int &ref3 = map[3];
const int &ref4 = map[4];
const int &ref5 = map[5];
const int &ref6 = map[6];
const int &ref7 = map[7];
const int &ref8 = map[8];
QVERIFY2(ref2 == 2, "ref failed");
QVERIFY2(ref3 == 3, "ref failed");
QVERIFY2(ref4 == 4, "ref failed");
QVERIFY2(ref5 == 5, "ref failed");
QVERIFY2(ref6 == 6, "ref failed");
QVERIFY2(ref7 == 7, "ref failed");
QVERIFY2(ref8 == 8, "ref failed");
// removing:
removeItem(map, 1);
removeItem(map, 3);
removeItem(map, 5);
{
QVERIFY2(ref2 == 2, "ref failed");
// QVERIFY2(ref3 == 3, "ref failed");
QVERIFY2(ref4 == 4, "ref failed");
// QVERIFY2(ref5 == 5, "ref failed");
QVERIFY2(ref6 == 6, "ref failed");
QVERIFY2(ref7 == 7, "ref failed");
QVERIFY2(ref8 == 8, "ref failed");
}
// adding
map[0] = 0;
map[1] = 1;
const int &ref0 = map[0];
const int &ref1 = map[1];
{
QVERIFY2(ref0 == 0, "ref failed");
QVERIFY2(ref1 == 1, "ref failed");
// QVERIFY2(ref1 == 1, "ref failed");
QVERIFY2(ref2 == 2, "ref failed");
// QVERIFY2(ref3 == 3, "ref failed");
QVERIFY2(ref4 == 4, "ref failed");
// QVERIFY2(ref5 == 5, "ref failed");
QVERIFY2(ref6 == 6, "ref failed");
QVERIFY2(ref7 == 7, "ref failed");
QVERIFY2(ref8 == 8, "ref failed");
}
}
void UnitTestCoordinate::test_container_references()
{
// LtsCoordVector is using std::unordered_map.
// Advantage over map is the signature of operator[].
// copy from lts_coord_vector.h
// prefer std::unordered_map, since 'T& operator[]' is used also for const methods
// like toUtmStd(int zone) const
// ^^^^^
// To be really sure:
// check, if ref to member are valid after removing and adding:
{
std::unordered_map<int, int> std_unordered_map;
test_references(std_unordered_map);
}
{
std::map<int, int> std_map;
test_references(std_map);
}
{
QMap<int, int> Qmap;
test_references(Qmap);
}
{
QHash<int, int> Qhash;
test_references(Qhash);
}
}
//
// LtsCoordVector
//
void UnitTestCoordinate::test_coordVector()
{
std::vector<Core::GeoPosition> geoVec;
{
// choose coord which could be convertet to zone 32 als well as zone 31:
QPointF utmPnt = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U LB 00001 50001")).geographic();
geoVec.push_back( Core::GeoPosition(utmPnt.x(), utmPnt.y(), 1.1) );
utmPnt = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U LB 00001 50001")).geographic(1, 0);
geoVec.push_back( Core::GeoPosition(utmPnt.x(), utmPnt.y(), 2.2) );
utmPnt = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U LB 00001 50001")).geographic(2, 0);
geoVec.push_back( Core::GeoPosition(utmPnt.x(), utmPnt.y(), 3.3) );
}
const LtsCoordVector coordVec(geoVec);
QVERIFY2(coordVec.size() == 3, "size failed");
int cnt = LtsCoordTrans::getConversionCounter_UTMUPS_Forward();
{ // 1st conversion to zone 32
const std::vector<LtsCoordTrans::UtmCoordinate> &utmVec = coordVec.toStdUtmCoord(32);
// NOTICE_LOG(m_logger) << " utmVec = " << utmVec[0].mgrs();
// NOTICE_LOG(m_logger) << " utmVec = " << utmVec[1].mgrs();
// NOTICE_LOG(m_logger) << " utmVec = " << utmVec[2].mgrs();
QVERIFY2(utmVec.size() == 3, "size failed");
QVERIFY2(utmVec.front().mgrs() == QStringLiteral("32U LB 00001 50001"), "mgrs failed");
QVERIFY2(utmVec.back().mgrs() == QStringLiteral("32U LB 00003 50001"), "mgrs failed");
QVERIFY2(LtsCoordTrans::getConversionCounter_UTMUPS_Forward() - cnt == 3, "getConversionCounterUTMUPS_Forward failed");
}
{ // 2nd conversion to zone 32
const std::vector<LtsCoordTrans::UtmCoordinate> &utmVec = coordVec.toStdUtmCoord(32);
QVERIFY2(utmVec.size() == 3, "size failed");
QVERIFY2(utmVec.front().mgrs() == QStringLiteral("32U LB 00001 50001"), "mgrs failed");
QVERIFY2(utmVec.back().mgrs() == QStringLiteral("32U LB 00003 50001"), "mgrs failed");
QVERIFY2(LtsCoordTrans::getConversionCounter_UTMUPS_Forward() - cnt == 3, "getConversionCounterUTMUPS_Forward failed");
}
{ // 1st conversion to zone 31
const std::vector<LtsCoordTrans::UtmCoordinate> &utmVec = coordVec.toStdUtmCoord(31);
QVERIFY2(utmVec.size() == 3, "size failed");
QVERIFY2(LtsCoordTrans::getConversionCounter_UTMUPS_Forward() - cnt == 6, "getConversionCounterUTMUPS_Forward failed");
}
{ // 2nd conversion to zone 31
const std::vector<LtsCoordTrans::UtmCoordinate> &utmVec = coordVec.toStdUtmCoord(31);
QVERIFY2(utmVec.size() == 3, "size failed");
QVERIFY2(LtsCoordTrans::getConversionCounter_UTMUPS_Forward() - cnt == 6, "getConversionCounterUTMUPS_Forward failed");
}
//
// QVector is supported also:
// Note: toUtmQVector benefits from conversion to toUtmStd, so no additional conversion :-)
//
{ // 1st conversion to zone 32
const QVector<LtsCoordTrans::UtmCoordinate> &utmVec = coordVec.toQVectorUtmCoord(32);
QVERIFY2(utmVec.size() == 3, "size failed");
QVERIFY2(utmVec.front().mgrs() == QStringLiteral("32U LB 00001 50001"), "mgrs failed");
QVERIFY2(utmVec.back().mgrs() == QStringLiteral("32U LB 00003 50001"), "mgrs failed");
QVERIFY2(LtsCoordTrans::getConversionCounter_UTMUPS_Forward() - cnt == 6, "getConversionCounterUTMUPS_Forward failed");
}
{ // 2nd conversion to zone 32
const QVector<LtsCoordTrans::UtmCoordinate> &utmVec = coordVec.toQVectorUtmCoord(32);
QVERIFY2(utmVec.size() == 3, "size failed");
QVERIFY2(utmVec.front().mgrs() == QStringLiteral("32U LB 00001 50001"), "mgrs failed");
QVERIFY2(utmVec.back().mgrs() == QStringLiteral("32U LB 00003 50001"), "mgrs failed");
QVERIFY2(LtsCoordTrans::getConversionCounter_UTMUPS_Forward() - cnt == 6, "getConversionCounterUTMUPS_Forward failed");
}
//
// QVector<QPointF> is supported also:
// Note: toUtmQVector benefits from conversion to toUtmStd, so no additional conversion :-)
//
{ // 1st conversion to zone 32
const QVector<QPointF> &utmVec = coordVec.toQVectorQPointF(32);
QVERIFY2(utmVec.size() == 3, "size failed");
QVERIFY2(LtsCoordTrans::getConversionCounter_UTMUPS_Forward() - cnt == 6, "getConversionCounterUTMUPS_Forward failed");
}
{ // 2nd conversion to zone 32
const QVector<LtsCoordTrans::UtmCoordinate> &utmVec = coordVec.toQVectorUtmCoord(32);
QVERIFY2(utmVec.size() == 3, "size failed");
QVERIFY2(LtsCoordTrans::getConversionCounter_UTMUPS_Forward() - cnt == 6, "getConversionCounterUTMUPS_Forward failed");
}
}
// main
// main
// main
int main(int argc, char *argv[])
{
NGLCoreApplication app(argc, argv);
app.createLogger("UnitTestCoordinate", "log4cxxPositionUnitTest.props");
UnitTestCoordinate tc;
int ret = QTest::qExec(&tc, argc, argv);
// cleanup:
if (s_eleMgr)
{
s_eleMgr->cleanupAll();
}
return ret;
}
@@ -0,0 +1,85 @@
/**
* @RDE_HEADER@
* Unittest
* $Id$
* $HeadURL$
* $Author$
*/
#pragma once
#include <LtsElevation/lts_elevation_manager.h>
#include <ElevationMap/elevation_map_manager.h>
#include <QCoreApplication>
#include <QtTest/QTest>
#include <NGLLogger/INGLLogger.h>
using LOGGER_T = NGL::INGLLogger&;
class UnitTestCoordinate : public QObject
{
Q_OBJECT
public:
UnitTestCoordinate();
private Q_SLOTS:
//
// TestCases
//
// void initTestCase_data(); // will be called to create a global test data table.
void initTestCase(); // will be called before the first test function is executed.
void init(); // will be called before each test function is executed.
// void cleanup(); // will be called after each test function is executed.
void test_NaN();
void test_coordinate_simple1();
void test_coordinate_simple2();
void test_coordinate_simple3();
void test_coordinate_simple4();
void test_coordinate_fromUtm();
void test_coordinate_fromUtm2();
void test_coordinate_elevationAt();
void test_coordinate_elevationAt_isCovered();
void test_coordinate_elevation_data();
void test_coordinate_elevation();
void test_coordinate_mgrs();
void test_coordinate_distance();
void test_coordinate_distanceDiffZones();
void test_squareInDifferentZone();
void test_coordinate_angle();
void test_coordinate_cont();
void test_coordinate_avoid_copy();
// LtsCoordVector
void test_container_references();
void test_coordVector();
// void cleanup();
private:
LOGGER_T m_logger;
};
+96
View File
@@ -0,0 +1,96 @@
/**
* @RDE_HEADER@
* LtsCoordVector
* $Id$
* $HeadURL$
* $Author$
* Created by Norbert Thoden, ESVG, 2025-01-06
*/
#include "lts_coord_vector.h"
namespace LtsCoordTrans
{
LtsCoordVector::LtsCoordVector(const std::vector<Core::GeoPosition> &geo_coordinates)
: m_geo_coordinates(geo_coordinates)
{
}
const std::vector<UtmCoordinate> &LtsCoordVector::toStdUtmCoord(int zone) const
{
convertGeoToStdUtm(zone);
return m_utmMapStd[zone];
}
const QVector<UtmCoordinate> &LtsCoordVector::toQVectorUtmCoord(int zone) const
{
convertGeoToQVectorUtm(zone);
return m_utmMapQVector[zone];
}
const QVector<QPointF> &LtsCoordVector::toQVectorQPointF(int zone) const
{
convertGeoToQVectorQPointF(zone);
return m_utmMapQVectorQPointF[zone];
}
void LtsCoordVector::convertGeoToStdUtm(int zone) const
{
const auto it = m_utmMapStd.find(zone);
if (it != m_utmMapStd.end())
return;
convertGeoToUtmInternal(zone, m_utmMapStd[zone]); // do real conversion
}
void LtsCoordVector::convertGeoToQVectorUtm(int zone) const
{
const auto it = m_utmMapQVector.find(zone);
if (it != m_utmMapQVector.end())
return;
// approach 1:
// convertGeoToUtmInternal(zone, m_utmMapQVector[zone]);
// approach 2:
convertGeoToStdUtm(zone);
QVector<LtsCoordTrans::UtmCoordinate> &utmVectorRef = m_utmMapQVector[zone];
utmVectorRef.clear();
utmVectorRef.reserve(m_geo_coordinates.size());
for (const auto &utmCoord : m_utmMapStd[zone])
utmVectorRef.push_back(utmCoord);
}
void LtsCoordVector::convertGeoToQVectorQPointF(int zone) const
{
const auto it = m_utmMapQVectorQPointF.find(zone);
if (it != m_utmMapQVectorQPointF.end())
return;
convertGeoToStdUtm(zone);
QVector<QPointF> &QVectorQPointFRef = m_utmMapQVectorQPointF[zone];
QVectorQPointFRef.clear();
QVectorQPointFRef.reserve(m_geo_coordinates.size());
for (const auto &utmCoord : m_utmMapStd[zone])
QVectorQPointFRef.push_back(QPointF(utmCoord.x, utmCoord.y));
}
//------------------------------------------------------------------------------
// class LtsCoordinate
//------------------------------------------------------------------------------
// output operator:
//std::ostream &operator<<(std::ostream &stream, const LtsCoordVector &value)
//{
// stream << value.toString().toStdString();
// return stream;
//}
} // namespace LtsCoordTrans
+92
View File
@@ -0,0 +1,92 @@
/**
* @RDE_HEADER@
* LtsCoordVector
* $Id$
* $HeadURL$
* $Author$
* Created by Norbert Thoden, ESVG, 2025-01-06
*/
#pragma once
#include <LtsCoordinate/ltscoordinate_export.h>
#include <Core/core.hpp> // Core::GeoPosition
#include <LtsCoordTrans/lts_coord_trans.h> // UtmCoordinate
#include <unordered_map>
#include <QPointF>
#include <QVector>
#include <QDebug>
#include <iosfwd>
#include <vector>
namespace LtsCoordTrans
{
/*!
* \brief The LtsCoordVector class caches conversion results of vector<Core::GeoPosition> to vector<LtsCoordTrans::UtmCoordinate>
* \section secMotivation Motivation
* Especially in ISM there are several situations where this kind of conversion is executed several times and caching could be helpful.
*
* Please see also the UnitTest UnitTestCoordinate.
*/
class LTSCOORDINATE_EXPORT LtsCoordVector
{
public:
/*! \name Creation */
//@{
explicit LtsCoordVector(const std::vector<Core::GeoPosition> &geo_coordinates);
/*! \name Access, part 1 - without Conversion*/
//@{
int size() const { return m_geo_coordinates.size() ; }
const std::vector<Core::GeoPosition> &geoPosition() const { return m_geo_coordinates; }
//@}
/*! \name Access, part 2 - with Conversion */
//@{
const std::vector<LtsCoordTrans::UtmCoordinate> &toStdUtmCoord(int zone) const;
const QVector<LtsCoordTrans::UtmCoordinate> &toQVectorUtmCoord(int zone) const;
const QVector<QPointF> &toQVectorQPointF(int zone) const;
//@}
protected:
void convertGeoToStdUtm(int zone) const;
void convertGeoToQVectorUtm(int zone) const;
void convertGeoToQVectorQPointF(int zone) const;
template<typename T>
void convertGeoToUtmInternal(int zone, T &utmVectorRef) const
{
// do real conversion:
utmVectorRef.clear();
utmVectorRef.reserve(m_geo_coordinates.size());
for (const auto &geoCoord : qAsConst(m_geo_coordinates))
utmVectorRef.push_back(LtsCoordTrans::UtmCoordinate::fromGeographic(geoCoord.lon(), geoCoord.lat(), zone));
}
protected:
std::vector<Core::GeoPosition> m_geo_coordinates;
// prefer std::unordered_map, since 'T& operator[]' is used also for const methods
// like toUtmStd(int zone) const
// ^^^^^
mutable std::unordered_map<int, std::vector<LtsCoordTrans::UtmCoordinate>> m_utmMapStd; // zone, vector<>
mutable std::unordered_map<int, QVector<LtsCoordTrans::UtmCoordinate>> m_utmMapQVector; // zone, vector<>
mutable std::unordered_map<int, QVector<QPointF>> m_utmMapQVectorQPointF; // zone, vector<>
};
// output operator:
//LTSCOORDINATE_EXPORT std::ostream &operator<< (std::ostream &stream,
// const LtsCoordVector &value);
} // namespace LtsCoordTrans
+184
View File
@@ -0,0 +1,184 @@
/**
* @RDE_HEADER@
* LtsCoordinate
* $Id$
* $HeadURL$
* $Author$
* Created by Norbert Thoden, ESVG, 2024-08-27
*/
#include "lts_coordinate.h"
#include <QLineF>
namespace LtsCoordTrans
{
static long s_conversion_counter = 0; // counts conversion
//
std::function<double(double, double, double, bool*)> LtsCoordinate::m_getElevationCB;
//------------------------------------------------------------------------------
// class LtsCoordinate
//------------------------------------------------------------------------------
void LtsCoordinate::setElevationCallback(const elevationCbFunc &elevationCB)
{
m_getElevationCB = elevationCB;
}
bool LtsCoordinate::hasElevationCallback()
{
return m_getElevationCB != nullptr;
}
double LtsCoordinate::elevationAt(double defaultElevation, bool *bIsCovered) const
{
// shall we cache bIsCovered also?
// no. If bIsCovered is provided, we explicitly call m_getElevationCB
// if not, we benefit from cache (m_elevation);
if (!bIsCovered)
if (!std::isnan(m_elevation))
return m_elevation;
if (m_getElevationCB)
m_elevation = m_getElevationCB(m_geo.lon(), m_geo.lat(), defaultElevation, bIsCovered);
return m_elevation;
}
void LtsCoordinate::setZValueAccordingElevation(double defaultElevation)
{
m_geo.z(elevationAt(defaultElevation));
}
double LtsCoordinate::elevationAt(const LtsCoordinate &coordinate, double defaultElevation, bool *bIsCovered)
{
return coordinate.elevationAt(defaultElevation, bIsCovered);
}
void LtsCoordinate::replaceZValueByElevationIfValue(double zValueToBeReplaced, double defaultElevation)
{
// 2024-10: Norbert Thoden
// if (qFuzzyCompare(m_geo.z(), zValueToBeReplaced)) did not work with NaN as expected
// qFuzzyCompare(std::nan(""), std::nan("")) will return 'false' !!!
// so check NaN explicit:
if ((std::isnan(zValueToBeReplaced) && std::isnan(m_geo.z())) ||
qFuzzyCompare(m_geo.z(), zValueToBeReplaced))
setZValueAccordingElevation(defaultElevation);
}
double LtsCoordinate::angleDegreeUtm2D(const LtsCoordinate &coord1, const LtsCoordinate &coord2)
{
const LtsCoordTrans::UtmCoordinate &utm1 = coord1.utmCoordinate(); // implicit coord1.zone will be set
const LtsCoordTrans::UtmCoordinate &utm2 = coord2.utmCoordinateDiffZone(utm1.zone);
const QPointF& offsetUtm = utm2.toPoint() - utm1.toPoint();
QLineF poly(QPointF(), offsetUtm);
const double angle = poly.angle();
// QLineF::angle()
// 270
// |
// |
// 180 --------- 0
// |
// |
// 90
// desired:
// 0
// |
// |
// 270 --------- 90
// |
// |
// 180
return fmod(angle+90.0, 360);
}
const UtmCoordinate &LtsCoordinate::utmCoordinateInternal(int zone) const
{
if (zone == -1)
{
if (!hasUtm())
convertGeoToUtm();
return m_utm2D; // <- return
}
utmCoordinateDiffZone(zone); // ensure m_utm2D_differentZone
return m_utm2D_differentZone; // <- return
}
void LtsCoordinate::convertGeoToUtm() const
{
// geo -> utm
int zone = -1;
// avoid working with NaN
if (std::isnan(m_geo.lon()) || std::isnan(m_geo.lat()))
{
fprintf(stderr, "Error while fromGeographic, value is NaN\n");
m_utm2D = LtsCoordTrans::UtmCoordinate();
return;
}
#ifdef LTS_DEBUG
++s_conversion_counter;
#endif // LTS_DEBUG
m_utm2D = LtsCoordTrans::UtmCoordinate::fromGeographic(m_geo.lon(), m_geo.lat(), zone);
Q_ASSERT_X(m_utm2D.zone > 0, "LtsCoordinate", "zone has to be set");
}
QString LtsCoordinate::mgrs(int prec, bool withSpaces) const
{
utmCoordinateInternal(); // ensure m_utm2D
QString tmp = m_utm2D.mgrs(prec, withSpaces);
return tmp;
}
QString LtsCoordinate::mgrsWithZ(int prec, bool withSpaces) const
{
double ele = elevationAt();
QString tmp = mgrs(prec, withSpaces) + QStringLiteral(" z=") + QString::number(ele, 'f', 2);
return tmp;
}
QString LtsCoordinate::toString(bool fullAndExpensive) const
{
if (fullAndExpensive)
{
utmCoordinateInternal(); // ensure m_utm2D
elevationAt(); // ensure m_elevation
}
std::ostringstream stream;
stream << "LtsCoordinate(m_geo=" << m_geo
<< ", m_utm2D.x " << m_utm2D.x << ", m_utm2D.y " << m_utm2D.y << ", m_utm2D.zone " << m_utm2D.zone;
if (!qFuzzyIsNull(m_utm2D.x) || !qFuzzyIsNull(m_utm2D.y))
stream << ", m_utm2D.mgrs '" << m_utm2D.mgrs().toUtf8().constData() << "'";
stream << ", elevation " << std::fixed << std::setprecision(3) << m_elevation // << std::fixed << std::setprecision(3) -> 3 decimal places
<< ")";
return QString::fromStdString(stream.str());
}
void LtsCoordinate::resetConversionCounter()
{
s_conversion_counter = 0;
}
long LtsCoordinate::getConversionCounter()
{
return s_conversion_counter;
}
// output operator:
std::ostream &operator<<(std::ostream &stream, const LtsCoordinate &value)
{
stream << value.toString().toStdString();
return stream;
}
} // namespace LtsCoordTrans
+417
View File
@@ -0,0 +1,417 @@
/**
* @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