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

+78
View File
@@ -0,0 +1,78 @@
#
# $Id$
# $HeadURL$
# $Author$
# Created by Christian Ehrlicher, SLSE, 2016-08-31
#
FIND_PACKAGE(GeographicLib REQUIRED)
NGL_ADD_LIBRARY2(LtsCoordTrans WS AAR SV HLA SHARED
lts_coord_trans.h
lts_coord_trans.cpp
)
NGL_TARGET_LINK_LIBRARIES(LtsCoordTrans
PRIVATE
GeographicLib
PUBLIC
Qt5::Core
)
NGL_TARGET_INCLUDE_DIRECTORIES(LtsCoordTrans
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../
)
SET(ltsCoordWidget_SRCS
lts_coord_widget.cpp
lts_coord_widget.h
lts_coord_widget_p.cpp
lts_coord_widget_p.h
)
NGL_WRAP_UI(ltsCoordWidget_SRCS
lts_coord_widget_mgrs.ui
lts_coord_widget_wgs84.ui
)
NGL_ADD_LIBRARY2(ltsCoordWidget WS AAR SV SHARED ${ltsCoordWidget_SRCS})
NGL_TARGET_LINK_LIBRARIES(ltsCoordWidget
PRIVATE
LtsCoordTrans
PUBLIC
Qt5::Core
Qt5::Widgets
)
NGL_TARGET_INCLUDE_DIRECTORIES(ltsCoordWidget
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../
)
IF(TARGET LtsCoordTrans)
SET(_tools lonlat2mgrs mgrs2lonlat)
FOREACH(_tool ${_tools})
NGL_ADD_EXECUTABLE(ALL ${_tool} ${_tool}.cpp)
NGL_TARGET_LINK_LIBRARIES(${_tool}
Qt5::Core
GeographicLib
)
NGL_INSTALL_TARGET(${_tool})
ENDFOREACH()
ENDIF()
IF(BUILD_TESTING AND TARGET ltsCoordWidget)
ADD_EXECUTABLE(lts_coord_widget_test lts_coord_widget_test.cpp)
TARGET_LINK_LIBRARIES(lts_coord_widget_test
ltsCoordWidget
LtsCoordTrans
)
ENDIF()
IF(BUILD_TESTING AND TARGET LtsCoordTrans)
ADD_EXECUTABLE(lts_coord_trans_unit_test
lts_coord_trans_unit_test.cpp
lts_coord_trans_unit_test.h
)
TARGET_LINK_LIBRARIES(lts_coord_trans_unit_test
LtsCoordTrans
Qt5::Test
GeographicLib
)
ADD_TEST(NAME lts_coord_trans_unit_test COMMAND $<TARGET_FILE:lts_coord_trans_unit_test>)
ENDIF()
+71
View File
@@ -0,0 +1,71 @@
/**
* Convert a lon/lat coordinate to mgrs
* $Id$
* $HeadURL$
* $Author$
*/
#include <GeographicLib/UTMUPS.hpp>
#include <GeographicLib/MGRS.hpp>
#include <cstring>
#include <string>
int main(int argc, char **argv)
{
if (argc < 3 || argc > 5)
{
fprintf(stderr, "Usage: %s <lon_degree> <lat_degree> [<precision>]\n", argv[0]);
fprintf(stderr, "Usage: %s <lon_rad> <lat_rad> -r [<precision>]\n", argv[0]);
return -1;
}
bool lonLatInRad = false;
double lon = atof(argv[1]);
double lat = atof(argv[2]);
int prec = 5;
if (argc >= 4)
{
int precArgId = -1;
if (strcmp(argv[3], "-r") == 0)
{
lonLatInRad = true;
if (argc == 5)
precArgId = 4;
}
else
precArgId = 3;
if (precArgId > 0)
prec = atoi(argv[precArgId]);
}
std::string mgrs;
int zone;
bool northp;
double x, y;
try
{
const double latDeg = lonLatInRad ? lat * 180 / M_PI : lat;
const double lonDeg = lonLatInRad ? lon * 180 / M_PI : lon;
GeographicLib::UTMUPS::Forward(latDeg, lonDeg, zone, northp, x, y);
GeographicLib::MGRS::Forward(zone, northp, x, y, prec, mgrs);
}
catch (...)
{
fprintf(stderr, "Error converting %9.6f, %9.6f to MGRS coords\n", lat, lon);
return -2;
}
std::string part1, part2, part3, part4;
part1 = mgrs.substr(0, 3);
part2 = mgrs.substr(3, 2);
if (prec > 0)
{
part3 = mgrs.substr(5, prec);
part4 = mgrs.substr(5 + prec, prec);
}
fprintf(stdout, "%13.10f, %13.10f = %s %s %s %s (UTM: %d %.2f, %.2f %c)\n",
lon, lat,
part1.c_str(), part2.c_str(), part3.c_str(), part4.c_str(),
zone, x, y, northp ? 'N' : 'S');
return 0;
}
+452
View File
@@ -0,0 +1,452 @@
/**
* @RDE_HEADER@
* Provide some coordinate translation functions on top of GeographicLib
* $Id$
* $HeadURL$
* $Author$
*
* Created by Christian Ehrlicher, SLSE, 2016-08-31
*/
#include "lts_coord_trans.h"
#include <GeographicLib/Geocentric.hpp>
#include <GeographicLib/UTMUPS.hpp>
#include <GeographicLib/MGRS.hpp>
#include <QPointF>
#include <QRectF>
#include <QString>
#include <QStringList>
// #include <QDebug>
#include <iomanip> // setprecision
namespace LtsCoordTrans
{
static long s_conversion_counter_MGRS_Forward = 0; // counts conversion
static long s_conversion_counter_MGRS_Reverse = 0; // counts conversion
static long s_conversion_counter_WGS84_Forward = 0; // counts conversion
static long s_conversion_counter_WGS84_Reverse = 0; // counts conversion
static long s_conversion_counter_UTMUPS_Forward = 0; // counts conversion
static long s_conversion_counter_UTMUPS_Reverse = 0; // counts conversion
inline void incrementCounter(long &counterRef)
{
#ifdef LTS_DEBUG
++counterRef;
#endif
}
static inline std::string cleanupMgrsString(const QString &in)
{
const QByteArray ba(in.toLatin1());
std::string ret;
ret.reserve(ba.size());
for (const char c : ba)
{
if (c != ' ')
ret += c;
}
return ret;
}
QString UtmCoordinate::mgrs(int prec, bool withSpaces) const
{
if (prec < 0)
prec = 0;
if (prec > 11)
prec = 11;
std::string mgrs;
try
{
GeographicLib::MGRS::Forward(zone, northp, x, y, prec, mgrs);
incrementCounter(s_conversion_counter_MGRS_Forward);
}
catch (const std::exception &e)
{
fprintf(stderr, "Error while calculating mgrs from utm %d, %9.6f, %9.6f: %s\n", zone, x, y, e.what());
mgrs.clear();
}
QString ret;
if (withSpaces && mgrs.size() == static_cast<size_t>(5 + 2 * prec))
{
// 32UMD1234512345 -> 32U MD 12345 12345
const char *ptr = mgrs.c_str();
// for utm zones < 10, GeiographicLib appends a zero -> 08
ret = QString::fromLatin1(ptr, 3) + QLatin1Char(' ') +
QString::fromLatin1(ptr + 3, 2);
if (prec > 0)
{
ret += QLatin1Char(' ') + QString::fromLatin1(ptr + 5, prec) +
QLatin1Char(' ') + QString::fromLatin1(ptr + 5 + prec, prec);
}
}
else
ret = QString::fromLatin1(mgrs.c_str(), mgrs.size());
return ret;
}
QPointF UtmCoordinate::toPoint() const
{
return {x,y};
}
QPointF UtmCoordinate::geographic(double offsetX, double offsetY) const
{
QPointF ret;
try
{
GeographicLib::UTMUPS::Reverse(zone, northp, x + offsetX, y + offsetY, ret.ry(), ret.rx());
incrementCounter(s_conversion_counter_UTMUPS_Reverse);
}
catch (const std::exception &e)
{
fprintf(stderr, "Error while calculating geographic from utm %d, %9.6f, %9.6f: %s\n", zone, x + offsetX, y + offsetY, e.what());
ret = QPointF();
}
return ret * deg2rad();
}
QPointF UtmCoordinate::geographic(const QPointF &offset) const
{
return geographic(offset.x(), offset.y());
}
QPointF UtmCoordinate::relativeTo(const UtmCoordinate &other) const
{
if (zone == other.zone)
return QPointF(x - other.x, y - other.y);
UtmCoordinate otherInMyZone = fromGeographic(other.geographic(), zone);
return QPointF(x - otherInMyZone.x, y - otherInMyZone.y);
}
UtmCoordinate UtmCoordinate::fromGeographic(double lon, double lat, int setZone)
{
// radian -> degree
lon *= rad2deg();
lat *= rad2deg();
UtmCoordinate ret;
try
{
// lat/lon -> utm
GeographicLib::UTMUPS::Forward(lat, lon, ret.zone, ret.northp, ret.x, ret.y, setZone);
incrementCounter(s_conversion_counter_UTMUPS_Forward);
}
catch (const std::exception &e)
{
fprintf(stderr, "Error while calculating mgrs base point from %9.6f, %9.6f: %s\n", lat, lon, e.what());
ret = UtmCoordinate();
}
return ret;
}
UtmCoordinate UtmCoordinate::fromGeographic(const QPointF &lonLat, int setZone)
{
return fromGeographic(lonLat.x(), lonLat.y(), setZone);
}
UtmCoordinate UtmCoordinate::fromMgrs(const QString &mgrs)
{
UtmCoordinate utm;
try
{
int prec;
// centerp must be true (GeographicLib::MGRS::Forward calculates with this!)
GeographicLib::MGRS::Reverse(cleanupMgrsString(mgrs), utm.zone, utm.northp, utm.x, utm.y, prec, true);
incrementCounter(s_conversion_counter_MGRS_Reverse);
}
catch (const std::exception &e)
{
fprintf(stderr, "Error while converting mgrs coordinate %s to utm: %s\n", qPrintable(mgrs), e.what());
utm = UtmCoordinate();
}
return utm;
}
QString mgrsFromGeographic(double lon, double lat, int prec, bool withSpaces)
{
return UtmCoordinate::fromGeographic(lon, lat, -1).mgrs(prec, withSpaces);
}
QPointF geographicFromMgrs(const QString &mgrs)
{
return UtmCoordinate::fromMgrs(mgrs).geographic();
}
QVector<UtmCoordinate> &staticDefaultSquaresHelper()
{
static QVector<UtmCoordinate> s_defaultMgrsSquares;
return s_defaultMgrsSquares;
}
UtmCoordinate &staticPreferedMgrsSquareHelper()
{
static UtmCoordinate s_preferedMgrsSquare;
return s_preferedMgrsSquare;
}
QVector<UtmCoordinate> &staticMgrsSquaresWithOffsetsHelper()
{
static QVector<UtmCoordinate> s_mgrsSquaresWithOffsets;
return s_mgrsSquaresWithOffsets;
}
QRectF addMarginToArea(const QRectF &rect_LatLon_radiant, const QSize &marginMeter)
{
double lonMin, lonMax, latMin, latMmax;
rect_LatLon_radiant.normalized().getCoords(&lonMin, &latMin, &lonMax, &latMmax);
// In that case upperRight and lowerLeft have to be converted to an UtmCoordinate
// using the utm zone of the basepoint.
// so caluclate the basepoint of the training area:
// see also basePointOfTrainingArea at Lts/Proxies/TrainingAreaBase/tab_training_area_helper.h
LtsCoordTrans::UtmCoordinate basePoint;
{
// calc tetra base point from training area center
const QPointF centerRad = rect_LatLon_radiant.center();
QString mgrs = LtsCoordTrans::mgrsFromGeographic(centerRad.x(), centerRad.y(), -1);
mgrs += QStringLiteral("00000000000000");
// now we've the correct tetra base point
basePoint = LtsCoordTrans::UtmCoordinate::fromMgrs(mgrs);
}
const UtmCoordinate lowerLeft(UtmCoordinate::fromGeographic(lonMin, latMin, basePoint.zone));
const UtmCoordinate upperRight(UtmCoordinate::fromGeographic(lonMax, latMmax, basePoint.zone));
int offsetX = marginMeter.width();
int offsetY = marginMeter.height();
const QPointF topLeft(lowerLeft.geographic(-offsetX, -offsetY));
const QPointF bottomRight(upperRight.geographic(offsetX, offsetY));
QRectF res = QRectF(topLeft, bottomRight);
return res;
}
QVector<UtmCoordinate> getMgrsSquares(const QRectF &rect, bool setAsDefaultSquares, bool setAsPreferedSquare, bool setAsSquareWithMargin)
{
QVector<UtmCoordinate> ret = getMgrsSquaresInternal(rect);
if (setAsDefaultSquares)
staticDefaultSquaresHelper() = ret;
if (setAsPreferedSquare)
staticPreferedMgrsSquareHelper() = LtsCoordTrans::UtmCoordinate::fromGeographic(rect.center());
if (setAsSquareWithMargin)
staticMgrsSquaresWithOffsetsHelper() = ret;
return ret;
}
QVector<UtmCoordinate> getMgrsSquaresInternal(const QRectF &rect)
{
QVector<UtmCoordinate> ret;
QVector<QString> tmpMgrsSquares;
// calculate mgrs squares
// 10km - Schritte - hoffentlich sind wir nicht in kleineren Zonen unterwegs
double lonMin, lonMax, latMin, latMmax;
rect.normalized().getCoords(&lonMin, &latMin, &lonMax, &latMmax);
// maybe the training area is NOT within a single UTM square.
bool withInSingleUtmSquare = true;
{
const UtmCoordinate upperRight(UtmCoordinate::fromGeographic(lonMax, latMmax));
const UtmCoordinate lowerLeft(UtmCoordinate::fromGeographic(lonMin, latMin));
withInSingleUtmSquare = upperRight.zone == lowerLeft.zone;
}
// In that case upperRight and lowerLeft have to be converted to an UtmCoordinate
// using the utm zone of the basepoint.
// so caluclate the basepoint of the training area:
// see also basePointOfTrainingArea at Lts/Proxies/TrainingAreaBase/tab_training_area_helper.h
LtsCoordTrans::UtmCoordinate basePoint;
{
// calc tetra base point from training area center
QPointF centerRad = rect.center();
QString mgrs = LtsCoordTrans::mgrsFromGeographic(centerRad.x(), centerRad.y(), -1);
mgrs += QStringLiteral("00000000000000");
// now we've the correct tetra base point
basePoint = LtsCoordTrans::UtmCoordinate::fromMgrs(mgrs);
}
const UtmCoordinate upperRight(UtmCoordinate::fromGeographic(lonMax, latMmax, basePoint.zone));
const UtmCoordinate lowerLeft(UtmCoordinate::fromGeographic(lonMin, latMin, basePoint.zone));
const double maxX(upperRight.x - lowerLeft.x); // meter
const double maxY(upperRight.y - lowerLeft.y); // meter
// +10000 um auch den Rest rechts und oben zu berücksichtigen
// allerdings dann genau den Rand und auf keinen Fall darüber hinaus
const int increment = withInSingleUtmSquare ? 10000 : 1000;
for (double x = 0; x <= maxX + increment; x += increment)
{
const double xOfs = std::min(x, maxX);
for (double y = 0; y <= maxY + increment; y += increment)
{
const double yOfs = std::min(y, maxY);
// convert to utm coordinate on mgrs intersection point
const UtmCoordinate utm(UtmCoordinate::fromGeographic(lowerLeft.geographic(xOfs, yOfs)));
const QString mgrs(utm.mgrs(0));
// qDebug("mgrs: %s, %s, %lf, %lf", qPrintable(mgrs), qPrintable(utm.mgrs(5)), xOfs, yOfs);
if (!tmpMgrsSquares.contains(mgrs))
{
tmpMgrsSquares += mgrs;
ret += utm;
}
}
}
return ret;
}
QStringList getMgrsSurroundingSquares(const QRectF &geoRect, const QSize &offsetMeter)
{
// get main squares
const QVector<UtmCoordinate> mainUtms = getMgrsSquaresInternal(geoRect);
QStringList mainSquares;
mainSquares.reserve(mainUtms.count());
for (const auto &utm : mainUtms)
mainSquares.append(utm.mgrs(0));
// get mainsquare with surrounding squares rect by main square center pos and offsets
const QRectF rectWithOffset = addMarginToArea(geoRect,offsetMeter);
QVector<UtmCoordinate> mainUtmsWithOffset = getMgrsSquaresInternal(rectWithOffset);
QStringList mainSquaresWithOffset;
mainSquaresWithOffset.reserve(mainUtmsWithOffset.count());
for (const auto &utm : qAsConst(mainUtmsWithOffset))
mainSquaresWithOffset.append(utm.mgrs(0));
// get only surrounding squares
QStringList surroundingSquares;
surroundingSquares.reserve(mainSquaresWithOffset.count());
for (const auto & mgrs0 : mainSquaresWithOffset)
{
if (!mainSquares.contains(mgrs0))
surroundingSquares.append(mgrs0);
}
return surroundingSquares;
}
QVector<UtmCoordinate> defaultMgrsSquareWithOffsets()
{
return staticMgrsSquaresWithOffsetsHelper();
}
void setDefaultMgrsSquares(const QVector<QString> &squares)
{
QVector<UtmCoordinate> coords;
coords.reserve(squares.size());
for (const QString &s : squares)
coords += UtmCoordinate::fromMgrs(s + QStringLiteral("00000000000000"));
staticDefaultSquaresHelper() = coords;
}
QVector<UtmCoordinate> defaultMgrsSquares()
{
return staticDefaultSquaresHelper();
}
UtmCoordinate preferedMgrsSquare()
{
return staticPreferedMgrsSquareHelper();
}
bool lonLatAltToGeocentric(double lon, double lat, double altitude, double &xOut, double &yOut, double &zOut)
{
// radian -> degree
lon *= rad2deg();
lat *= rad2deg();
try
{
GeographicLib::Geocentric::WGS84().Forward(lat, lon, altitude, xOut, yOut, zOut);
incrementCounter(s_conversion_counter_WGS84_Forward);
}
catch (...)
{
xOut = 0;
yOut = 0;
zOut = 0;
return false;
}
return true;
}
bool geocentric2LonLatAlt(double x, double y, double z,
double &lon, double &lat, double &altitude)
{
// Achtung: LatLon statt LonLat
// Geocentric -> LonLat
try
{
GeographicLib::Geocentric::WGS84().Reverse(x, y, z, lat, lon, altitude);
incrementCounter(s_conversion_counter_WGS84_Reverse);
}
catch (...)
{
lon = 0;
lat = 0;
altitude = 0;
return false;
}
// degree -> radian
lon *= deg2rad();
lat *= deg2rad();
return true;
}
double distance(const QPointF &p1, const QPointF &p2)
{
return distance(p1.x(), p1.y(), p2.x(), p2.y());
}
double squareDistance(const QPointF &p1, const QPointF &p2)
{
return squareDistance(p1.x(), p1.y(), p2.x(), p2.y());
}
std::ostream &operator<< (std::ostream &stream,
const UtmCoordinate &coord)
{
stream << "UtmCoordinate(" << std::setprecision(8) << coord.x
<< "#" << std::setprecision(8) << coord.y
<< " zone=" << coord.zone << ")";
return stream;
}
void dumpConversionCounter()
{
fprintf(stderr, "LtsCoordTrans: s_conversion_counter_MGRS_Forward : - mgrs : %ld\n" , s_conversion_counter_MGRS_Forward);
fprintf(stderr, "LtsCoordTrans: s_conversion_counter_MGRS_Reverse - fromMgrs : %ld\n" , s_conversion_counter_MGRS_Reverse);
fprintf(stderr, "LtsCoordTrans: s_conversion_counter_WGS84_Forward - lonLatAltToGeocentric : %ld\n" , s_conversion_counter_WGS84_Forward);
fprintf(stderr, "LtsCoordTrans: s_conversion_counter_WGS84_Reverse - geocentric2LonLatAlt : %ld\n" , s_conversion_counter_WGS84_Reverse);
fprintf(stderr, "LtsCoordTrans: s_conversion_counter_UTMUPS_Forward - fromGeographic : %ld\n" , s_conversion_counter_UTMUPS_Forward);
fprintf(stderr, "LtsCoordTrans: s_conversion_counter_UTMUPS_Reverse - geographic : %ld\n" , s_conversion_counter_UTMUPS_Reverse);
}
long getConversionCounter_MGRS_Forward() { return s_conversion_counter_MGRS_Forward; }
long getConversionCounter_MGRS_Reverse() { return s_conversion_counter_MGRS_Reverse; }
long getConversionCounter_WGS84_Forward() { return s_conversion_counter_WGS84_Forward; }
long getConversionCounter_WGS84_Reverse() { return s_conversion_counter_WGS84_Reverse; }
long getConversionCounter_UTMUPS_Forward() { return s_conversion_counter_UTMUPS_Forward; }
long getConversionCounter_UTMUPS_Reverse() { return s_conversion_counter_UTMUPS_Reverse; }
} // namespace LtsCoordTrans
+355
View File
@@ -0,0 +1,355 @@
/**
* @RDE_HEADER@
* Provide some coordinate translation functions on top of GeographicLib
* $Id$
* $HeadURL$
* $Author$
*
* Created by Christian Ehrlicher, SLSE, 2016-08-31
*/
#pragma once
#include <LtsCoordTrans/ltscoordtrans_export.h>
#include <string>
#include <cmath>
#include <QVector>
class QPointF;
class QRectF;
class QSize;
class QString;
namespace LtsCoordTrans
{
/**
* What do we want to achieve:
* - internally our coordinates are represented as geographic coordinates
* (lat/lon, wgs84, radian)
* - convert them to a user-readable format which is (currently) MGRS
* - convert to/from a metric offset relative to a base point from/to
* geographic coordinates (e.g. for equipment in training area, for calculations)
* - calculate a base point from a given geogrphic coordinate (base point
* should be on an mgrs intersection (e.g. 32UPD 0 0)
*
* a nice view of utm and mgrs square could be found here:
* https://mappingsupport.com/p2/coordinates-mgrs-gissurfer-maps.html
*/
// some helper functions
inline Q_CONSTEXPR double pi() { return M_PI; }
inline Q_CONSTEXPR double deg2rad() { return pi() / 180.; }
inline Q_CONSTEXPR double rad2deg() { return 1. / deg2rad(); }
struct LTSCOORDTRANS_EXPORT UtmCoordinate
{
int zone = -1;
bool northp = true;
double x = 0.; // in meter
double y = 0.; // in meter
UtmCoordinate() = default;
UtmCoordinate(int zone_, double x_, double y_, bool northp_ = true)
: zone(zone_)
, northp(northp_)
, x(x_)
, y(y_)
{}
/**
* @brief Checks if the UtmCoordinate is properly initialized (just checks the zone)
* @return if UtmCoordinate is valid
*/
bool isValid() const { return zone > 0 && zone <= 60; }
/**
* @brief Convert the utm coordinate into a mgrs string (prettified - with spaces)
* @param prec precision to use (default: 5)
* @param withSpaces Insert spaces between mgrs parts
* @return mgrs string (empty if conversion did not work)
*/
QString mgrs(int prec = 5, bool withSpaces = true) const;
QPointF toPoint() const;
/**
* @brief Convert the utm coordinate to geographic coordinate, add offset if desired
* @param offsetX the possible x offset (east) to the utm coordinate
* @param offsetY the possible y offset (north) to the utm coorinate
* @return
*/
QPointF geographic(double offsetX = 0., double offsetY = 0.) const;
/**
* @brief overload of QPointF geographic(double, double) const
*/
QPointF geographic(const QPointF &offset) const;
/**
* @brief Get relative Position of coordinate to other coordinate
* @param other Coordinate to get the offset to.
* @return
*/
QPointF relativeTo(const UtmCoordinate &other) const;
/**
* @brief convert a geographic coordinate into a utm coordinate
* @param lon longitude in radian
* @param lat latitude in radian
* @param setZone override default zone (needed e.g. to be sure that every utm coord in an vector gets the same utm zone)
* @return BasePointInformation for conversions from/to relative coordinates
*/
static UtmCoordinate fromGeographic(double lon, double lat, int setZone = -1);
/**
* @brief overload of UtmCoordinate fromGeographic(double, double, bool)
*/
static UtmCoordinate fromGeographic(const QPointF &lonLat, int setZone = -1);
/**
* @brief convert a mgrs coordinate into a utm coordinate
* @param mgrs coordinate to convert
*/
static UtmCoordinate fromMgrs(const QString &mgrs);
};
/**
* @brief convert lon/lat coordinates to mgrs. Shortcut for UtmCoordinate::fromGeographic().mgrs()
* @param lon longitude in radian
* @param lat latitude in radian
* @param prec the precision for the mgrs string
* @param withSpaces Insert spaces between mgrs parts
* @return mgrs as one string with spaces
*/
LTSCOORDTRANS_EXPORT QString mgrsFromGeographic(double lon, double lat, int prec = 5, bool withSpaces = true);
/**
* @brief convert a mgrs string into geographic coordinates. Shortcut for UtmCoordinate::fromMgrs().geographic()
* @param mgrs coordinate to convert
* @return geographic coordinate (invalid QPointF on conversion errror)
*/
LTSCOORDTRANS_EXPORT QPointF geographicFromMgrs(const QString &mgrs);
/**
* @brief addMarginToArea add a margin in meter to a north _orientated_ area
* @param rect_LatLon_radiant rect top be increased
* @param marginMeter margin to be added
* @return increase Area
*/
LTSCOORDTRANS_EXPORT QRectF addMarginToArea(const QRectF &rect_LatLon_radiant, const QSize &marginMeter);
/**
* Return all MGRS squares covered by the current exercise area
* @param rect the rectangle to retriebe the mgrs squares for (in geographic coordinates)
* @param setAsDefaultSquares if true, then set the calculated squares as default squares returned by defaultMgrsSquares()
* @param setAsPreferedSquare if true, then set the center square as prefered square returned by preferedMgrsSquare()
* @return all current available mgrs squares
*/
LTSCOORDTRANS_EXPORT QVector<UtmCoordinate> getMgrsSquares(const QRectF &rect, bool setAsDefaultSquares, bool setAsPreferedSquare, bool setAsSquareWithMargin = false);
/**
* @brief getMgrsSquaresInternal
* @param rect
* @return
*/
QVector<UtmCoordinate> getMgrsSquaresInternal(const QRectF &rect);
/**
* @brief getMgrsSurroundingSquares
* @param geoRect
* @param offsetMeter
* @return
*/
QStringList getMgrsSurroundingSquares(const QRectF &geoRect, const QSize &offsetMeter);
/**
* @brief defaultMgrsSquareWithOffsets
* @return
*/
LTSCOORDTRANS_EXPORT QVector<UtmCoordinate> defaultMgrsSquareWithOffsets();
/**
* @brief Set default MGRS squares to use if getMgrsSquares() is not possible
*/
LTSCOORDTRANS_EXPORT void setDefaultMgrsSquares(const QVector<QString> &squares);
/**
* @brief Return previously set default squares (e.g. for LtsCoordWidget)
* @return all available mgrs squares for the rect set with getMgrsSquares()
*/
LTSCOORDTRANS_EXPORT QVector<UtmCoordinate> defaultMgrsSquares();
/**
* @brief Return previously set prefered square (e.g. for LtsCoordWidget)
* @return prefered mgrs square (center) of the rect set with getMgrsSquares()
*/
LTSCOORDTRANS_EXPORT UtmCoordinate preferedMgrsSquare();
/**
* @brief Convert lon/lat/altitude to geocentric coordinates, output in x,y,z
* @param lon longitude in radian
* @param lat latitude in radian
* @param altitude in meter above ground
* @param xOut geocentric x-coordinate
* @param yOut geocentric y-coordinate
* @param zOut geocentric z-coordinate
* @return true on success
*/
LTSCOORDTRANS_EXPORT bool lonLatAltToGeocentric(double lon, double lat, double altitude, double &xOut, double &yOut, double &zOut);
/**
* @brief Convert geocentric coordinates to lon/lat/altitude output in lon,lat,altiude
* @param x geocentric x-coordinate
* @param y geocentric y-coordinate
* @param z geocentric z-coordinate
* @param lon longitude in radian
* @param lat latitude in radian
* @param altitude in meter above ground
* @return true on success
*/
LTSCOORDTRANS_EXPORT bool geocentric2LonLatAlt(double x, double y, double z,
double &lon, double &lat, double &altitude);
/**
* Overload of double squareDistance(const double lon1, const double lat1, const double lon2, const double lat2)
*/
inline double squareDistance(const UtmCoordinate &p1, const UtmCoordinate &p2)
{
if (p1.zone != p2.zone)
{
Q_ASSERT(false);
return std::nan("");
}
return std::pow(p1.x - p2.x, 2) + std::pow(p1.y - p2.y, 2);
}
/**
* Overload of double squareDistance(const double lon1, const double lat1, const double lon2, const double lat2)
*/
LTSCOORDTRANS_EXPORT double squareDistance(const QPointF &p1, const QPointF &p2);
/**
* @brief Calculate the square distance between two geographicPoints in meter
* @param lon1 longitude of the first point
* @param lat1 latitude of the first point
* @param lon2 longitude of the second point
* @param lat2 latitude of the second point
* @return distance between the two points in meter
*/
inline double squareDistance(const double lon1, const double lat1, const double lon2, const double lat2)
{
const auto p1(UtmCoordinate::fromGeographic(lon1, lat1));
const auto p2(UtmCoordinate::fromGeographic(lon2, lat2, p1.zone));
return squareDistance(p1, p2);
}
/**
* Overload of double squareDistance(const double lon1, const double lat1, const double lon2, const double lat2)
*/
inline double squareDistance(const UtmCoordinate &p1, const double lon2, const double lat2)
{
const auto p2(UtmCoordinate::fromGeographic(lon2, lat2, p1.zone));
return squareDistance(p1, p2);
}
/**
* Overload of double distance(const double lon1, const double lat1, const double lon2, const double lat2)
*/
inline double distance(const UtmCoordinate &p1, const UtmCoordinate &p2)
{
if (p1.zone != p2.zone)
{
Q_ASSERT(false);
return std::nan("");
}
return sqrt(squareDistance(p1, p2));
}
/**
* Overload of double distance(const double lon1, const double lat1, const double lon2, const double lat2)
*/
LTSCOORDTRANS_EXPORT double distance(const QPointF &p1, const QPointF &p2);
/**
* @brief Calculate the distance between two geographicPoints in meter
* @param lon1 longitude of the first point
* @param lat1 latitude of the first point
* @param lon2 longitude of the second point
* @param lat2 latitude of the second point
* @return distance between the two points in meter
*/
inline double distance(const double lon1, const double lat1, const double lon2, const double lat2)
{
return sqrt(squareDistance(lon1, lat1, lon2, lat2));
}
inline double distance(const UtmCoordinate &p1, const double lon2, const double lat2)
{
return sqrt(squareDistance(p1, lon2, lat2));
}
// for debugging purpose:
LTSCOORDTRANS_EXPORT void dumpConversionCounter();
LTSCOORDTRANS_EXPORT long getConversionCounter_MGRS_Forward();
LTSCOORDTRANS_EXPORT long getConversionCounter_MGRS_Reverse();
LTSCOORDTRANS_EXPORT long getConversionCounter_WGS84_Forward();
LTSCOORDTRANS_EXPORT long getConversionCounter_WGS84_Reverse();
LTSCOORDTRANS_EXPORT long getConversionCounter_UTMUPS_Forward();
LTSCOORDTRANS_EXPORT long getConversionCounter_UTMUPS_Reverse();
LTSCOORDTRANS_EXPORT std::ostream &operator<< (std::ostream &stream,
const UtmCoordinate &coord);
//
// class UtmCoordinate3D
//
/*!
* \brief The class UtmCoordinate3D add just a zValue to its baseClass UtmCoordinate
*/
class LTSCOORDTRANS_EXPORT UtmCoordinate3D : public LtsCoordTrans::UtmCoordinate
{
using BaseClass = LtsCoordTrans::UtmCoordinate;
public:
UtmCoordinate3D()
{
reset();
}
explicit UtmCoordinate3D(const LtsCoordTrans::UtmCoordinate &utm2d, double _zValue = std::nan(""))
:BaseClass()
{
zone = utm2d.zone;
northp = utm2d.northp;
x = utm2d.x;
y = utm2d.y;
zValue = _zValue;
}
void reset()
{
zone = -1;
northp = true;
// To be consistent with LtsCoordinate:
// 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':
x = 0.; // in meter
y = 0.; // in meter
zValue = std::nan("");
}
double z() const { return zValue; }
protected:
double zValue{std::nan("")};
};
} // namespace LtsCoordTrans
+473
View File
@@ -0,0 +1,473 @@
/**
* Unit test for ltscoordtrans
* $Id$
* $HeadURL$
* $Author$
*/
#include "lts_coord_trans_unit_test.h"
#include "lts_coord_trans.h"
#include <GeographicLib/Math.hpp>
#include <QTest>
#include <QDebug>
QTEST_MAIN(coordtrans_test)
void coordtrans_test::initTestCase()
{
qDebug("sizeof(UtmCoordinate) = %lu", sizeof(LtsCoordTrans::UtmCoordinate));
}
void coordtrans_test::cleanupTestCase()
{}
void coordtrans_test::testFromGeographic()
{
// 32UMD78 = 8.6264954126, 53.1138437183 -> 32UMD
// 32UPD55 = 11.3003696521, 52.8224535191 -> 32UPD
// 40RBM88 = 54.8821607021, 24.2631973752 -> 40QBM
LtsCoordTrans::UtmCoordinate utm;
QPointF lonlat;
lonlat = QPointF(8.6264954126, 53.1138437183) * LtsCoordTrans::deg2rad();
utm = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y(), -1);
QCOMPARE(utm.mgrs(0), QStringLiteral("32U MD"));
utm = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y());
QCOMPARE(utm.mgrs(8), QStringLiteral("32U MD 74999999 84999999"));
lonlat = QPointF(11.3003696521, 52.8224535191) * LtsCoordTrans::deg2rad();
utm = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y(), -1);
QCOMPARE(utm.mgrs(0), QStringLiteral("32U PD"));
utm = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y());
QCOMPARE(utm.mgrs(5), QStringLiteral("32U PD 55000 55000"));
lonlat = QPointF(54.8821607021, 24.2631973752) * LtsCoordTrans::deg2rad();
utm = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y(), -1);
QCOMPARE(utm.mgrs(0), QStringLiteral("40R BM"));
utm = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y());
QCOMPARE(utm.mgrs(5), QStringLiteral("40R BM 85000 85000"));
// 40R BM 00000 00000 (= base point) is not a 'valid' point because
// it overlaps with 40Q BM and therefore it should be 40Q BM 00000 00000
utm = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("40R BM 00000 00000"));
QCOMPARE(utm.mgrs(0), QStringLiteral("40Q BM"));
}
void coordtrans_test::testMgrsFromGeographic()
{
// 8.6191049344, 53.1048309844 -> 32U MD 745000000 840000000
QString mgrs;
QPointF lonlat;
lonlat = QPointF(8.6191049344, 53.1048309844) * LtsCoordTrans::deg2rad();
mgrs = LtsCoordTrans::mgrsFromGeographic(lonlat.x(), lonlat.y(), 9);
QCOMPARE(mgrs, QStringLiteral("32U MD 745000000 840000000"));
mgrs = LtsCoordTrans::mgrsFromGeographic(lonlat.x(), lonlat.y(), 8, false);
QCOMPARE(mgrs, QStringLiteral("32UMD7450000084000000"));
}
void coordtrans_test::testRelativeMetricalOffet_data()
{
// define set of data needed
QTest::addColumn<QPoint>("offset");
QTest::addColumn<QString>("correctMgrs");
// noch in der urspruenglichen zone:
QTest::newRow("set 1") << QPoint( 5, 5) << QStringLiteral("32U PD 99995 99995");
// impliziter zonewechsel:
QTest::newRow("set 2") << QPoint(100, 100) << QStringLiteral("32U QE 00090 00090");
QTest::newRow("set 3") << QPoint(500, 500) << QStringLiteral("32U QE 00490 00490");
}
void coordtrans_test::testRelativeMetricalOffet()
{
QFETCH(QPoint, offset);
QFETCH(QString, correctMgrs);
const LtsCoordTrans::UtmCoordinate &utmRef = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32U PD 99990 99990"));
qDebug() << "utmRef = " << utmRef.mgrs();
{
LtsCoordTrans::UtmCoordinate utmRaw;
utmRaw.x = utmRef.x + offset.x();
utmRaw.y = utmRef.y + offset.y();
utmRaw.zone = utmRef.zone;
qDebug() << "offset = " << offset << " - utmRaw = " << utmRaw.mgrs();
QCOMPARE(utmRaw.mgrs(), correctMgrs);
}
// besser:
// - api, also ctor nutzen,
// - koennen es const machen
{
const LtsCoordTrans::UtmCoordinate utmSimple(utmRef.zone,
utmRef.x + offset.x(),
utmRef.y + offset.y());
qDebug() << "offset = " << offset << " - utmSimple = " << utmSimple.mgrs();
QCOMPARE(utmSimple.mgrs(), correctMgrs);
}
}
QRectF createArea(const QString &base,
int xCenter, int yCenter, int width, int height)
{
// qDebug() << "createArea : base: " << base;
QRectF areaRect = QRectF(xCenter - width / 2, yCenter - height / 2, width, height);
LtsCoordTrans::UtmCoordinate utmBase(LtsCoordTrans::UtmCoordinate::fromGeographic(LtsCoordTrans::geographicFromMgrs(base)));
const QPointF topLeft(utmBase.geographic(areaRect.left(), areaRect.top()));
const QPointF bottomRight(utmBase.geographic(areaRect.right(), areaRect.bottom()));
// qDebug() << "TopLeft: " << topLeft * LtsCoordTrans::rad2deg();
// qDebug() << "BottomRight: " << bottomRight * LtsCoordTrans::rad2deg();
QRectF rect (topLeft, bottomRight);
return rect;
}
void coordtrans_test::testAddMarginToArea()
{
// Altmark
{
qDebug() << "Altmark";
QRectF area = createArea(QStringLiteral("32U PD 0000000000"), 50000, 10000, 30000, 30000);
QRectF area1Increased = LtsCoordTrans::addMarginToArea(area, QSize(5000, 20000));
qDebug() << "topLeft : " << qSetRealNumberPrecision(15) << area.topLeft() << " - degree:" << area.topLeft()*LtsCoordTrans::rad2deg();
qDebug() << "topLeftInc : " << qSetRealNumberPrecision(15) << area1Increased.topLeft() << " - degree:" << area1Increased.topLeft()*LtsCoordTrans::rad2deg();
qDebug() << "bottomRight : " << qSetRealNumberPrecision(15) << area.bottomRight() << " - degree:" << area.bottomRight()*LtsCoordTrans::rad2deg();
qDebug() << "bottomRightInc : " << qSetRealNumberPrecision(15) << area1Increased.bottomRight() << " - degree:" << area1Increased.bottomRight()*LtsCoordTrans::rad2deg();
QVERIFY2( area1Increased.topLeft().x() < area.topLeft().x(), "addMarginToArea topLeft X failed");
QVERIFY2( area1Increased.topLeft().y() < area.topLeft().y(), "addMarginToArea topLeft Y failed");
QVERIFY2( area1Increased.bottomRight().x() > area.bottomRight().x(), "addMarginToArea bottomRight X failed");
QVERIFY2( area1Increased.bottomRight().y() > area.bottomRight().y(), "addMarginToArea bottomRight Y failed");
}
// Luxemburg:
{
qDebug() << "Luxemburg";
QRectF area = createArea(QStringLiteral("32U LA 0000000000"), 100, 100, 5000, 5000);
QRectF area1Increased = LtsCoordTrans::addMarginToArea(area, QSize(50000, 20000));
qDebug() << "topLeft : " << qSetRealNumberPrecision(15) << area.topLeft() << " - degree:" << area.topLeft()*LtsCoordTrans::rad2deg();
qDebug() << "topLeftInc : " << qSetRealNumberPrecision(15) << area1Increased.topLeft() << " - degree:" << area1Increased.topLeft()*LtsCoordTrans::rad2deg();
qDebug() << "bottomRight : " << qSetRealNumberPrecision(15) << area.bottomRight() << " - degree:" << area.bottomRight()*LtsCoordTrans::rad2deg();
qDebug() << "bottomRightInc : " << qSetRealNumberPrecision(15) << area1Increased.bottomRight() << " - degree:" << area1Increased.bottomRight()*LtsCoordTrans::rad2deg();
QVERIFY2( area1Increased.topLeft().x() < area.topLeft().x(), "addMarginToArea topLeft X failed");
QVERIFY2( area1Increased.topLeft().y() < area.topLeft().y(), "addMarginToArea topLeft Y failed");
QVERIFY2( area1Increased.bottomRight().x() > area.bottomRight().x(), "addMarginToArea bottomRight X failed");
QVERIFY2( area1Increased.bottomRight().y() > area.bottomRight().y(), "addMarginToArea bottomRight Y failed");
}
}
void coordtrans_test::testIncreaseRect()
{
// punkt definieren:
QPointF lonlat = QPointF(8.6191049344, 53.1048309844) * LtsCoordTrans::deg2rad();
QString mgrs = LtsCoordTrans::mgrsFromGeographic(lonlat.x(), lonlat.y(), 9);
QCOMPARE(mgrs, QStringLiteral("32U MD 745000000 840000000"));
const LtsCoordTrans::UtmCoordinate centerPoint = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y());
LtsCoordTrans::UtmCoordinate topLeftUtm;
LtsCoordTrans::UtmCoordinate topLeftUtm2;
// Variante zum Vergroessern #1 via .geographic
{
QPointF topLeftGeo = centerPoint.geographic(-1111, 2222); // offset_m
QPointF bottomRightGeo = centerPoint.geographic( 3000, -4000); // offset_m
topLeftUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(topLeftGeo);
const LtsCoordTrans::UtmCoordinate bottomRightUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(bottomRightGeo);
qDebug() << "topLeftUtm: " << topLeftUtm.x << " - " << qSetRealNumberPrecision(15) << topLeftUtm.y;
qDebug() << "bottomRightUtm: " << bottomRightUtm.x << " - " << qSetRealNumberPrecision(15) << bottomRightUtm.y;
}
// Variante zum Vergroessern #2 via addition vom UtmCoordinates
{
topLeftUtm2.x = centerPoint.x - 1111;
topLeftUtm2.y = centerPoint.y + 2222;
LtsCoordTrans::UtmCoordinate bottomRightUtm2;
bottomRightUtm2.x = centerPoint.x + 3000;
bottomRightUtm2.y = centerPoint.y - 4000;
qDebug() << "topLeftUtm2: " << topLeftUtm2.x << " - " << qSetRealNumberPrecision(15) << topLeftUtm2.y;
qDebug() << "bottomRightUtm2: " << bottomRightUtm2.x << " - " << qSetRealNumberPrecision(15) << bottomRightUtm2.y;
}
QVERIFY2(qFuzzyCompare(topLeftUtm.x, topLeftUtm2.x), "testIncreaseRect x failed");
QVERIFY2(qFuzzyCompare(topLeftUtm.y, topLeftUtm2.y), "testIncreaseRect y failed");
}
void coordtrans_test::testIncreaseRect2()
{
// punkt definieren:
QPointF lonlat = QPointF(8.6191049344, 53.1048309844) * LtsCoordTrans::deg2rad();
QString mgrs = LtsCoordTrans::mgrsFromGeographic(lonlat.x(), lonlat.y(), 9);
QCOMPARE(mgrs, QStringLiteral("32U MD 745000000 840000000"));
QRectF myRect;
QRectF myRectIncreased;
{
const LtsCoordTrans::UtmCoordinate centerPoint = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y());
QPointF topLeftGeo = centerPoint.geographic(-3000, 2000); // offset_m
QPointF bottomRightGeo = centerPoint.geographic( 3000, -2000); // offset_m
myRect = QRectF(topLeftGeo, bottomRightGeo).normalized();
qDebug() << "myRect : " << myRect;
qDebug() << "myRect center : " << myRect.center();
qDebug() << "myRect height : " << myRect.height();
}
// hier geht es los:
{
const QPointF centerGeo = myRect.center();
const LtsCoordTrans::UtmCoordinate centerUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(centerGeo);
QPointF topLeftGeo = centerUtm.geographic(-6000, 4000);
QPointF bottomRightGeo = centerUtm.geographic( 6000, -4000);
myRectIncreased = QRectF(topLeftGeo, bottomRightGeo).normalized();
qDebug() << "myRectIncreased : " << myRectIncreased;
qDebug() << "myRectIncreased center: " << myRectIncreased.center();
qDebug() << "myRectIncreased height: " << myRectIncreased.height();
}
// qFuzzyCompae ist zu empfindlich, daher machen wir es selbst:
QVERIFY2( (int)(myRectIncreased.center().x()*10000) == (int)(myRect.center().x()*10000), "testIncreaseRect2 centerX failed");
QVERIFY2( (int)(myRectIncreased.center().y()*10000) == (int)(myRect.center().y()*10000), "testIncreaseRect2 centerY failed");
QVERIFY2(myRectIncreased.width() > myRect.width(), "testIncreaseRect2 width failed");
QVERIFY2(myRectIncreased.height() > myRect.height(), "testIncreaseRect2 height failed");
}
void coordtrans_test::testGeographicFromMgrs()
{
// 32UPD7654398765 -> 11.6436815550, 53.2088411454
QString mgrs(QStringLiteral("32UPD7654398765"));
QPointF geographic = LtsCoordTrans::geographicFromMgrs(mgrs);
QCOMPARE(geographic, QPointF(11.6436815550, 53.2088411454) * LtsCoordTrans::deg2rad());
}
void coordtrans_test::testMgrsSquares()
{
// a nice view of utm and mgrs square could be found here:
// https://mappingsupport.com/p2/coordinates-mgrs-gissurfer-maps.html
checkArea(QStringLiteral("32U PD 0000000000"), 50000, 10000, 30000, 30000, { QStringLiteral("32U PC"),
QStringLiteral("32U PD")
});
checkArea(QStringLiteral("32U MD 0000000000"), 50000, 10000, 30000, 30000, { QStringLiteral("32U MC"),
QStringLiteral("32U MD")
});
checkArea(QStringLiteral("32U MD 0000000000"), 89999, 89999, 21000, 21000, { QStringLiteral("32U MD"),
QStringLiteral("32U ME"),
QStringLiteral("32U ND"),
QStringLiteral("32U NE")
});
// UAE Areas
checkArea(QStringLiteral("39Q XG 0000000000"), 40000, 35000, 60000, 70000, { QStringLiteral("39Q XG"),
QStringLiteral("39R XG")
});
checkArea(QStringLiteral("40R CP 0000000000"), 94000, 9000, 7000, 16000, { QStringLiteral("40R CP") });
checkArea(QStringLiteral("40R DN 0000000000"), 500, 95500, 12000, 7000, { QStringLiteral("40R CN"),
QStringLiteral("40R DN")
});
checkArea(QStringLiteral("40R DN 0000000000"), 500, 96760, 30000, 30000, { QStringLiteral("40R CN"),
QStringLiteral("40R CP"),
QStringLiteral("40R DN"),
QStringLiteral("40R DP")
});
checkArea(QStringLiteral("40R BM 0000000000"), 59000, 59500, 46000, 40000, { QStringLiteral("40Q BM"),
QStringLiteral("40R BM")
});
// Luxemburg
//
checkArea(QStringLiteral("32U LA 0000000000"), 10000, 10000, 5000, 5000, { QStringLiteral("32U LA"),
});
// and here in different utm zones: 31, 32
checkArea(QStringLiteral("32U LA 0000000000"), 100, 100, 60000, 60000, { QStringLiteral("31U GQ"),
QStringLiteral("31U GR"),
QStringLiteral("32U KA"),
QStringLiteral("32U KV"),
QStringLiteral("32U LA"),
QStringLiteral("32U LV")
});
// is extent > 100000 realistic?
checkArea(QStringLiteral("32U LA 0000000000"), 100, 100,150000,150000, { QStringLiteral("31U FQ"),
QStringLiteral("31U FR"),
QStringLiteral("31U GQ"),
QStringLiteral("31U GR"),
QStringLiteral("32U KA"),
QStringLiteral("32U KV"),
QStringLiteral("32U LA"),
QStringLiteral("32U LV")
});
checkArea(QStringLiteral("32U LA 0000000000"), 100, 100,200000,200000, { QStringLiteral("31U FP"),
QStringLiteral("31U FQ"),
QStringLiteral("31U FR"),
QStringLiteral("31U GP"),
QStringLiteral("31U GQ"),
QStringLiteral("31U GR"),
QStringLiteral("31U GS"),
QStringLiteral("32U KA"),
QStringLiteral("32U KB"),
QStringLiteral("32U KV"),
QStringLiteral("32U LA"),
QStringLiteral("32U LB"),
QStringLiteral("32U LV"),
QStringLiteral("32U MA"),
QStringLiteral("32U MB"),
QStringLiteral("32U MV")
});
}
QVector<LtsCoordTrans::UtmCoordinate> checkAreaGetMgrsSquares(const QString &base,
int xCenter, int yCenter, int width, int height)
{
// qDebug() << "checkAreaGetMgrsSquares : base: " << base;
QRectF areaRect = QRectF(xCenter - width / 2, yCenter - height / 2, width, height);
LtsCoordTrans::UtmCoordinate utmBase(LtsCoordTrans::UtmCoordinate::fromGeographic(LtsCoordTrans::geographicFromMgrs(base)));
const QPointF topLeft(utmBase.geographic(areaRect.left(), areaRect.top()));
const QPointF bottomRight(utmBase.geographic(areaRect.right(), areaRect.bottom()));
// qDebug() << "TopLeft: " << topLeft * LtsCoordTrans::rad2deg();
// qDebug() << "BottomRight: " << bottomRight * LtsCoordTrans::rad2deg();
const auto squares = LtsCoordTrans::getMgrsSquares(QRectF(topLeft, bottomRight), false, false);
QStringList src;
src.reserve(squares.size());
for (const auto &utm : squares)
src += utm.mgrs(0);
src.sort();
// qDebug() << "src: '" << src;
return squares;
}
void coordtrans_test::testMgrsSquaresWithSurrounding()
{
// // Result: ONE MGRS-Square
// checkArea(QStringLiteral("32U NC 0000000000"), 50000, 50000, 5000, 5000, { QStringLiteral("32U NC")
// });
// // Result: NINE MGRS-Square
// checkArea(QStringLiteral("32U NC 0000000000"), 50000, 50000, 120000, 120000, { QStringLiteral("32U MB"),
// QStringLiteral("32U MC"),
// QStringLiteral("32U MD"),
// QStringLiteral("32U NB"),
// QStringLiteral("32U NC"),
// QStringLiteral("32U ND"),
// QStringLiteral("32U PB"),
// QStringLiteral("32U PC"),
// QStringLiteral("32U PD")
// });
int initialSize = 5000;
// Result: ONE MGRS-Square
QVector<LtsCoordTrans::UtmCoordinate> squares1 = checkAreaGetMgrsSquares(QStringLiteral("32U NC 0000000000"), 50000, 50000, initialSize, initialSize);
QVERIFY2(squares1.size() == 1, "checkAreaGetMgrsSquares failed");
// Result: NINE MGRS-Square
int biggerSize = initialSize + (1*110000);
QVector<LtsCoordTrans::UtmCoordinate> squares9 = checkAreaGetMgrsSquares(QStringLiteral("32U NC 0000000000"), 50000, 50000, biggerSize, biggerSize);
// qDebug() << "squares9 '" << squares9.size();
QVERIFY2(squares9.size() == 9, "checkAreaGetMgrsSquares biggerSize failed");
int muchBiggerSize = initialSize + (3*110000);
QVector<LtsCoordTrans::UtmCoordinate> square33 = checkAreaGetMgrsSquares(QStringLiteral("32U NC 0000000000"), 50000, 50000, muchBiggerSize, muchBiggerSize);
// qDebug() << "square33 '" << square33.size();
QVERIFY2(square33.size() == 33, "checkAreaGetMgrsSquares muchBiggerSize failed");
QStringList string1;
string1.reserve(squares1.size());
for (const LtsCoordTrans::UtmCoordinate &item : qAsConst(squares1))
string1.append(item.mgrs(0));
QStringList string9;
string9.reserve(squares9.size());
for (const LtsCoordTrans::UtmCoordinate &item : qAsConst(squares9))
string9.append(item.mgrs(0));
QStringList string33;
string33.reserve(square33.size());
for (const LtsCoordTrans::UtmCoordinate &item : qAsConst(square33))
string33.append(item.mgrs(0));
{
QStringList surroundingStrings;
surroundingStrings.reserve(squares9.size());
for (const QString &str : qAsConst(string9))
if (!string1.contains(str))
surroundingStrings.append(str);
QVERIFY2(surroundingStrings.size() == 8, "checkAreaGetMgrsSquares squaresDiff failed");
}
{
QStringList surroundingStrings;
surroundingStrings.reserve(string33.size());
for (const QString &str : qAsConst(string33))
if (!string1.contains(str))
surroundingStrings.append(str);
QVERIFY2(surroundingStrings.size() == 32, "checkAreaGetMgrsSquares squaresDiff failed");
}
}
void coordtrans_test::checkArea(const QString &base,
int xCenter, int yCenter, int width, int height,
const QStringList &coords)
{
qDebug() << "checkArea : base: " << base;
QRectF areaRect = QRectF(xCenter - width / 2, yCenter - height / 2, width, height);
LtsCoordTrans::UtmCoordinate utmBase(LtsCoordTrans::UtmCoordinate::fromGeographic(LtsCoordTrans::geographicFromMgrs(base)));
const QPointF topLeft(utmBase.geographic(areaRect.left(), areaRect.top()));
const QPointF bottomRight(utmBase.geographic(areaRect.right(), areaRect.bottom()));
qDebug() << "TopLeft: " << topLeft * LtsCoordTrans::rad2deg();
qDebug() << "BottomRight: " << bottomRight * LtsCoordTrans::rad2deg();
const auto squares = LtsCoordTrans::getMgrsSquares(QRectF(topLeft, bottomRight), false, false);
QStringList src;
src.reserve(squares.size());
for (const auto &utm : squares)
src += utm.mgrs(0);
src.sort();
qDebug() << "src: '" << src << "' expected: '" << coords << "'";
QCOMPARE(src, coords);
}
+38
View File
@@ -0,0 +1,38 @@
/**
* Unit test for ltscoordtrans
* $Id$
* $HeadURL$
* $Author$
*/
#pragma once
#include <QObject>
#include <QStringList>
class coordtrans_test : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void cleanupTestCase();
void testFromGeographic();
void testMgrsFromGeographic();
void testRelativeMetricalOffet_data();
void testRelativeMetricalOffet();
void testAddMarginToArea();
void testIncreaseRect();
void testIncreaseRect2();
void testGeographicFromMgrs();
void testMgrsSquares();
void testMgrsSquaresWithSurrounding();
private:
void checkArea(const QString &base,
int xCenter, int yCenter, int width, int height,
const QStringList &coords);
};
+203
View File
@@ -0,0 +1,203 @@
/**
* @RDE_HEADER@
* An Input Widget for coordinates
* $Id$
* $HeadURL$
* $Author$
*
* Created by Christian Ehrlicher, SLSE, 2016-09-08
*/
#include "lts_coord_widget.h"
#include "lts_coord_widget_p.h"
#include <LtsCoordTrans/lts_coord_trans.h>
#include <QToolButton>
namespace LtsCoordWidget
{
LtsCoordWidget::LtsCoordWidget(DisplayType dt, QWidget *parent)
: QWidget(parent)
, m_displayType(dt)
{
setFocusPolicy(Qt::StrongFocus);
rebuildUi();
}
void LtsCoordWidget::setCoordinate(const QPointF &geoCoord, bool bUseAsDefaultValue)
{
if (m_coordinate != geoCoord)
emit coordinateChanged(geoCoord);
if (bUseAsDefaultValue)
m_defaultCoordinate = geoCoord;
if (m_inputWidgetHandler)
m_inputWidgetHandler->setCoordinate(geoCoord);
}
void LtsCoordWidget::clear()
{
if (m_inputWidgetHandler)
m_inputWidgetHandler->clear();
}
void LtsCoordWidget::setCoordinate(double lon, double lat, bool bUseAsDefaultValue)
{
setCoordinate(QPointF(lon, lat), bUseAsDefaultValue);
}
QPointF LtsCoordWidget::coordinate() const
{
if (!m_inputWidgetHandler)
return QPointF();
return m_inputWidgetHandler->coordinate();
}
LtsCoordWidget::DisplayType LtsCoordWidget::displayType() const
{
return m_displayType;
}
void LtsCoordWidget::setDisplayType(LtsCoordWidget::DisplayType dt)
{
if (m_displayType == dt)
return;
m_displayType = dt;
rebuildUi();
}
bool LtsCoordWidget::withResetButton() const
{
return m_bWithResetButton;
}
void LtsCoordWidget::setWithResetButton(bool bWith)
{
if (m_bWithResetButton == bWith)
return;
m_bWithResetButton = bWith;
if (m_inputWidgetHandler)
m_inputWidgetHandler->resetButton()->setVisible(m_bWithResetButton);
}
bool LtsCoordWidget::showToolTip() const
{
return m_bShowToolTip;
}
void LtsCoordWidget::setShowToolTip(bool bShow)
{
if (m_bShowToolTip == bShow)
return;
m_bShowToolTip = bShow;
updateToolTip();
}
bool LtsCoordWidget::mgrsSquareEditable() const
{
return m_bMgrsSquareEditable;
}
void LtsCoordWidget::setMgrsSquareEditable(bool bEditable)
{
if (m_bMgrsSquareEditable == bEditable)
return;
m_bMgrsSquareEditable = bEditable;
if (m_inputWidgetHandler)
m_inputWidgetHandler->setSquaresEditable(bEditable);
}
void LtsCoordWidget::setWithSurroundingMgrsSquares(bool bEnable)
{
if (m_inputWidgetHandler)
m_inputWidgetHandler->setWithSurroundingSquare(bEnable);
}
void LtsCoordWidget::setMgrsSquares(const QVector<LtsCoordTrans::UtmCoordinate> &geoCoords)
{
if (m_inputWidgetHandler)
m_inputWidgetHandler->setMgrsSquares(geoCoords);
}
bool LtsCoordWidget::event(QEvent *event)
{
if (event->type() == QEvent::ToolTip)
updateToolTip();
return QWidget::event(event);
}
void LtsCoordWidget::rebuildUi()
{
// MGRS -> ComboBox with allowed MGRS-Squares + x / y IntEdit with 5 digits
// MGRS7 -> ComboBox with allowed MGRS-Squares + x / y IntEdit with 7 digits
// UTM -> ComboBox with all UTM zones + IntEdit with 5 digits --> TODO
// WGS84 -> lon/lat double inputs
if (m_inputWidgetHandler)
{
m_inputWidgetHandler->cleanup();
delete m_inputWidgetHandler;
delete layout();
}
switch (m_displayType)
{
case DisplayType::MGRS5:
m_inputWidgetHandler = new MgrsEdit(5, this);
break;
case DisplayType::MGRS7:
m_inputWidgetHandler = new MgrsEdit(7, this);
break;
case DisplayType::UTM:
//m_inputWidgetHandler = new MgrsEdit(5, this);
qt_assert("DisplayType::UTM not yet implemented", __FILE__, __LINE__);
break;
case DisplayType::WGS84:
m_inputWidgetHandler = new Wgs84Edit(this);
break;
}
if (!m_inputWidgetHandler)
return;
m_inputWidgetHandler->setSquaresEditable(m_bMgrsSquareEditable);
m_inputWidgetHandler->resetButton()->setVisible(m_bWithResetButton);
connect(m_inputWidgetHandler->resetButton(), &QToolButton::clicked,
this, &LtsCoordWidget::onResetToDefaultCoordinate);
connect(m_inputWidgetHandler, &LtsCoordInputWidgetHandler::coordinateChanged,
this, &LtsCoordWidget::coordinateChanged);
connect(m_inputWidgetHandler, &LtsCoordInputWidgetHandler::invalidCoordinate,
this, &LtsCoordWidget::invalidCoordinate);
if (!m_defaultCoordinate.isNull())
m_inputWidgetHandler->setCoordinate(m_defaultCoordinate);
}
void LtsCoordWidget::updateToolTip()
{
QString tt;
if (m_bShowToolTip)
{
const QPointF p(coordinate());
if (!p.isNull())
{
const QString mgrs = LtsCoordTrans::mgrsFromGeographic(p.x(), p.y());
const QPointF p2(p * LtsCoordTrans::rad2deg());
tt = tr("MGRS: %1\nlat/lon: %2/%3").arg(mgrs).arg(p2.y(), 0, 'f', 10).arg(p2.x(), 0, 'f', 10);
}
}
setToolTip(tt);
}
void LtsCoordWidget::onResetToDefaultCoordinate()
{
if (!m_inputWidgetHandler)
return;
if (m_defaultCoordinate.isNull())
m_inputWidgetHandler->clear();
m_inputWidgetHandler->setCoordinate(m_defaultCoordinate);
}
} // namespace LtsCoordWidget
+90
View File
@@ -0,0 +1,90 @@
/**
* @RDE_HEADER@
* An Input Widget for coordinates
* $Id$
* $HeadURL$
* $Author$
*
* Created by Christian Ehrlicher, SLSE, 2016-09-08
*/
#pragma once
#include <LtsCoordTrans/ltscoordwidget_export.h>
#include <LtsCoordTrans/lts_coord_trans.h>
#include <QWidget>
class QToolButton;
namespace LtsCoordWidget
{
class LtsCoordInputWidgetHandler;
class LTSCOORDWIDGET_EXPORT LtsCoordWidget : public QWidget
{
Q_OBJECT
public:
enum class DisplayType
{
MGRS5,// display coordinate as MGRS with 5 digits (= m)
MGRS7,// display coordinate as MGRS with 7 digits (= mm)
UTM, // display coordinate as UTM
WGS84, // display coordinate as WGS84 (degree)
MGRS = DisplayType::MGRS5 // display coordinate as MGRS (default precision = MGRS5)
};
Q_ENUM(DisplayType)
Q_PROPERTY(DisplayType displayType READ displayType WRITE setDisplayType)
Q_PROPERTY(bool withResetButton READ withResetButton WRITE setWithResetButton)
Q_PROPERTY(bool showToolTip READ showToolTip WRITE setShowToolTip)
Q_PROPERTY(bool mgrsSquareEditable READ mgrsSquareEditable WRITE setMgrsSquareEditable)
/**
* @brief Create a new LtsCoordWidget with default display type (= MGRS)
*/
explicit LtsCoordWidget(QWidget *parent = nullptr)
: LtsCoordWidget(LtsCoordWidget::DisplayType::MGRS, parent)
{}
/**
* @brief Create a new LtsCoordWidget with given display type
*/
explicit LtsCoordWidget(DisplayType dt, QWidget *parent = nullptr);
QPointF coordinate() const;
DisplayType displayType() const;
bool withResetButton() const;
bool showToolTip() const;
bool mgrsSquareEditable() const;
bool event(QEvent *event) override;
public Q_SLOTS:
/**
* @brief Set the current visible coordinate
* @param geoCoord the coordinate to display (in wgs84, radian)
* @param bUseAsDefaultValue if true, the reset button will be visible and reset to the given value if pressed
*/
void setCoordinate(const QPointF &geoCoord, bool bUseAsDefaultValue = true);
void setCoordinate(double lon, double lat, bool bUseAsDefaultValue = true);
void setDisplayType(LtsCoordWidget::DisplayType dt);
void setWithResetButton(bool bWith);
void setShowToolTip(bool bShow);
void setMgrsSquareEditable(bool bEditable);
void setWithSurroundingMgrsSquares(bool bEnable);
void setMgrsSquares(const QVector<LtsCoordTrans::UtmCoordinate> &geoCoords);
void clear();
Q_SIGNALS:
void coordinateChanged(const QPointF &newGeoCoord);
void invalidCoordinate();
private:
void rebuildUi();
void updateToolTip();
private Q_SLOTS:
void onResetToDefaultCoordinate();
private:
bool m_bWithResetButton = false;
bool m_bShowToolTip = true;
bool m_bMgrsSquareEditable = false;
LtsCoordWidget::DisplayType m_displayType = LtsCoordWidget::DisplayType::MGRS;
LtsCoordInputWidgetHandler *m_inputWidgetHandler = nullptr;
QPointF m_coordinate;
QPointF m_defaultCoordinate;
};
}
+55
View File
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Mgrs</class>
<widget class="QWidget" name="Mgrs">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>322</width>
<height>32</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QComboBox" name="cbxZone"/>
</item>
<item>
<widget class="QLineEdit" name="leEast">
<property name="toolTip">
<string>Pos X</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="leNorth">
<property name="toolTip">
<string>Pos Y</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="tbReset">
<property name="icon">
<iconset>
<normaloff>:/style/arrow_back.svg</normaloff>:/style/arrow_back.svg</iconset>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+332
View File
@@ -0,0 +1,332 @@
/**
* @RDE_HEADER@
* An Input Widget for coordinates
* $Id$
* $HeadURL$
* $Author$
*
* Created by Christian Ehrlicher, SLSE, 2016-09-08
*/
#include "lts_coord_widget_p.h"
namespace LtsCoordWidget
{
//
// LtsCoordInputWidgetHandler
//
void LtsCoordInputWidgetHandler::onCoordinateDataChanged()
{
const QPointF coord(coordinate());
if (coord.isNull())
{
emit invalidCoordinate();
return;
}
emit coordinateChanged(coord);
}
//
// MgrsEdit
//
MgrsEdit::MgrsEdit(int precision, QWidget *widget)
: LtsCoordInputWidgetHandler(widget)
, m_precision(precision)
, m_ui(new Ui::Mgrs)
{
m_ui->setupUi(widget);
if (m_precision < 0)
m_precision = 0;
if (m_precision > 11)
m_precision = 11;
const int upperLimit = pow(10, m_precision) - 1;
m_ui->leEast->setValidator(new QIntValidator(0, upperLimit, m_ui->leEast));
m_ui->leNorth->setValidator(new QIntValidator(0, upperLimit, m_ui->leNorth));
setDefaultMgrsSquares();
connect(m_ui->cbxZone, QOverload<const QString &>::of(&QComboBox::currentIndexChanged),
this, &MgrsEdit::onCoordinateDataChanged);
connect(m_ui->leEast, &QLineEdit::editingFinished,
this, &MgrsEdit::onCoordinateDataChanged);
connect(m_ui->leNorth, &QLineEdit::editingFinished,
this, &MgrsEdit::onCoordinateDataChanged);
}
MgrsEdit::~MgrsEdit()
{
delete m_ui;
}
bool MgrsEdit::isValid()
{
// check number of digits
const QString &east = m_ui->leEast->text();
if (east.size() != m_precision)
return false;
const QString &north = m_ui->leNorth->text();
if (north.size() != m_precision)
return false;
if (m_ui->cbxZone->currentIndex() < 0)
return false;
if (m_ui->cbxZone->currentText().isEmpty())
return false;
return true;
}
void MgrsEdit::setCoordinate(const QPointF &geoCoord)
{
const QString mgrsString = LtsCoordTrans::mgrsFromGeographic(geoCoord.x(), geoCoord.y(), m_precision, false);
if (mgrsString.isEmpty())
{
m_ui->leEast->clear();
m_ui->leNorth->clear();
return;
}
// prevent signal coordinateChanged()
m_ui->leEast->setText(QString());
m_ui->leNorth->setText(QString());
// split up
const QString mgrsSquare = mgrsString.left(3) + QLatin1Char(' ') + mgrsString.mid(3, 2);
QString east = mgrsString.mid(5, m_precision);
QString north = mgrsString.mid(5 + m_precision, m_precision);
int idx = m_ui->cbxZone->findText(mgrsSquare);
if (idx < 0)
{
addMgrsSquare(mgrsSquare);
idx = m_ui->cbxZone->findText(mgrsSquare);
}
m_ui->cbxZone->setCurrentIndex(idx);
m_ui->leEast->setText(east);
m_ui->leNorth->setText(north);
}
static inline QString getMgrsCoord(QLineEdit *le, int prec)
{
QString ret = le->text();
if (ret.isEmpty())
return ret;
while (ret.size() < prec)
ret += QLatin1Char('0');
le->setText(ret);
return ret;
}
QPointF MgrsEdit::coordinate() const
{
const QString east = getMgrsCoord(m_ui->leEast, m_precision);
const QString north = getMgrsCoord(m_ui->leNorth, m_precision);
if (east.isEmpty() || north.isEmpty() || m_ui->cbxZone->currentIndex() < 0)
return QPointF();
const QString mgrsString(m_ui->cbxZone->currentText() + east + north);
return LtsCoordTrans::geographicFromMgrs(mgrsString);
}
QToolButton *MgrsEdit::resetButton()
{
return m_ui->tbReset;
}
void MgrsEdit::cleanup()
{
// we need to cleanup here because the ownership was transfered to parent widget
// within m_ui->setupUi(parent)
delete m_ui->horizontalLayout;
delete m_ui->cbxZone;
delete m_ui->leEast;
delete m_ui->leNorth;
delete m_ui->tbReset;
}
void MgrsEdit::clear()
{
m_ui->cbxZone->clear();
m_ui->leEast->clear();
m_ui->leNorth->clear();
setDefaultMgrsSquares();
}
void MgrsEdit::setSquaresEditable(bool bEditable)
{
m_ui->cbxZone->setEditable(bEditable);
}
void MgrsEdit::setWithSurroundingSquare(bool bEnabled)
{
m_withMgrsSquareOffset = bEnabled;
}
static QStringList defaultMgrsSquares()
{
const QVector<LtsCoordTrans::UtmCoordinate> squares(LtsCoordTrans::defaultMgrsSquares());
QStringList sl;
sl.reserve(squares.size());
for (const LtsCoordTrans::UtmCoordinate &c : squares)
sl += c.mgrs(0);
std::sort(sl.begin(), sl.end());
return sl;
}
static QString preferedMgrsSquare()
{
const LtsCoordTrans::UtmCoordinate c(LtsCoordTrans::preferedMgrsSquare());
return c.mgrs(0); // should match one of the defaultMgrsSquares :-)
}
void MgrsEdit::setDefaultMgrsSquares()
{
m_ui->cbxZone->addItems(defaultMgrsSquares());
m_ui->cbxZone->setCurrentIndex(m_ui->cbxZone->findText(preferedMgrsSquare()));
}
void MgrsEdit::addMgrsSquare(const QString &newMgrsSquare)
{
QStringList sl = defaultMgrsSquares();
sl += newMgrsSquare;
if (m_withMgrsSquareOffset)
{
QStringList squares = utmCoordsToList(LtsCoordTrans::defaultMgrsSquareWithOffsets());
// if square not already in list add it.
for (const auto &sq : qAsConst(squares))
if (!sl.contains(sq))
sl.append(sq);
}
std::sort(sl.begin(), sl.end());
m_ui->cbxZone->clear();
m_ui->cbxZone->addItems(sl);
}
QStringList MgrsEdit::utmCoordsToList(const QVector<LtsCoordTrans::UtmCoordinate> &coords)
{
QStringList list;
list.reserve(coords.count());
for (const auto &utm : coords)
list.append(utm.mgrs(0));
return list;
}
void MgrsEdit::setMgrsSquares(const QVector<LtsCoordTrans::UtmCoordinate> &geoCoords)
{
QStringList sl = utmCoordsToList(geoCoords);
if (sl.isEmpty())
return;
std::sort(sl.begin(), sl.end());
m_ui->cbxZone->clear();
m_ui->cbxZone->addItems(sl);
}
//
// Wgs84Edit
//
Wgs84Edit::Wgs84Edit(QWidget *widget)
: LtsCoordInputWidgetHandler(widget)
, m_ui(new Ui::Wgs84)
{
m_ui->setupUi(widget);
DoubleValidator *v = new DoubleValidator(-180, 180, 9, m_ui->leLon);
v->setNotation(QDoubleValidator::StandardNotation);
m_ui->leLon->setValidator(v);
v = new DoubleValidator(-180, 180, 9, m_ui->leLat);
v->setNotation(QDoubleValidator::StandardNotation);
m_ui->leLat->setValidator(v);
connect(m_ui->leLon, &QLineEdit::editingFinished,
this, &Wgs84Edit::onCoordinateDataChanged);
connect(m_ui->leLat, &QLineEdit::editingFinished,
this, &Wgs84Edit::onCoordinateDataChanged);
}
Wgs84Edit::~Wgs84Edit()
{
delete m_ui;
}
bool Wgs84Edit::isValid()
{
// not tested yet:
return (!m_ui->leLon->text().isEmpty()) && (!m_ui->leLat->text().isEmpty());
}
void Wgs84Edit::setCoordinate(const QPointF &geoCoord)
{
const QPointF coord(geoCoord * LtsCoordTrans::rad2deg());
const QLocale l;
m_ui->leLon->setText(l.toString(coord.x(), 'f', 9));
m_ui->leLat->setText(l.toString(coord.y(), 'f', 9));
}
QPointF Wgs84Edit::coordinate() const
{
QPointF ret;
const QLocale l; // current default locale
// remove default group separator because QDoubleValidator accepts them everywhere...
// maybe we need to derive from QDoubleValidator to prevent this behavior
const QString lonStr = m_ui->leLon->text().remove(l.groupSeparator());
const QString latStr = m_ui->leLat->text().remove(l.groupSeparator());
if (lonStr.isEmpty() || latStr.isEmpty())
return ret;
ret.rx() = l.toDouble(lonStr);
ret.ry() = l.toDouble(latStr);
return ret * LtsCoordTrans::deg2rad();
}
QToolButton *Wgs84Edit::resetButton()
{
return m_ui->tbReset;
}
void Wgs84Edit::cleanup()
{
// we need to cleanup here because the ownership was transfered to parent widget
// within m_ui->setupUi(parent)
delete m_ui->horizontalLayout;
delete m_ui->leLon;
delete m_ui->leLat;
delete m_ui->tbReset;
delete m_ui->lblLon;
delete m_ui->lblLat;
}
void Wgs84Edit::clear()
{
m_ui->leLon->clear();
m_ui->leLat->clear();
}
//
// DoubleValidator
//
DoubleValidator::DoubleValidator(double bottom, double top, int decimals, QObject *parent)
: QDoubleValidator(bottom, top, decimals, parent)
{}
QValidator::State DoubleValidator::validate(QString &input, int &pos) const
{
const QValidator::State ret = QDoubleValidator::validate(input, pos);
if (ret != QValidator::Acceptable)
return ret;
QLocale l;
const QChar gs(l.groupSeparator());
if (gs.isNull())
return ret;
if (input.contains(gs))
return QValidator::Invalid;
return ret;
}
} // namespace LtsCoordWidget
+100
View File
@@ -0,0 +1,100 @@
/**
* @RDE_HEADER@
* An Input Widget for coordinates
* $Id$
* $HeadURL$
* $Author$
*
* Created by Christian Ehrlicher, SLSE, 2016-09-08
*/
#pragma once
#include <QWidget>
#include <QComboBox>
#include <QVBoxLayout>
#include <LtsCoordTrans/lts_coord_trans.h>
#include "ui_lts_coord_widget_mgrs.h"
#include "ui_lts_coord_widget_wgs84.h"
namespace LtsCoordWidget
{
class LtsCoordInputWidgetHandler : public QObject
{
Q_OBJECT
public:
explicit LtsCoordInputWidgetHandler(QObject *parent)
: QObject(parent)
{}
virtual bool isValid() = 0; // 2024, N.Thoden: has to be tested
virtual void setCoordinate(const QPointF &geoCoord) = 0;
virtual QPointF coordinate() const = 0;
virtual QToolButton *resetButton() = 0;
virtual void cleanup() = 0;
virtual void clear() = 0;
virtual void setSquaresEditable(bool bEditable) = 0;
virtual void setWithSurroundingSquare(bool bEnabled) = 0;
virtual void setMgrsSquares(const QVector<LtsCoordTrans::UtmCoordinate> &geoCoords) = 0;
protected Q_SLOTS:
void onCoordinateDataChanged();
Q_SIGNALS:
void coordinateChanged(const QPointF &newGeoCoord);
void invalidCoordinate();
};
class MgrsEdit final : public LtsCoordInputWidgetHandler
{
Q_OBJECT
public:
explicit MgrsEdit(int precision, QWidget *widget);
~MgrsEdit();
bool isValid() override;
void setCoordinate(const QPointF &geoCoord) override;
QPointF coordinate() const override;
QToolButton *resetButton() override;
void cleanup() override;
void clear() override;
void setSquaresEditable(bool bEditable) override;
void setWithSurroundingSquare(bool bEnabled) override;
void setMgrsSquares(const QVector<LtsCoordTrans::UtmCoordinate> &geoCoords) override;
private:
void setDefaultMgrsSquares();
void addMgrsSquare(const QString &newMgrsSquare);
QStringList utmCoordsToList(const QVector<LtsCoordTrans::UtmCoordinate> &coords);
private:
int m_precision;
bool m_withMgrsSquareOffset=false;
Ui::Mgrs *m_ui;
};
class Wgs84Edit final : public LtsCoordInputWidgetHandler
{
Q_OBJECT
public:
explicit Wgs84Edit(QWidget *widget);
~Wgs84Edit();
bool isValid() override;
void setCoordinate(const QPointF &geoCoord) override;
QPointF coordinate() const override;
QToolButton *resetButton() override;
void cleanup() override;
void clear() override;
void setSquaresEditable(bool bEditable) override { Q_UNUSED(bEditable); }
void setWithSurroundingSquare(bool bEnabled) override { Q_UNUSED(bEnabled); }
void setMgrsSquares(const QVector<LtsCoordTrans::UtmCoordinate> &geoCoords) override { Q_UNUSED(geoCoords); }
private:
Ui::Wgs84 *m_ui;
};
class DoubleValidator final : public QDoubleValidator
{
Q_OBJECT
public:
DoubleValidator(double bottom, double top, int decimals, QObject *parent = nullptr);
QValidator::State validate(QString &input, int &pos) const override;
};
}
+122
View File
@@ -0,0 +1,122 @@
/**
* @RDE_HEADER@
* Small test app for ltscoordwidget
* $Id$
* $HeadURL$
* $Author$
*
* Created by Christian Ehrlicher, SLSE, 2016-09-08
*/
#include <QApplication>
#include <QDialog>
#include <QPushButton>
#include <QVBoxLayout>
#include <QLabel>
#include <LtsCoordTrans/lts_coord_trans.h>
#include "lts_coord_widget.h"
static void updateLabel(const QPointF &coord, QLabel *lbl, int prec)
{
QString mgrs = LtsCoordTrans::mgrsFromGeographic(coord.x(), coord.y(), prec);
QString str = QStringLiteral("mgrs: %1, lon: %2, lat: %3")
.arg(mgrs)
.arg(coord.x() * LtsCoordTrans::rad2deg(), 0, 'f', 10)
.arg(coord.y() * LtsCoordTrans::rad2deg(), 0, 'f', 10);
lbl->setText(str);
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
LtsCoordTrans::UtmCoordinate base(LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32UMD00")));
QPointF topLeft(base.geographic(+50000, -50000));
QPointF bottomRight(base.geographic(-50000, 50000));
LtsCoordTrans::getMgrsSquares(QRectF(topLeft, bottomRight), true, true);
QDialog *dialog(new QDialog);
QVBoxLayout *lay(new QVBoxLayout(dialog));
// const QPointF defCoord(QPointF(8.0, 53.0) * LtsCoordTrans::deg2rad());
const QPointF defCoord(QPointF(53.0, 8.0) * LtsCoordTrans::deg2rad());
// MGRS5
LtsCoordWidget::LtsCoordWidget *wMgrs5(new LtsCoordWidget::LtsCoordWidget(LtsCoordWidget::LtsCoordWidget::DisplayType::MGRS, dialog));
wMgrs5->setWithResetButton(true);
wMgrs5->setCoordinate(defCoord, true);
lay->addWidget(wMgrs5);
QLabel *lbl5(new QLabel(dialog));
lay->addWidget(lbl5);
QObject::connect(wMgrs5, &LtsCoordWidget::LtsCoordWidget::coordinateChanged,
lbl5, [lbl5](const QPointF & coord)
{
updateLabel(coord, lbl5, 5);
});
// MGRS7
LtsCoordWidget::LtsCoordWidget *wMgrs7(new LtsCoordWidget::LtsCoordWidget(LtsCoordWidget::LtsCoordWidget::DisplayType::MGRS7, dialog));
wMgrs7->setWithResetButton(true);
wMgrs7->setCoordinate(defCoord, true);
lay->addWidget(wMgrs7);
QLabel *lbl7(new QLabel(dialog));
lay->addWidget(lbl7);
QObject::connect(wMgrs7, &LtsCoordWidget::LtsCoordWidget::coordinateChanged,
wMgrs7, [lbl7](const QPointF & coord)
{
updateLabel(coord, lbl7, 7);
});
//WGS 84 degree
LtsCoordWidget::LtsCoordWidget *w84(new LtsCoordWidget::LtsCoordWidget(LtsCoordWidget::LtsCoordWidget::DisplayType::WGS84, dialog));
w84->setWithResetButton(true);
w84->setCoordinate(defCoord, true);
lay->addWidget(w84);
QLabel *lbl84(new QLabel(dialog));
lay->addWidget(lbl84);
QObject::connect(w84, &LtsCoordWidget::LtsCoordWidget::coordinateChanged,
w84, [lbl84](const QPointF & coord)
{
updateLabel(coord, lbl84, 11);
});
// Test live-change
QHBoxLayout *hLay = new QHBoxLayout;
lay->addLayout(hLay);
LtsCoordWidget::LtsCoordWidget *wChange(new LtsCoordWidget::LtsCoordWidget(LtsCoordWidget::LtsCoordWidget::DisplayType::WGS84, dialog));
wChange->setWithResetButton(true);
wChange->setCoordinate(defCoord, true);
hLay->addWidget(wChange);
QPushButton *pbReset(new QPushButton(QStringLiteral("With Reset"), dialog));
hLay->addWidget(pbReset);
QObject::connect(pbReset, &QPushButton::clicked,
wChange, [wChange]() { wChange->setWithResetButton(!wChange->withResetButton());});
QPushButton *pbNext(new QPushButton(QStringLiteral("Next Type"), dialog));
hLay->addWidget(pbNext);
QObject::connect(pbNext, &QPushButton::clicked,
wChange, [wChange]()
{
LtsCoordWidget::LtsCoordWidget::DisplayType dt = wChange->displayType();
switch (dt)
{
case LtsCoordWidget::LtsCoordWidget::DisplayType::MGRS5:
dt = LtsCoordWidget::LtsCoordWidget::DisplayType::MGRS7; break;
case LtsCoordWidget::LtsCoordWidget::DisplayType::MGRS7:
dt = LtsCoordWidget::LtsCoordWidget::DisplayType::WGS84; break;
case LtsCoordWidget::LtsCoordWidget::DisplayType::WGS84:
dt = LtsCoordWidget::LtsCoordWidget::DisplayType::MGRS5; break;
default:
break;
}
wChange->setDisplayType(dt);
});
QPushButton *pb(new QPushButton(QStringLiteral("Quit"), dialog));
lay->addWidget(pb);
QObject::connect(pb, &QPushButton::clicked, qApp, QCoreApplication::quit);
dialog->show();
int ret = app.exec();
delete dialog;
return ret;
}
+58
View File
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Wgs84</class>
<widget class="QWidget" name="Wgs84">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>293</width>
<height>32</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="lblLat">
<property name="text">
<string>Lat:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="leLat"/>
</item>
<item>
<widget class="QLabel" name="lblLon">
<property name="text">
<string>Lon:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="leLon"/>
</item>
<item>
<widget class="QToolButton" name="tbReset">
<property name="icon">
<iconset>
<normaloff>icon:arrow_back.svg</normaloff>icon:arrow_back.svg</iconset>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+49
View File
@@ -0,0 +1,49 @@
/**
* Convert a lon/lat coordinate to mgrs
* $Id$
* $HeadURL$
* $Author$
*/
#include <GeographicLib/UTMUPS.hpp>
#include <GeographicLib/MGRS.hpp>
#include <string>
int main(int argc, char **argv)
{
if (argc != 2)
{
fprintf(stderr, "Usage: %s <mgrs>\n", argv[0]);
return -1;
}
std::string mgrs = argv[1];
mgrs.erase(remove_if(mgrs.begin(), mgrs.end(), isspace), mgrs.end());
int prec;
double lonDeg, latDeg;
try
{
int zone;
bool northp;
double x, y;
GeographicLib::MGRS::Reverse(mgrs, zone, northp, x, y, prec, true);
GeographicLib::UTMUPS::Reverse(zone, northp, x, y, latDeg, lonDeg);
GeographicLib::UTMUPS::Forward(latDeg, lonDeg, zone, northp, x, y);
GeographicLib::MGRS::Forward(zone, northp, x, y, prec, mgrs);
}
catch (...)
{
fprintf(stderr, "Error converting %s to Lon/Lat coords\n", mgrs.c_str());
return -2;
}
std::string part1, part2, part3, part4;
part1 = mgrs.substr(0, 3);
part2 = mgrs.substr(3, 2);
part3 = mgrs.substr(5, prec);
part4 = mgrs.substr(5 + prec, prec);
fprintf(stdout, "%s %s %s %s = %13.10f, %13.10f ( %13.10f, %13.10f )\n",
part1.c_str(), part2.c_str(), part3.c_str(), part4.c_str(), lonDeg, latDeg, lonDeg / 180. * M_PI, latDeg / 180. * M_PI);
return 0;
}