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

+37
View File
@@ -0,0 +1,37 @@
#
# $Id$
# $HeadURL$
# $Author$
#
FIND_PACKAGE(GDAL REQUIRED)
SET(Elevation_SRCS
elevation_map.cpp
elevation_map.h
elevation_map_manager.cpp
elevation_map_manager.h
line_of_sight_calculation.cpp
line_of_sight_calculation.h
)
NGL_ADD_LIBRARY2(AAR SV WS HLA situationMapElevationMap SHARED ${Elevation_SRCS})
NGL_TARGET_INCLUDE_DIRECTORIES(situationMapElevationMap
PRIVATE
${GDAL_INCLUDE_DIR}
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../
${CMAKE_CURRENT_BINARY_DIR}/../
)
NGL_TARGET_LINK_LIBRARIES(situationMapElevationMap
PRIVATE
situationMapCommon
Qt5::Concurrent
Qt5::Gui
NGLLogger
${GDAL_LIBRARIES}
LtsCoordTrans
PUBLIC
Qt5::Core
LtsUtilsCoreHeaderOnly
)
+975
View File
@@ -0,0 +1,975 @@
/**
* @RDE_HEADER@
* adapting GeISiR source to SituationMap
* $Id$
* $HeadURL$
* $Author$
*/
#include "elevation_map.h"
#include <QtConcurrent>
#include <QPolygonF>
#include <MathUtils/math_const.h>
#include <QElapsedTimer>
#include <gdal.h>
#include <iostream>
#include <limits>
#include <memory>
#include <math.h>
namespace SituationMap
{
class BoundingRect
{
double m_top = 0.0;
double m_bottom = 0.0;
double m_left = 0.0;
double m_right = 0.0;
void swapBounds()
{
if (m_top < m_bottom)
std::swap(m_bottom, m_top);
if (m_right < m_left)
std::swap(m_left, m_right);
} // void swapBounds()
public:
BoundingRect() = default;
BoundingRect(double left, double bottom, double right, double top)
: m_top(top), m_bottom(bottom), m_left(left), m_right(right)
{
swapBounds();
} // BoundingRect( double left, double bottom, double right, double top)
BoundingRect(const BoundingRect &) = default;
BoundingRect &operator =(const BoundingRect &) = default;
~BoundingRect() = default;
inline void operator()(double left, double bottom, double right, double top)
{
m_top = top;
m_bottom = bottom;
m_left = left;
m_right = right;
swapBounds();
} // inline void operator() (double left, double bottom, double right, double top)
double top() const { return m_top; }
double bottom() const { return m_bottom; }
double left() const { return m_left; }
double right() const { return m_right; }
bool contains(const QPointF &p) const { return ((p.x() >= m_left) && (p.x() <= m_right) && (p.y() >= m_bottom) && (p.y() <= m_top)); }
QRectF toQRectF() const
{
return QRectF(QPointF(m_left, m_top), QPointF(m_right, m_bottom));
} // toQRectF
}; // BoundingRect
struct ElevationMap::Internal
{
BoundingRect _location; // Lage der Karte in Lat/Lon
struct PointDouble
{
void setX(double x) { _x = x;}
void setY(double y) { _y = y;}
double x() { return _x;}
double y() { return _y;}
double _x{0.0};
double _y{0.0};
};
PointDouble _elexelSize; // LatLon Size of 1 elevation sample, redundat
int _totalColumns = 0;
int _totalRows = 0;
double _toMeterFactor = 1.0;
std::vector<int> _elevationField;
template<class BufferType> bool copyBufferData(GDALRasterBandH hBand, int xBlkSize, int yBlkSize)
{
// NLDEVCTRL-804: Konsistenz d. Z-Werte zwischen 2D/3D (also ElevationMap vs. ESRI::BaseSurface)
// This ElevationMap was using an approach similar to following.
// Now it´s more or less identical and the difference using 'Altmark_dt1.tif'
// are gone. AHTC is also fine :-)
/*
CPLErr GetHistogram( GDALRasterBand *poBand, GUIntBig *panHistogram )
{
memset( panHistogram, 0, sizeof(GUIntBig) * 256 );
CPLAssert( poBand->GetRasterDataType() == GDT_Byte );
int nXBlockSize, nYBlockSize;
poBand->GetBlockSize( &nXBlockSize, &nYBlockSize );
int nXBlocks = (poBand->GetXSize() + nXBlockSize - 1) / nXBlockSize;
int nYBlocks = (poBand->GetYSize() + nYBlockSize - 1) / nYBlockSize;
GByte *pabyData = (GByte *) CPLMalloc(nXBlockSize * nYBlockSize);
for( int iYBlock = 0; iYBlock < nYBlocks; iYBlock++ )
{
for( int iXBlock = 0; iXBlock < nXBlocks; iXBlock++ )
{
int nXValid, nYValid;
poBand->ReadBlock( iXBlock, iYBlock, pabyData );
// Compute the portion of the block that is valid
// for partial edge blocks.
poBand->GetActualBlockSize(iXBlock, iYBlock, &nXValid, &nYValid)
// Collect the histogram counts.
for( int iY = 0; iY < nYValid; iY++ )
{
for( int iX = 0; iX < nXValid; iX++ )
{
panHistogram[pabyData[iX + iY * nXBlockSize]] += 1;
}
}
}
}
}
Source https://gdal.org/api/gdalrasterband_cpp.html
*/
const size_t bufSize = xBlkSize * yBlkSize;
std::unique_ptr<BufferType[]> bufferPtr(new BufferType[bufSize]);
int nXBlocks = (GDALGetRasterBandXSize(hBand) + xBlkSize - 1) / xBlkSize;
int nYBlocks = (GDALGetRasterBandYSize(hBand) + yBlkSize - 1) / yBlkSize;
for (int iYBlock = 0; iYBlock < nYBlocks; ++iYBlock)
{
for (int iXBlock = 0; iXBlock < nXBlocks; ++iXBlock)
{
CPLErr err = GDALReadBlock( hBand, iXBlock, iYBlock, bufferPtr.get() ); // Gdal liest spaltenweise !! x <-> y
if (err != CE_None)
{
return false;
}
int nXValid, nYValid;
// Unfortunately GDALGetActualBlockSize is not available at Suse 42.3 (gdal 2.1.3)
// so check bounds using _totalColumns, _totalRows:
#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(2, 2, 0)
# define HAS_ACTUAL_BLOCKSIZE
#endif
#ifdef HAS_ACTUAL_BLOCKSIZE
GDALGetActualBlockSize(hBand, iXBlock, iYBlock, &nXValid, &nYValid);
#else
nXValid = xBlkSize;
nYValid = yBlkSize;
#endif // HAS_ACTUAL_BLOCKSIZE
const int curYOffset = (iYBlock * yBlkSize);
const int curXOffset = (iXBlock * xBlkSize);
for (int iY = 0; iY < nYValid; ++iY)
{
const int row = iY + curYOffset; // Line
#ifndef HAS_ACTUAL_BLOCKSIZE
if (row >= _totalRows)
continue;
#endif // HAS_ACTUAL_BLOCKSIZE
for (int iX = 0; iX < nXValid; ++iX)
{
// identical to gdallocationinfo
const int column = iX + curXOffset; // Pixel
#ifndef HAS_ACTUAL_BLOCKSIZE
if (column >= _totalColumns)
continue;
#endif // HAS_ACTUAL_BLOCKSIZE
int bi = iX + (iY * xBlkSize);
const int z = static_cast<int>( static_cast<double>(bufferPtr[bi]) / _toMeterFactor );
_elevationField[column + row * _totalColumns] = z;
} // for
} // for
} // for
} // for
return true;
} // template<class BufferType> void copyBufferData(BufferType * bufferPtr, int xBlkSize, int yBlkSize)
}; // struct ElevationMap::Internal
ElevationMap::ElevationMap(const QFileInfo &fileInfo)
: m_internal(new Internal())
, m_aproximation(true)
, m_valid(false)
, m_logger(getLogger("ElevationMap"))
{
GDALAllRegister();
m_fileName = fileInfo.absoluteFilePath();
INFO_LOG(m_logger) << " ctor ElevationMap: map of file '" << m_fileName << "'"
<< ", this=" << this;
if (!fileInfo.exists())
{
ERROR_LOG(m_logger) << LOGFUNCTION << " elevation data file \'" << m_fileName << "\' doesn't exists.";
return;
}
if (!fileInfo.isReadable())
{
ERROR_LOG(m_logger) << LOGFUNCTION << " elevation data file \'" << m_fileName << "\' isn't readable.";
return;
}
auto cb = [this]()
{
return readFromFile();
};
auto cbFinished = [this]()
{
NOTICE_LOG(m_logger) << " Finished reading elevation map of file '" << m_fileName << "'"
<< ", this=" << this;
};
QObject::connect(&m_gdalReadWatcher, &QFutureWatcher<bool>::finished,
&m_gdalReadWatcher, cbFinished);
m_gdalReadWatcher.setFuture(QtConcurrent::run(cb));
// sync init, takes ~800ms during sitmap loading 72MB file
// QElapsedTimer t;
// t.start();
// m_gdalReadWatcher.waitForFinished();
// NOTICE_LOG(m_logger) << "Time loading elevation map: " << t.elapsed();
} // ElevationMap::ElevationMap( const QFile &file )
ElevationMap::~ElevationMap()
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
m_gdalReadWatcher.cancel();
m_gdalReadWatcher.waitForFinished();
delete m_internal;
m_internal = nullptr;
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
} // ElevationMap::~ElevationMap
bool ElevationMap::waitForInitialization()
{
m_gdalReadWatcher.waitForFinished();
return m_valid;
}
bool ElevationMap::isValid() const
{
// N. Thoden, 27.04.2020
// isFinished seems to be unreliable:
// if mainThread request ::isValid() diretly after ::waitForInitialization()
// you get false (by 'if (!m_gdalReadWatcher.isFinished())'
// but m_valid is true. So skip the additional check.
// if (!m_gdalReadWatcher.isFinished())
// return false;
return m_valid;
}
QString ElevationMap::filename() const
{
return m_fileName;
}
bool ElevationMap::readFromFile()
{
m_valid = false;
GDALDatasetH hDataset = GDALOpen(m_fileName.toLocal8Bit().constData(), GA_ReadOnly);
if (nullptr == hDataset)
{
ERROR_LOG(m_logger) << LOGFUNCTION << " GDALOpen failed, may be calling of GDALAllRegister is missing";
return false;
}
// Detect basic parameters
//
GDALDriverH hDriver = GDALGetDatasetDriver(hDataset);
int nRasterXSize = GDALGetRasterXSize(hDataset);
int nRasterYSize = GDALGetRasterYSize(hDataset);
double adfGeoTransform[6];
CPLErr err = GDALGetGeoTransform(hDataset, adfGeoTransform); // XY Spatial Referenz WGS84 (4326) expected!!!
if (err != CE_None)
{
ERROR_LOG(m_logger) << LOGFUNCTION << " GDAL failed to get geo raster transform.";
GDALClose(hDataset);
return false;
}
if (nullptr == hDriver) { GDALClose(hDataset); return false; }
if (nRasterXSize <= 0) { GDALClose(hDataset); return false; }
if (nRasterYSize <= 0) { GDALClose(hDataset); return false; }
double pixWidth = adfGeoTransform[1];
double pixHeight = adfGeoTransform[5];
auto toRAD = [](double x) -> double { return x * MathConst::pi / 180.0; };
m_internal->_location(toRAD(adfGeoTransform[0]) // adfGeoTransform is in degrees
, toRAD(adfGeoTransform[3])
, toRAD(adfGeoTransform[0] + double(nRasterXSize) * pixWidth + double(nRasterYSize) * adfGeoTransform[2])
, toRAD(adfGeoTransform[3] + double(nRasterYSize) * pixHeight + double(nRasterXSize) * adfGeoTransform[4]));
m_internal->_elexelSize.setX(fabs(toRAD(pixWidth)));
m_internal->_elexelSize.setY(fabs(toRAD(pixHeight)));
GDALRasterBandH hBand = GDALGetRasterBand(hDataset, 1);
if (nullptr == hBand)
{
ERROR_LOG(m_logger) << LOGFUNCTION << " GDAL failed to read raster band.";
GDALClose(hDataset);
return false;
}
int xBlkSize;
int yBlkSize;
GDALGetBlockSize(hBand, &xBlkSize, &yBlkSize);
if (xBlkSize <0 || yBlkSize < 0)
{
ERROR_LOG(m_logger) << LOGFUNCTION << " no buffer, next operations will possible fail....";
return false;
}
m_internal->_totalColumns = nRasterXSize;
m_internal->_totalRows = nRasterYSize;
m_internal->_elevationField.resize(nRasterXSize * nRasterYSize);
const GDALDataType type = GDALGetRasterDataType(hBand);
switch (type)
{
case GDT_UInt16:
m_internal->_toMeterFactor = 1.0;
m_valid = m_internal->copyBufferData<unsigned short int> ( hBand, xBlkSize, yBlkSize );
break;
case GDT_Int16:
m_internal->_toMeterFactor = 1.0;
m_valid = m_internal->copyBufferData<short int> ( hBand, xBlkSize, yBlkSize );
break;
case GDT_Float32:
m_internal->_toMeterFactor = 0.01;
m_valid = m_internal->copyBufferData<float> ( hBand, xBlkSize, yBlkSize );
break;
case GDT_Float64:
m_internal->_toMeterFactor = 0.01;
m_valid = m_internal->copyBufferData<double> ( hBand, xBlkSize, yBlkSize );
break;
default:
{
ERROR_LOG(m_logger) << LOGFUNCTION << " data type isn't supported type or a floating point type"
<< " (type=" << type << ")";
GDALClose(hDataset);
return false;
}
} // switch
if (!m_valid)
{
CPLErrorNum errNum = CPLGetLastErrorNo();
ERROR_LOG(m_logger) << LOGFUNCTION << " GDAL failed to read data block (error no " << int(errNum) << ")";
}
GDALClose(hDataset);
INFO_LOG(m_logger) << LOGFUNCTION << " loaded, filename =" << m_fileName << "'"
<< ", m_valid=" << m_valid;
return m_valid;
} // bool ElevationMap::readFromFile(const QString & fileName)
QRectF ElevationMap::extent() const
{
if (!isValid())
return QRectF();
return m_internal->_location.toQRectF();
}
QPair<double, double> ElevationMap::elexelSize() const
{
return {m_internal->_elexelSize._x, m_internal->_elexelSize._y};
}
int ElevationMap::getElevation(int column, int row) const
{
row = m_internal->_totalRows - row;
const size_t index = column + row * m_internal->_totalColumns;
if (m_internal->_elevationField.empty() || m_internal->_elevationField.size() < index)
return std::numeric_limits<int>::min();
return m_internal->_elevationField.at(index);
} // int ElevationMap::getElevation( int column, int row)
double ElevationMap::elevation(double lon, double lat) const
{
if (!isValid())
return 0;
return getElevation(lon, lat) * m_internal->_toMeterFactor;
} // double ElevationMap::elevation( double lon, double lat)
double ElevationMap:: getElevation(double lon, double lat) const
{
if (!m_internal->_location.contains(QPointF(lon, lat)))
{
return std::numeric_limits<int>::min(); // ausserhalb der Höhenkarte !!
}
double x = (lon - m_internal->_location.left()) / m_internal->_elexelSize.x();
int column = floor(x);
double y = (lat - m_internal->_location.bottom()) / m_internal->_elexelSize.y();
int row = floor(y);
int elevation = getElevation(column, row);
if (elevation == std::numeric_limits<int>::min())
return elevation;
if (!m_aproximation)
return elevation;
/*
** Approximation: e0, e1, e2, e3 Hoehenwerte der 4 benachbarten Stützpunkte
**
** + e3 + e2
** y
** ^ + er er = e0 + x*(e1-e0) + y*(e3-e0) + x*y(e2-e3+e0-e1)
** |
** |
** |
** | + e0 + e1
** -----> x
**
** Besonderheiten: die Stützpunkte liegen definitiongemaess jeweils in der Mitte des Rasters,
** so dass je nach Groesse von x, y andere Nachbarpunkte zu berücksichtigen sind, und x, sowie y entsprechend
** angepasst werden muesen.
** Bei Randrasterpunkten kann keine Interpolation erfolgen, so dass hier entsprechend extrapoliert wird.
*/
x = (x - (double)column); // Residuum normiert auf Stützpunktabstand
y = (y - (double)row); // Residuum normiert auf Stützpunktabstand
x -= 0.5; // Mitte berücksichtigen
y -= 0.5; // Mitte berücksichtigen
if (x < 0.0) { column--; x += 1.0;} // linke Spalte zur Interpolation nutzen
if (y < 0.0) { row--; y += 1.0;} // untere Reihe zur Interpolation nutzen
// Randrasterpunkte berücksichtigen: extrapolieren statt interpolieren
if (column <= -1) { x = -x; column = 0;}
else if (column >= (m_internal->_totalColumns - 1)) { x += 1.0; column = m_internal->_totalColumns - 2;}
if (row == -1) { y = -y; row = 0;}
else if (row >= (m_internal->_totalRows - 1)) { y += 1.0; row = m_internal->_totalRows - 2;}
// Die Approximation:
double substitute = elevation;
double e0 = getOrSubstituteElevation(column, row, substitute);
double e1 = getOrSubstituteElevation(column + 1, row, substitute);
double e2 = getOrSubstituteElevation(column + 1, row + 1, substitute);
double e3 = getOrSubstituteElevation(column, row + 1, substitute);
double er = e0 + x * (e1 - e0) + y * (e3 - e0) + x * y * (e2 - e3 + e0 - e1);
return er; // floor(er+0.5);; runden herausgenommen 19.8.2008 JT
} // double ElevationMap:: getElevation( double lon, double lat)
double ElevationMap::getOrSubstituteElevation(int column, int row, double substitute) const
{
int elevation = getElevation(column, row);
if (elevation != std::numeric_limits<int>::min())
return elevation;
int substCount = 0;
double calcSubstitute = 0.0;
elevation = getElevation(column + 1, row);
if (elevation != std::numeric_limits<int>::min()) {substCount++; calcSubstitute += elevation; }
elevation = getElevation(column - 1, row);
if (elevation != std::numeric_limits<int>::min()) {substCount++; calcSubstitute += elevation; }
elevation = getElevation(column, row + 1);
if (elevation != std::numeric_limits<int>::min()) {substCount++; calcSubstitute += elevation; }
elevation = getElevation(column, row - 1);
if (elevation != std::numeric_limits<int>::min()) {substCount++; calcSubstitute += elevation; }
if (substCount)
substitute = calcSubstitute / substCount;
return substitute;
} // double ElevationMap::getOrSubstituteElevation( int column, int row, double substitute)
double ElevationMap::distance(const QPointF &start, const QPointF &end) const
{
if (!isValid())
return 0;
if (!m_internal->_location.contains(start) || !m_internal->_location.contains(end))
{
return -1.0; // ausserhalb{ der Höhenkarte !!
}
double start_z = getElevation(start.x(), start.y());
if (start_z <= std::numeric_limits<int>::min()) return -1.0;
double end_z = getElevation(end.x(), end.y());
if (end_z <= std::numeric_limits<int>::min()) return -1.0;
const QPointF &dist = distance_in_meter_WGS84(start, end);
double dz = end_z - start_z;
double d = sqrt(dist.x() * dist.x() + dist.y() * dist.y() + dz * dz) ;
return d;
} // double ElevationMap::distance( const QPointF &start, const QPointF &end )
double ElevationMap::profileDistance(const QPointF &start, const QPointF &end) const
{
if (!isValid())
return 0;
if (!m_internal->_location.contains(start) || !m_internal->_location.contains(end))
{
return -1.0; // ausserhalb der Höhenkarte !!
}
Vec3dVector elevProfile;
getSamplePoints(start, end, elevProfile);
for (Vec3dVector::iterator itr = elevProfile.begin(); itr != elevProfile.end(); ++itr)
itr->setZ(getElevation(itr->x(), itr->y()));
// Einzellaengen aufsummieren, Stützstellen mit undefinierten Hoehen werden ausgelassen!
Point3D curPos(*elevProfile.begin());
double sumLng = 0.0;
for (Vec3dVector::iterator itr = elevProfile.begin() + 1; itr != elevProfile.end(); ++itr)
{
if ((*itr).z() > std::numeric_limits<int>::min())
{
if (curPos.z() > std::numeric_limits<int>::min())
{
const QPointF &dist = distance_in_meter_WGS84(QPointF(curPos.x(), curPos.y()), QPointF(itr->x(), itr->y()));
double dz = itr->z() - curPos.z();
sumLng += sqrt(dist.x() * dist.x() + dist.y() * dist.y() + dz * dz) ;
}
else
{
double dx = itr->x() - curPos.x();
double dy = itr->y() - curPos.y();
sumLng += sqrt(dx * dx + dy * dy);
}
curPos = *itr;
}
}
return sumLng;
} // double ElevationMap::profileDistance( const QPointF &start, const QPointF &end)
unsigned int ElevationMap::elevationProfile(const QPointF &start, const QPointF &end, Vec3dVector &elevProfile) const
{
unsigned int startSize = elevProfile.size();
getSamplePoints(start, end, elevProfile);
for (Vec3dVector::iterator itr = elevProfile.begin() + startSize; itr != elevProfile.end(); ++itr)
itr->setZ(getElevation(itr->x(), itr->y()));
return (elevProfile.size() - startSize);
} // unsigned int ElevationMap::elevationProfile( const QPointF &start, const QPointF &end, Vec3dVector &elevProfile)
bool ElevationMap::contains(const QPointF &point) const
{
return m_internal->_location.contains(point);
}
bool ElevationMap::intersects(const QPolygonF &polygon) const
{
const BoundingRect &bbox = m_internal->_location;
// maybe QPointF uses float, but we can accept double to be reduced to float:
const QPolygonF &ourBorder = QPolygonF({QPointF(bbox.left() , bbox.bottom()),
QPointF(bbox.left() , bbox.top()),
QPointF(bbox.right(), bbox.top()),
QPointF(bbox.right(), bbox.bottom())});
#if QT_VERSION < QT_VERSION_CHECK(5,10,0)
return ourBorder.intersected(polygon).size() > 0;
#else
return ourBorder.intersects(polygon);
#endif
}
void ElevationMap::getSamplePoints(const QPointF &start, const QPointF &end, Vec3dVector &sPnts) const
{
sPnts.clear();
sPnts.push_back(Point3D::fromQPointF(start));
bool endIsClipped = false;
QPointF cStart(start);
QPointF cEnd(end);
if (!m_internal->_location.contains(start) || !m_internal->_location.contains(end))
{
int res = clipp2Map(start, end, cStart, cEnd);
if (res == 0) // no intersection
{
sPnts.push_back(Point3D::fromQPointF(end));
return;
}
if (res & 1) // start clipped
sPnts.push_back(Point3D::fromQPointF(cStart));
if (res & 2) // end clipped
endIsClipped = true;
}
QPointF delta(cEnd - cStart);
// Berechne alle Schnittpunkte des Vektors( cStart, cEnd) mit den x-Grenzen der Hoehenstuetzrechtecke.
// Die Stützpunkte liegen definitiongemaess jeweils in der Mitte des Raster-Rechtecks
int startCol = floor((cStart.x() - m_internal->_location.left()) / m_internal->_elexelSize.x());
if (startCol < 0) startCol = 0;
else if (startCol >= m_internal->_totalColumns) startCol = m_internal->_totalColumns - 1;
int endCol = floor((cEnd.x() - m_internal->_location.left()) / m_internal->_elexelSize.x());
if (endCol < 0) endCol = 0;
else if (endCol >= m_internal->_totalColumns) endCol = m_internal->_totalColumns - 1;
int incCol = 0;
if (endCol > startCol) incCol = 1;
else if (endCol < startCol) incCol = -1;
double m = delta.y() / delta.x();
Vec2dVector xCuts;
xCuts.reserve(std::abs(endCol - startCol));
for (int col = startCol; col != endCol; col += incCol)
{
double x = m_internal->_location.left() + ((col + 0.5) + (incCol * 0.5)) * m_internal->_elexelSize.x();
double y = cStart.y() + (x - cStart.x()) * m;
xCuts.push_back(QPointF(x, y));
}
// Berechne alle Schnittpunkte des Vektors( cStart, cEnd) mit den y-Grenzen der Hoehenstuetzrechtecke.
// Die Stützpunkte liegen definitiongemaess jeweils in der Mitte des Raster-Rechtecks
int startRow = floor((cStart.y() - m_internal->_location.bottom()) / m_internal->_elexelSize.y());
if (startRow < 0) startRow = 0;
else if (startRow >= m_internal->_totalRows) startRow = m_internal->_totalRows - 1;
int endRow = floor((end.y() - m_internal->_location.bottom()) / m_internal->_elexelSize.y());
if (endRow < 0) endRow = 0;
else if (endRow >= m_internal->_totalRows) endRow = m_internal->_totalRows - 1;
int incRow = 0;
if (endRow > startRow) incRow = 1;
else if (endRow < startRow) incRow = -1;
m = delta.x() / delta.y();
Vec2dVector yCuts;
yCuts.reserve(std::abs(endRow - startRow));
for (int row = startRow; row != endRow; row += incRow)
{
double y = m_internal->_location.bottom() + ((row + 0.5) + (incRow * 0.5)) * m_internal->_elexelSize.y();
double x = cStart.x() + (y - cStart.y()) * m;
yCuts.push_back(QPointF(x, y));
}
// xCuts und yCuts zusammenführen, unter Berücksichtigung der Reihenfolge
Vec2dVector mergeCuts;
Vec2dVector::iterator xCutItr = xCuts.begin();
Vec2dVector::iterator yCutItr = yCuts.begin();
while ((xCutItr != xCuts.end()) && (yCutItr != yCuts.end()))
{
if (incCol > 0)
{
if ((*xCutItr).x() < (*yCutItr).x()) { mergeCuts.push_back(*xCutItr); ++xCutItr; }
else { mergeCuts.push_back(*yCutItr); ++yCutItr; }
}
else
{
if ((*xCutItr).x() > (*yCutItr).x()) { mergeCuts.push_back(*xCutItr); ++xCutItr; }
else { mergeCuts.push_back(*yCutItr); ++yCutItr; }
}
}
while ((xCutItr != xCuts.end())) { mergeCuts.push_back(*xCutItr); ++xCutItr; }
while ((yCutItr != yCuts.end())) { mergeCuts.push_back(*yCutItr); ++yCutItr; }
// mergeCuts enthält sequenziell alle Schnittpunkte des Vektors mit den Hoehengridkanten
// Die 2 Schnittpunkte pro Hoehenwert werden gemittelt zu einem zusammengefasst
if (mergeCuts.size() > 1)
{
for (Vec2dVector::iterator itr = mergeCuts.begin() + 1; itr != mergeCuts.end(); ++itr)
sPnts.push_back(Point3D::fromQPointF((*(itr - 1) + *itr) * 0.5));
}
sPnts.push_back(Point3D::fromQPointF(cEnd));
// DEBUG_LOG(m_logger) << " xCuts.si " << xCuts.size()
// << ", yCuts.si " << yCuts.size()
// << ", mergeCuts.si " << mergeCuts.size();
if (endIsClipped) sPnts.push_back(Point3D::fromQPointF(end));
} // void ElevationMap::getSamplePoints( const QPointF &start, const QPointF &end, Vec3dVector & sPnts)
// start und/oder end liegt ausserhalb !! in Lat/Lon !!
int ElevationMap::clipp2Map(const QPointF &start, const QPointF &end, QPointF &cStart, QPointF &cEnd) const
{
Vec2dVector clippVec;
if (end.x() != start.x())
{
double x = m_internal->_location.right();
double m = (end.y() - start.y()) / (end.x() - start.x());
if (isInIntervall(x, start.x(), end.x()))
{
double yc = start.y() + (x - start.x()) * m;
if ((yc <= m_internal->_location.top()) && (yc >= m_internal->_location.bottom()))
clippVec.push_back(QPointF(x, yc));
}
x = m_internal->_location.left();
if (isInIntervall(x, start.x(), end.x()))
{
double yc = start.y() + (x - start.x()) * (end.y() - start.y()) / (end.x() - start.x());
if ((yc <= m_internal->_location.top()) && (yc >= m_internal->_location.bottom()))
clippVec.push_back(QPointF(x, yc));
}
}
if (end.y() != start.y())
{
double y = m_internal->_location.top();
double m = (end.x() - start.x()) / (end.y() - start.y());
if (isInIntervall(y, start.y(), end.y()))
{
double xc = start.x() + (y - start.y()) * m;
if ((xc < m_internal->_location.right()) && (xc > m_internal->_location.left()))
clippVec.push_back(QPointF(xc, y));
}
y = m_internal->_location.bottom();
if (isInIntervall(y, start.y(), end.y()))
{
double xc = start.x() + (y - start.y()) * m;
if ((xc < m_internal->_location.right()) && (xc > m_internal->_location.left()))
clippVec.push_back(QPointF(xc, y));
}
}
int size = (int)clippVec.size();
if (size == 0)
{
return 0; // komlett ausserhalb ( oder komplett innerhalb)
}
if (size == 1)
{
if (m_internal->_location.contains(start))
{
cEnd = clippVec[0]; return 2;
}
else
{
cStart = clippVec[0]; return 1;
}
}
if (size == 2)
{
if (squareLength(clippVec[0] - start) < squareLength(clippVec[1] - start))
{
cStart = clippVec[0];
cEnd = clippVec[1];
}
else
{
cStart = clippVec[1];
cEnd = clippVec[0];
}
return 3;
}
ERROR_LOG(m_logger) << LOGFUNCTION << "Internal Clipp Error!";
return 0;
} // int ElevationMap::clipp2Map( const QPointF &start, const QPointF &end, QPointF &cStart, QPointF &cEnd)
// Line of Sight Profile: berücksichtigt Erdkrümmung und WGS84 Erdellipsoid
void ElevationMap::LOSProfile(const QPointF &start, const QPointF &end, LOSVector &losProfile)
{
m_gdalReadWatcher.waitForFinished();
if (!m_internal->_location.contains(start)) return ; // ausserhalb der Höhenkarte !!
bool clipped = false;
double totalLng;
QPointF startG = start;
QPointF endG = end;
if (!m_internal->_location.contains(end)) // Clippen
{
clipped = true;
totalLng = length(endG - startG);
if (endG.x() < m_internal->_location.left())
{
double m = (endG.y() - startG.y()) / (endG.x() - startG.x());
double x = m_internal->_location.left();
endG.setX(x);
endG.setY(startG.y() + m * (x - startG.x()));
}
else if (endG.x() > m_internal->_location.right())
{
double m = (endG.y() - startG.y()) / (endG.x() - startG.x());
double x = m_internal->_location.right();
endG.setX(x);
endG.setY(startG.y() + m * (x - startG.x()));
}
if (endG.y() < m_internal->_location.bottom())
{
double m = (endG.x() - startG.x()) / (endG.y() - startG.y());
double y = m_internal->_location.bottom();
endG.setY(y);
endG.setX(startG.x() + m * (y - startG.y()));
}
else if (endG.y() > m_internal->_location.top())
{
double m = (endG.x() - startG.x()) / (endG.y() - startG.y());
double y = m_internal->_location.top();
endG.setY(y);
endG.setX(startG.x() + m * (y - startG.y()));
}
}
// Korrektursteigung aufgrund WGS84 Erdellipsoid, Lineare Approximation, Fehler bis 200 km: < 1.0m
double startRadius = radiusWGS84(startG.y());
double corrM = (radiusWGS84(endG.y()) - startRadius) / length(endG - startG);
Vec3dVector elevProfile;
getSamplePoints(start, end, elevProfile);
for (Vec3dVector::iterator itr = elevProfile.begin(); itr != elevProfile.end(); ++itr)
{
double dist = length(distance_in_meter_WGS84(start, itr->toPointF()));
double height = elevation(itr->x(), itr->y());
height += corrM * dist; // WGS84 Erdellipsoid Korrektur
height -= (startRadius * (1 - cos(atan(dist / startRadius)))); // Einbeziehen der Erdkruemmung
// alternativ als Naeherung: height -= ( dist * dist) / (startRadius * 2.0);
losProfile.push_back(LOSentry(dist, height));
}
if (clipped)
losProfile.push_back(LOSentry(totalLng, -NAN));
qCritical() << " losProfile.isze() = " << losProfile.size();
} // void ElevationMap::LOSProfile( const QPointF &start, const QPointF &end, LOSVector &losProfile)
} // SituationMap
/* -----------------------------------------------------------------------------------------------
** CVS-Id/Release ...: $Header$
** -----------------------------------------------------------------------------------------------
**
** Historie:
**
** $Log: not supported by cvs2svn $
** Revision 1.17.1.1 2008/06/16 06:05:37 joerg
** Transparenz und Bias einheitlich für alle Objekte implementiert
**
** Revision 1.17 2006/03/24 09:05:01 klaus
** Elevationprofile: Begrenzung auf Kartengröße aufgehoben ( Clipping)
**
** Revision 1.16 2005/11/09 13:44:36 klaus
** Umsetzung auf Linux;
**
** Revision 1.15 2005/04/15 13:05:45 joerg
** Resource Control
**
** Revision 1.14 2005/03/04 14:29:26 klaus
** Elevationmap mit CachedTileList versehen und automatischem Entladen von Tiles
**
** Revision 1.13 2005/03/03 12:37:53 klaus
** dynamischen Laden und Entladen der ElevationTiles für Reliefmap
**
** Revision 1.12 2005/03/01 12:36:59 klaus
** Reliefmap eingeführt, aus Elevationmap entfernt
**
** Revision 1.11 2005/02/25 15:32:56 klaus
** Ergänzung um Reliefkarte
**
** Revision 1.10 2005/02/10 14:15:19 klaus
** Bias in Basisklasse MapObject überführt
**
** Revision 1.9 2005/02/10 13:50:33 klaus
** Bias für SightFans eingeführt
**
** Revision 1.8 2005/02/09 16:45:26 klaus
** 1. Version der SightFans
**
** Revision 1.7 2005/01/26 12:26:23 klaus
** todos bearbeitet, Fehlerrückmeldnugen
**
** Revision 1.6 2004/12/14 10:29:48 klaus
** 7. Erweiterung für Höhenkarten
**
** Revision 1.5 2004/12/07 11:08:28 klaus
** 5. Erweiterung für Höhenkarten
**
** Revision 1.4 2004/12/03 16:25:14 klaus
** 4. Erweiterung für Höhenkarten
**
** Revision 1.3 2004/11/30 13:18:13 klaus
** 3. Erweiterung für Höhenkarten
**
** Revision 1.2 2004/11/23 14:36:40 klaus
** 2. Erweiterung für Höhenkarten
**
**
** ----------------------------------------------------------------------------------------------- */
+168
View File
@@ -0,0 +1,168 @@
/**
* @RDE_HEADER@
* adapting GeISiR source to SituationMap
* $Id$
* $HeadURL$
* $Author$
*/
#pragma once
#include "elevation_map_helper.h"
#include <ElevationMap/situationmapelevationmap_export.h>
#include <LtsUtilsCore/qpoint3d.h>
#include <Common/situation_map_logging.h>
#include <QFutureWatcher>
#include <QPointF>
#include <QRectF>
#include <QVector>
class QFileInfo;
namespace SituationMap
{
//------------------------------------------------------------------------------------------------------------
// ElevationMap
//------------------------------------------------------------------------------------------------------------
class SITUATIONMAPELEVATIONMAP_EXPORT ElevationMap final
{
friend class ElevationMapManager;
public:
ElevationMap() = delete;
ElevationMap(const ElevationMap &) = delete;
ElevationMap &operator = (const ElevationMap &) = delete;
explicit ElevationMap(const QFileInfo &fileInfo);
~ElevationMap();
bool waitForInitialization();
bool isValid() const;
QString filename() const;
QRectF extent() const;
QPair<double, double> elexelSize() const;
double elevation(double lon, double lat) const; // rad
double distance(const QPointF &start, const QPointF &end) const;
double profileDistance(const QPointF &start, const QPointF &end) const;
unsigned int elevationProfile(const QPointF &start, const QPointF &end, Vec3dVector &elevProfile) const;
void interpolation(bool on) { m_aproximation = on; }
bool interpolation() const { return m_aproximation; }
bool contains(const QPointF &point) const;
bool intersects(const QPolygonF &polygon) const;
// Line of Sight Profile: berücksichtigt Erdkrümmung und WGS84 Erdellipsoid
void LOSProfile(const QPointF &start, const QPointF &end, LOSVector &losProfile);
protected:
int getElevation(int column, int row) const;
double getElevation(double lon, double lat) const;
double getOrSubstituteElevation(int column, int row, double substitute) const;
// getSamplePoints rastert den Vector( start, end) in Lat/Lon entsprechend dem Höhenraster
void getSamplePoints(const QPointF &start, const QPointF &end, Vec3dVector &sPnts) const;
int clipp2Map(const QPointF &start, const QPointF &end, QPointF &cStart, QPointF &cEnd) const;
bool readFromFile();
struct Internal;
Internal *m_internal;
QString m_fileName;
bool m_aproximation;
bool m_valid;
QFutureWatcher<bool> m_gdalReadWatcher;
LOGGER_T m_logger; ///< Interface of the Logging-Facility
}; // ElevationMap
} // SituationMap
/* -----------------------------------------------------------------------------------------------
** CVS-Id/Release ...: $Header$
** -----------------------------------------------------------------------------------------------
**
** Historie:
**
** $Log: not supported by cvs2svn $
** Revision 1.20 2006/09/26 07:51:12 Jörg
** Syntax Fehler unter Suse Linux 10.1 behoben.
**
** Revision 1.19 2006/03/24 09:05:24 klaus
** Elevationprofile: Begrenzung auf Kartengröße aufgehoben ( Clipping)
**
** Revision 1.18 2005/11/09 11:12:27 klaus
** Anpassung an Linux: Kommentar hinter #endif mit "//" versehen
**
** Revision 1.17 2005/08/22 14:15:02 joerg
** typeId()
**
** Revision 1.16 2005/08/12 09:37:44 joerg
** Koordinaten-Umprojezieren bei Wechsel der Projektion
**
** Revision 1.15 2005/04/15 13:04:35 joerg
** Resource Control
**
** Revision 1.14 2005/03/04 14:29:13 klaus
** Elevationmap mit CachedTileList versehen und automatischem Entladen von Tiles
**
** Revision 1.13 2005/03/03 12:37:14 klaus
** dynamischen Laden und Entladen der ElevationTiles für Reliefmap
**
** Revision 1.12 2005/03/01 12:33:12 klaus
** Reliefmap eingeführt, aus Elevationmap entfernt
**
** Revision 1.11 2005/02/25 15:26:41 klaus
** Ergänzung um Reliefkarte
**
** Revision 1.10 2005/02/10 14:13:21 klaus
** Bias in Basisklasse MapObject überführt
**
** Revision 1.9 2005/02/10 13:51:01 klaus
** Bias für SightFans eingeführt
**
** Revision 1.8 2005/02/09 16:53:06 klaus
** 1. Version der SightFans
**
** Revision 1.7 2005/01/26 12:25:41 klaus
** todos bearbeitet, Fehlerrückmeldnugen
**
** Revision 1.6 2004/12/14 09:43:25 klaus
** 7. Erweiterung für Höhenkarten
**
** Revision 1.5 2004/12/07 11:07:20 klaus
** 5. Erweiterung für Höhenkarten
**
** Revision 1.4 2004/12/03 16:24:12 klaus
** 4. Erweiterung für Höhenkarten
**
** Revision 1.3 2004/11/30 13:15:28 klaus
** 3. Erweiterung für Höhenkarten
**
** Revision 1.2 2004/11/23 14:36:14 klaus
** 2. Erweiterung für Höhenkarten
**
**
** ----------------------------------------------------------------------------------------------- */
@@ -0,0 +1,88 @@
/**
* @RDE_HEADER@
* Some functions and struct for ElevationMap/LineOfSight (LOS)
* $Id$
* $HeadURL$
* $Author$
*/
#pragma once
#include <LtsUtilsCore/qpoint3d.h>
#include <math.h>
#include <MathUtils/math_const.h>
#include <QPointF>
#include <QVector>
namespace SituationMap
{
inline bool isInIntervall(double x, double x1, double x2)
{
return (((x > x1) && (x < x2)) || (((x > x2) && (x < x1))));
} // inline bool isInIntervall(double x, double x1, double x2)
inline double radiusWGS84(double lat)
{
double sinLAT = sin(lat);
return (MathConst::WGS84_Major_a * sqrt(1.0 - MathConst::WGS84_SQR_EXC * sinLAT * sinLAT));
} // inline double radiusWGS84(double lat)
inline QPointF distance_in_meter_WGS84(const QPointF &start, const QPointF &end) // start, end in LonLat
{
// todo: prüfen ob Distanz zu groß, dann verbesserte Berechnung !!
double lat = (end.y() + start.y()) / 2.0;
double faktorLon = MathConst::WGS84_Major_a * cos(lat); // radiusWGS84(lat) * 2.0 * PI;
double faktorLat = MathConst::WGS84_Minor_b;
return QPointF(faktorLon * (end.x() - start.x()), faktorLat * (end.y() - start.y()));
} // QPointF distance_in_meter_WGS84(const QPointF &start, const QPointF &end)
inline double squareLength(const QPointF &v)
{
return v.x() * v.x() + v.y() * v.y();
} // inline double squareLength(const QPoint & v)
inline double length(const QPointF &v)
{
return sqrt(squareLength(v));
} // inline double length(const QPoint & v)
struct LOSentry // Defines 1 Entry for Line of Sight
{
LOSentry() = default;
LOSentry(double dist, double height) : _dist(dist), _height(height) { ; }
double &dist() { return _dist; }
double &height() { return _height; }
double dist() const { return _dist; }
double height() const { return _height; }
bool isUndefined() const { return std::isnan(_height); }
private:
double _dist = 0; // Distance from Start
double _height = -NAN;
}; /* LOSentry */
using Point3D = LtsUtils::QPoint3D; ///< x = lon ; y = lat , z = elevation/altitude in meters
using LOSVector = QVector<LOSentry>;
using Vec2dVector = QVector<QPointF>;
using Vec3dVector = QVector<Point3D>;
} // SituationMap
@@ -0,0 +1,531 @@
/**
* @RDE_HEADER@
* Manage and combine several ElevationMap
* $Id$
* $HeadURL$
* $Author$
*/
#include "elevation_map_manager.h"
#include "elevation_map.h"
// Qt
#include <QPointer>
#include <QFileInfo>
#include <QRectF>
#include <QPolygonF>
// system
#include <vector>
namespace SituationMap
{
static long s_counter{0};
//------------------------------------------------------------------------------------------------------------
// ElevationMapManager::Private
//------------------------------------------------------------------------------------------------------------
class ElevationMapManager::Private
{
public:
// fine to rough
std::vector<ElevationMap *> m_elevationMapCont;
QRectF m_simpleExtent;
};
//------------------------------------------------------------------------------------------------------------
// ElevationMapManager
//------------------------------------------------------------------------------------------------------------
ElevationMapManager::ElevationMapManager()
: BaseClass()
, d(new Private())
, m_logger(getLogger("ElevationMapManager"))
{
++s_counter;
TRACE_LOG(m_logger) << LOGFUNCTION << " START, s_counter=" << s_counter << " this=" << this;
TRACE_LOG(m_logger) << LOGFUNCTION << " END, s_counter=" << s_counter << " this=" << this;
}
ElevationMapManager::~ElevationMapManager()
{
--s_counter;
TRACE_LOG(m_logger) << LOGFUNCTION << " START, s_counter=" << s_counter << " this=" << this;
cleanupCurrentContent();
delete d;
d = nullptr;
TRACE_LOG(m_logger) << LOGFUNCTION << " END, s_counter=" << s_counter << " this=" << this;
}
int ElevationMapManager::init(const QStringList &filePaths_rough_to_fine)
{
NOTICE_LOG(m_logger) << LOGFUNCTION << " START" << " filePaths_rough_to_fine=" << filePaths_rough_to_fine.join(QStringLiteral(" ; "));
cleanupCurrentContent();
d->m_elevationMapCont.reserve(filePaths_rough_to_fine.size());
int cnt = 0;
for (const QString &filePath : filePaths_rough_to_fine)
{
QFileInfo fileInfo(filePath);
const QString &fileName = fileInfo.absoluteFilePath();
if (!fileInfo.exists())
{
ERROR_LOG(m_logger) << LOGFUNCTION << " elevation data file \'" << fileName << "\' doesn't exists.";
continue;
}
if (!fileInfo.isReadable())
{
ERROR_LOG(m_logger) << LOGFUNCTION << " elevation data file \'" << fileName << "\' isn't readable.";
continue;
}
// ctor will spawn thread via QtConcurrent and read file async:
d->m_elevationMapCont.insert(d->m_elevationMapCont.begin(), new ElevationMap(fileInfo));
++cnt;
} // for
NOTICE_LOG(m_logger) << LOGFUNCTION << " END" << " filePaths_rough_to_fine=" << filePaths_rough_to_fine.join(QStringLiteral(" ; "))
<< ", following number of files are going to be loaded: " << cnt;
return cnt;
}
void ElevationMapManager::cleanupCurrentContent()
{
qDeleteAll(d->m_elevationMapCont);
d->m_elevationMapCont.clear();
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
bool ElevationMapManager::waitForInitialization()
{
for (ElevationMap *elevationMap : d->m_elevationMapCont)
elevationMap->waitForInitialization();
return isValid();
}
bool ElevationMapManager::isValid() const
{
bool allAreValid = true;
for (const ElevationMap *elevationMap : d->m_elevationMapCont)
allAreValid = allAreValid && elevationMap->isValid();
// FATAL_LOG(m_logger) << LOGFUNCTION << " allAreValid = " << allAreValid;
return allAreValid;
}
double ElevationMapManager::elevation(double lon_rad, double lat_rad, bool *bIsCovered) const
{
// assume, that all children are ready
for (const ElevationMap *elevationMap : d->m_elevationMapCont)
{
// qCritical() << " EMM::elevation: "
// << " lon_rad=" << lon_rad
// << " lat_rad=" << lat_rad
// << " (try: " << elevationMap->filename() << ")...";
double singleResult = elevationMap->elevation(lon_rad, lat_rad);
if (singleResult > -300) // -300 -> ausserhalb der Höhenkarte !!
{
// qCritical() << " EMM::elevation: elevation= " << singleResult << " (taken from: " << elevationMap->filename() << ")";
if (bIsCovered)
*bIsCovered = true;
return singleResult;
}
}
// DEBUG_LOG(m_logger) << LOGFUNCTION << " invalid= "
// << " lon_rad=" << lon_rad
// << " lat_rad=" << lat_rad;
if (bIsCovered)
*bIsCovered = false;
return std::numeric_limits<double>::min();
}
double ElevationMapManager::elevation(double lon_rad, double lat_rad, double defaultValue, bool *bIsCovered) const
{
bool bIsCoveredLocal = false;
const double elevationValue = elevation(lon_rad, lat_rad, &bIsCoveredLocal);
// in any case: if provided, transfer isCovered back to caller
if (bIsCovered)
*bIsCovered = bIsCoveredLocal;
// which value to be returned?
if (!bIsCoveredLocal)
return defaultValue;
return elevationValue;
}
QRectF ElevationMapManager::simpleUnitedExtent() const
{
if (d->m_simpleExtent.isNull())
{
bool bFirst = true;
for (const ElevationMap *elevationMap : d->m_elevationMapCont)
{
const QRectF &singleExtent = elevationMap->extent();
if (bFirst)
{
bFirst = false;
d->m_simpleExtent = singleExtent;
// qCritical() << " bFirst.left " << singleExtent.left() << " bFirst.right " << singleExtent.right();
// qCritical() << " bFirst.top " << singleExtent.top() << " bFirst.bottom " << singleExtent.bottom();
} else {
// prefer following instead QRectF::united (top vs. bottom):
// qCritical() << " next.left " << singleExtent.left() << " secnextright " << singleExtent.right();
// qCritical() << " next.top " << singleExtent.top() << " next.bottom " << singleExtent.bottom();
d->m_simpleExtent.setLeft (std::min(d->m_simpleExtent.left(), singleExtent.left()));
d->m_simpleExtent.setTop (std::max(d->m_simpleExtent.top(), singleExtent.top())); // top - >use max !!!
d->m_simpleExtent.setBottom(std::min(d->m_simpleExtent.bottom(), singleExtent.bottom())); // bottom -> use min
d->m_simpleExtent.setRight (std::max(d->m_simpleExtent.right(), singleExtent.right()));
}
} // for
}
return d->m_simpleExtent;
}
bool ElevationMapManager::contains(const QPointF &point) const
{
for (const ElevationMap *elevationMap : d->m_elevationMapCont)
{
if (elevationMap->contains(point))
return true;
}
return false;
}
void ElevationMapManager::LOSProfile(const QPointF &start, const QPointF &end, LOSVector &losProfile)
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
if (d->m_elevationMapCont.empty())
{
ERROR_LOG(m_logger) << LOGFUNCTION << " no elevationMap";
return;
}
waitForInitialization();
QRectF simpleExtentLocal = simpleUnitedExtent();
// qCritical() << " simpleExtent.left " << simpleExtentLocal.left() << " simpleExtent.right " << simpleExtentLocal.right();
// qCritical() << " simpleExtent.top " << simpleExtentLocal.top() << " simpleExtent.bottom " << simpleExtentLocal.bottom();
if (!simpleExtentLocal.contains(start))
{
ERROR_LOG(m_logger) << LOGFUNCTION << " start coordinate not within area"
<< ", start: x=" << start.x() << " ; y=" << start.y()
<< ", area: "
<< " left " << simpleExtentLocal.left() << ", right " << simpleExtentLocal.right()
<< " top " << simpleExtentLocal.top() << ", bottom " << simpleExtentLocal.bottom()
;
return; // ausserhalb der Höhenkarte !!
}
bool clipped = false;
double totalLng;
QPointF startG = start;
QPointF endG = end;
if (!simpleExtentLocal.contains(end)) // Clippen
{
clipped = true;
totalLng = length(endG - startG);
if (endG.x() < simpleExtentLocal.left())
{
double m = (endG.y() - startG.y()) / (endG.x() - startG.x());
double x = simpleExtentLocal.left();
endG.setX(x);
endG.setY(startG.y() + m * (x - startG.x()));
}
else if (endG.x() > simpleExtentLocal.right())
{
double m = (endG.y() - startG.y()) / (endG.x() - startG.x());
double x = simpleExtentLocal.right();
endG.setX(x);
endG.setY(startG.y() + m * (x - startG.x()));
}
if (endG.y() < simpleExtentLocal.bottom())
{
double m = (endG.x() - startG.x()) / (endG.y() - startG.y());
double y = simpleExtentLocal.bottom();
endG.setY(y);
endG.setX(startG.x() + m * (y - startG.y()));
}
else if (endG.y() > simpleExtentLocal.top())
{
double m = (endG.x() - startG.x()) / (endG.y() - startG.y());
double y = simpleExtentLocal.top();
endG.setY(y);
endG.setX(startG.x() + m * (y - startG.y()));
}
}
// Korrektursteigung aufgrund WGS84 Erdellipsoid, Lineare Approximation, Fehler bis 200 km: < 1.0m
double startRadius = radiusWGS84(startG.y());
double corrM = (radiusWGS84(endG.y()) - startRadius) / length(endG - startG);
//
// getSamplePoints(start, end, elevProfile);
// find best elevationMap for equested path {startG, endG}
//
// 1st find maps covering the desired pathG:
const QPolygonF pathG( QVector<QPointF>{startG, endG} );
std::vector<ElevationMap *> coveredElevationMaps;
coveredElevationMaps.reserve(d->m_elevationMapCont.size());
for (auto curItem : qAsConst(d->m_elevationMapCont))
{
if (curItem && curItem->intersects(pathG))
coveredElevationMaps.push_back(curItem);
} // for
if (m_logger.isDebugEnabled())
{
DEBUG_LOG(m_logger) << " coveredElevationMap.size() = " << coveredElevationMaps.size();
for (auto curItem : qAsConst(coveredElevationMaps))
DEBUG_LOG(m_logger) << " coveredElevationMap = " << curItem->filename();
}
// if no maps covers the desired path, stay on old approach: use most bottom elevation map:
if (coveredElevationMaps.empty())
coveredElevationMaps = d->m_elevationMapCont;
// 2nd: find elevation map with highest resolution to achieve best results:
using ELE_VEC_IT = std::vector<ElevationMap*>::iterator;
ELE_VEC_IT eleIt = coveredElevationMaps.begin();
for (ELE_VEC_IT curItem = coveredElevationMaps.begin()+1; curItem != coveredElevationMaps.end(); ++curItem)
{
ElevationMap *elePtr = *curItem;
if (!elePtr)
continue;
if (elePtr->elexelSize().first < (*eleIt)->elexelSize().first)
eleIt = curItem;
} // for
Q_ASSERT_X((*eleIt), "ElevationMapManager::LOSProfile", "eleIt failed");
Vec3dVector elevProfile;
(*eleIt)->getSamplePoints(start, end, elevProfile);
INFO_LOG(m_logger) << "Using ElevationMap '" << (*eleIt)->filename() << "'"
<< " (of size = " << d->m_elevationMapCont.size() << ")"
<< " with_elexelSize " << (*eleIt)->elexelSize().first
<< " -> # SamplePoints = " << elevProfile.size();
for (Vec3dVector::iterator itr = elevProfile.begin(); itr != elevProfile.end(); ++itr)
{
double dist = length(distance_in_meter_WGS84(start, itr->toPointF()));
double height = elevation(itr->x(), itr->y()); // use elevation over all seperate elevationMaps
height += corrM * dist; // WGS84 Erdellipsoid Korrektur
height -= (startRadius * (1 - cos(atan(dist / startRadius)))); // Einbeziehen der Erdkruemmung
// alternativ als Naeherung: height -= ( dist * dist) / (startRadius * 2.0);
losProfile.push_back(LOSentry(dist, height));
}
if (clipped)
losProfile.push_back(LOSentry(totalLng, -NAN));
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
}
ElevationMapManager::LineOfSight ElevationMapManager::lineOfSightCalculation(const LOSVector &losProfile,
double centerHat,
double objHat) const
{
// Analog zu SightFanImpl::calculateSightBeam
if (losProfile.size() < 2) // Startpunkt undefiniert oder ausserhalb der Karte
{
return LineOfSight::LOS_ERROR;
}
LOSVector::const_iterator itr = losProfile.begin();
const double centerHeight = itr->height() + centerHat;
double d1 = 0.0;
double height1 = itr->height();
// // überspringen bis _innerRadius erreicht ist !
// while( ++itr != losProfile.end())
// {
// double d2 = itr->dist(); // *EarthRadius;
// double height2 = itr->height();
// if( d2 >= m_internal->m_innerRadius)
// {
// height1 = height1 + (m_internal->m_innerRadius - d1) * ( height2 - height1)/(d2 - d1);
// d1 = m_internal->m_innerRadius;
// --itr;
// break;
// }
// d1 = d2;
// height1 = height2;
// }
// bool visible = true;
// SightMode mode = START;
d1 = itr->dist();
double curM = (height1 - centerHeight ) / d1; // bestimmende Steigung
// sBeam.add( SightEntry( d1, START) );
while (++itr != losProfile.end())
{
double d2 = itr->dist(); // *EarthRadius;
double height2 = itr->height();
if (std::isnan(height2)) // Undefined
{
return LineOfSight::LOS_UNDEFINED;
}
double hObj = (height2 + objHat - centerHeight );
double objM = hObj / d2; // Steigung bezogen auf Objekte( Objekte verdecken nicht !)
LineOfSight newMode = ((objM >= curM)) ? LineOfSight::LOS_VISIBLE : LineOfSight::LOS_INVISIBLE;
if (newMode == LineOfSight::LOS_INVISIBLE)
return LineOfSight::LOS_INVISIBLE;
// if (newMode != mode)
// {
// if ( (mode != LineOfSight::LOS_UNDEFINED) && (d2 != d1) )
// {
// // Korrekturwert für Distanz berechnen, unter Berücksichtigung der Objekthoehe !!
// double localM = (height2 - height1) /(d2 - d1);
// // localM == curM kann nicht auftreten, da newMode != mode
// double dCorr = (hObj - (curM * d2)) / (localM - curM);
// if (dCorr < 0.0)
// dCorr = 0.0;
// dCorr = d2 - dCorr;
// if (dCorr < d1)
// dCorr = d1;
// sBeam.add( SightEntry( dCorr, mode) );
// }
// mode = newMode;
// }
double terrM = (height2 - centerHeight) / d2; // Steigung bezogen auf Gelaende
if (terrM >= curM)
{
curM = terrM; // neue bestimmende Steigung
}
// height1 = height2;
// d1 = d2;
} // while
return LineOfSight::LOS_VISIBLE;
}
ElevationMapManager::LineOfSight ElevationMapManager::lineOfSightCalculation(const LtsUtils::QPoint3D &startPointGeo_absZ,
const LtsUtils::QPoint3D &endPointGeo_absZ)
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START" << ", startPointGeo_absZ = " << startPointGeo_absZ << ", endPointGeo_absZ = " << endPointGeo_absZ;
if (!isValid())
{
ERROR_LOG(m_logger) << LOGFUNCTION << " Error, LtsElevationManager not valid";
return LineOfSight::LOS_ERROR;
}
// 1st: a) centerHat / startHat:
const double startHat = startPointGeo_absZ.z() - this->elevation(startPointGeo_absZ.x(), startPointGeo_absZ.y(), -10000);
if (startHat < 0)
{
ERROR_LOG(m_logger) << LOGFUNCTION << " Error, startHat is negativ";
return LineOfSight::LOS_ERROR;
}
// 1st: b) endHat / objHat:
const double endHat = endPointGeo_absZ.z() - this->elevation(endPointGeo_absZ.x(), endPointGeo_absZ.y(), -10000);
if (endHat < 0)
{
ERROR_LOG(m_logger) << LOGFUNCTION << " Error, objHat is negativ"
<< ", startPointGeo_absZ=" << startPointGeo_absZ
<< ", endPointGeo_absZ=" << endPointGeo_absZ;
return LineOfSight::LOS_ERROR;
}
SituationMap::ElevationMapManager::LineOfSight eleMapMgrResult = lineOfSightCalculation(startPointGeo_absZ.toPointF(), startHat,
endPointGeo_absZ.toPointF(), endHat);
TRACE_LOG(m_logger) << LOGFUNCTION << " END" << ", startPointGeo = " << startPointGeo_absZ << ", endPointGeo = " << endPointGeo_absZ
<< " -> LineOfSight " << static_cast<int>(eleMapMgrResult);
return eleMapMgrResult;
}
ElevationMapManager::LineOfSight ElevationMapManager::lineOfSightCalculation(const QPointF &startPointGeo, double startHat,
const QPointF &endPointGeo, double endHat)
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START"
<< ", startPointGeo = " << startPointGeo << ", startHat = " << startHat
<< ", endPointGeo = " << endPointGeo << ", endHat = " << endHat ;
if (!isValid())
{
ERROR_LOG(m_logger) << LOGFUNCTION << " Error, LtsElevationManager not valid";
return LineOfSight::LOS_ERROR;
}
if (startHat < 0)
{
ERROR_LOG(m_logger) << LOGFUNCTION << " Error, startHat is negativ";
return LineOfSight::LOS_ERROR;
}
if (endHat < 0)
{
ERROR_LOG(m_logger) << LOGFUNCTION << " Error, endHat is negativ";
return LineOfSight::LOS_ERROR;
}
// 1st: create LOSVector:
SituationMap::LOSVector losProfile;
LOSProfile(startPointGeo, endPointGeo, losProfile);
if (losProfile.size() < 2)
{
ERROR_LOG(m_logger) << LOGFUNCTION << " Error, losProfile not valid"
<< ", losProfile.size = " << losProfile.size();
return LineOfSight::LOS_ERROR;
}
// 2nd: lineOfSightCalculation
SituationMap::ElevationMapManager::LineOfSight eleMapMgrResult = this->lineOfSightCalculation(losProfile, startHat, endHat);
TRACE_LOG(m_logger) << LOGFUNCTION << " END"
<< ", startPointGeo = " << startPointGeo << ", startHat = " << startHat
<< ", endPointGeo = " << endPointGeo << ", endPointHat = " << endHat
<< " -> LineOfSight " << static_cast<int>(eleMapMgrResult);
return eleMapMgrResult;
}
} // SituationMap
@@ -0,0 +1,84 @@
/**
* @RDE_HEADER@
* Manage and combine several ElevationMap
* $Id$
* $HeadURL$
* $Author$
*/
#pragma once
#include "elevation_map_helper.h"
#include <ElevationMap/situationmapelevationmap_export.h>
#include <Common/situation_map_logging.h>
#include <QObject>
namespace SituationMap
{
// forward:
class ElevationMap;
//------------------------------------------------------------------------------------------------------------
// ElevationMapManager
//------------------------------------------------------------------------------------------------------------
class SITUATIONMAPELEVATIONMAP_EXPORT ElevationMapManager final : public QObject
{
using BaseClass = QObject;
public:
ElevationMapManager();
ElevationMapManager(const ElevationMapManager &) = delete;
ElevationMapManager &operator = (const ElevationMapManager &) = delete;
~ElevationMapManager();
int init(const QStringList &filePaths_rough_to_fine);
void cleanupCurrentContent();
bool waitForInitialization();
bool isValid() const;
double elevation(double lon_rad, double lat_rad, bool *bIsCovered = nullptr) const;
double elevation(double lon_rad, double lat_rad, double defaultValue, bool *bIsCovered = nullptr) const;
// united extent of seperate files:
QRectF simpleUnitedExtent() const;
bool contains(const QPointF &point) const;
// Line of Sight Profile: berücksichtigt Erdkrümmung und WGS84 Erdellipsoid
void LOSProfile(const QPointF &start, const QPointF &end, LOSVector &losProfile);
/*! \name Line of Sight */
//@{
enum class LineOfSight { LOS_UNDEFINED,
LOS_ERROR,
LOS_VISIBLE,
LOS_INVISIBLE };
LineOfSight lineOfSightCalculation(const LOSVector &losProfile,
double centerHat,
double objHat) const;
LineOfSight lineOfSightCalculation(const LtsUtils::QPoint3D &startPointGeo_absZ,
const LtsUtils::QPoint3D &endPointGeo_absZ);
LineOfSight lineOfSightCalculation(const QPointF &startPointGeo, double startHat,
const QPointF &endPointGeo, double endHat);
//@}
protected:
class Private;
Private *d;
LOGGER_T m_logger; ///< Interface of the Logging-Facility
}; // ElevationMapManager
} // SituationMap
@@ -0,0 +1,229 @@
/**
* @RDE_HEADER@
* LineOfSightCalculation (L400)
* $Id$
* $HeadURL$
* $Author$
* Created by N. Thoden, NLE, 06.04.2020
*/
#include "line_of_sight_calculation.h"
#include "elevation_map.h"
#include <LtsCoordTrans/lts_coord_trans.h>
//
// ESRI / ARCGIS
// if you link libEsriCommonQt, you propably have to add ArcGISQt5Hack AND: # make sure it's loaded *before* libEsriCommonQt.so
// this may effect all executables linking situationMapElevationMap. CHECK!
//
// Qt
#include <QTimer>
#include <QElapsedTimer>
// 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(); }
namespace SituationMap
{
LineOfSightCalculation::LineOfSightCalculation(ElevationMap *elevationMap)
: m_logger(getLogger("LineOfSightCalculation"))
, m_elevationMap(elevationMap)
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
}
LineOfSightCalculation::~LineOfSightCalculation()
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
}
bool LineOfSightCalculation::calculate(ObserverTargetData &observerTarget, unsigned int sampleIntervallMeter)
{
// same checks:
if (!m_elevationMap || !m_elevationMap->isValid())
{
ERROR_LOG(m_logger) << LOGFUNCTION << " no elevationMap configured or not valid";
observerTarget.setLineOfSightVisibility(LineOfSightVisibility::Undefined);
return false;
}
if (!observerTarget.isObserverAndTargetValid())
{
ERROR_LOG(m_logger) << LOGFUNCTION << " observer and/or target not valid";
observerTarget.setLineOfSightVisibility(LineOfSightVisibility::Undefined);
return false;
}
if (observerTarget.observer().toPointF() == observerTarget.target().toPointF())
{
ERROR_LOG(m_logger) << LOGFUNCTION << " observer and target are identical regarding x and y";
observerTarget.setLineOfSightVisibility(LineOfSightVisibility::Undefined);
return false;
}
bool success = true;
QElapsedTimer timer;
if (m_logger.isInfoEnabled())
timer.start();
//
//
// consider using ElevationMap::LOSProfile
//
//
// determine how many iterations we need:
double distanceInMeter = distanceMeter(observerTarget.observer(),
observerTarget.target());
//ERROR_LOG(m_logger) << LOGFUNCTION << " distanceMeter = " << distanceInMeter;
int maxNumberOfIterations = distanceInMeter / sampleIntervallMeter; // check visibility every ~5m
// maxNumberOfIterations = 1000000;
if (maxNumberOfIterations<=0)
maxNumberOfIterations = 5;
double distanceFromObserverToTargetAsFraction = 0;
int i = 0;
while (i<maxNumberOfIterations)
{
++i;
/*
z-axis (Elevation) pCandidate
|---------- /
| ----------
| ----------
| ----------
| ----------
|------------------------------------------------------------ xy-axis
^ ^ ^ ^ ^ ^ ^ ^ samples (every ~5meters)
^ ^
ObserverPosition TargetPosition
On every sample position, check if directLineElevation (maximum) is
obstructed by elevationMapElevation (real value)
if (elevationMapElevation>directLineElevation) -> obstructed/break
*/
distanceFromObserverToTargetAsFraction = (double)i/maxNumberOfIterations;
const Point3D &pCandidate = movePoint(observerTarget.observer(),
observerTarget.target(),
distanceFromObserverToTargetAsFraction);
// {
// static Point3D oldCandidate;
// double distanceInMeter = distanceMeter(pCandidate, oldCandidate);
// ERROR_LOG(m_logger) << LOGFUNCTION << " distanceMeter = " << distanceInMeter;
// oldCandidate = pCandidate;
// }
// z is the elevation of a *direct* line:
const double directLineElevation = pCandidate.z();
const double elevationMapElevation = m_elevationMap->elevation(pCandidate.x(), pCandidate.y());
// Coord covered by elevationMap?
if (elevationMapElevation <= std::numeric_limits<int>::min())
{
ERROR_LOG(m_logger) << LOGFUNCTION << " Error, coordinate not covered be elevationData. Finish this LineOfSightCalculation."
<< " i=" << i
<< ", pCandidate = " << pCandidate;
// write back data
observerTarget.setObstacle(pCandidate);
observerTarget.setLineOfSightVisibility(LineOfSightVisibility::Undefined);
// finish the loop:
success = false;
break;
}
// analyze / check visibility:
if (elevationMapElevation>directLineElevation)
{
DEBUG_LOG(m_logger) << LOGFUNCTION << " i=" << i
<< " break - pCandidate = " << pCandidate
<< " -> directLineElevation = " << directLineElevation
<< " -> elevationMapElevation = " << elevationMapElevation;
// write result
observerTarget.setObstacle(pCandidate);
observerTarget.setLineOfSightVisibility(LineOfSightVisibility::Obstructed);
// finish the loop:
break;
} else {
observerTarget.setLineOfSightVisibility(LineOfSightVisibility::Visible);
}
} // while
if (m_logger.isInfoEnabled())
{
INFO_LOG(m_logger) << LOGFUNCTION << " END"
<< ", success = " << success
<< ", " << i << " of "<< maxNumberOfIterations << " iterations"
<< ", distanceFromObserverToTargetAsFraction = " << distanceFromObserverToTargetAsFraction
<< ", pCandidate=" << observerTarget.obstacle()
<< ", lineOfSightVisibility=" << static_cast<int>(observerTarget.lineOfSightVisibility())
<< ", took [ms] " << timer.elapsed();
}
TRACE_LOG(m_logger) << LOGFUNCTION << " END" << ", success = " << success;
return success;
}
Point3D LineOfSightCalculation::movePoint(const Point3D &oberserver, const Point3D &target, double distanceFromObserverToTargetAsFraction) // 0.0 ... 1.0
{
// positions are lon/lat @ rad.
// for evaluation we do that very simple:
// strange, but: using Point3D seems to be faster then using QPointF
const Point3D &dist = target - oberserver;
const Point3D &point = oberserver + (dist * distanceFromObserverToTargetAsFraction);
return point;
}
double LineOfSightCalculation::distanceMeter(const Point3D &oberserver, const Point3D &target)
{
// Try to avoid the dependency to ESRI (keep ArcGISQt5Hack in mind)
// And good accuracy is not required here
// use LtsCoordTrans::distance
// Esri::ArcGISRuntime::Point geom1(oberserver.x()*rad2deg(), oberserver.y()*rad2deg(), oberserver.z(), Esri::ArcGISRuntime::SpatialReference::wgs84());
// Esri::ArcGISRuntime::Point geom2(target.x() *rad2deg(), target.y() *rad2deg(), target.z() , Esri::ArcGISRuntime::SpatialReference::wgs84());
// Esri::ArcGISRuntime::GeometryEngine *gEngine = Esri::ArcGISRuntime::GeometryEngine::instance();
// Esri::ArcGISRuntime::GeodeticDistanceResult geodeticDistanceResult = gEngine->distanceGeodetic(geom1, geom2,
// Esri::ArcGISRuntime::LinearUnitId::Meters,
// Esri::ArcGISRuntime::AngularUnitId::Degrees,
// Esri::ArcGISRuntime::GeodeticCurveType::Geodesic);
// double distance = geodeticDistanceResult.distance();
double distance = LtsCoordTrans::distance(oberserver.toPointF(), target.toPointF());
return distance;
}
} // namespace SituationMap
@@ -0,0 +1,92 @@
/**
* @RDE_HEADER@
* LineOfSightCalculation (L400)
* $Id$
* $HeadURL$
* $Author$
* Created by N. Thoden, NLE, 06.04.2020
*/
#pragma once
#include <ElevationMap/situationmapelevationmap_export.h>
#include <Common/situation_map_logging.h>
#include <LtsUtilsCore/qpoint3d.h>
using Point3D = LtsUtils::QPoint3D; ///< x = lon ; y = lat , z = elevation/altitude in meters
namespace SituationMap
{
// forward
class ElevationMap;
//----------------------------------------------------------------------------
/*!
\brief Define visibility.
*/
enum class LineOfSightVisibility
{
Undefined, /*!< no data at that location? */
Visible,
Obstructed
};
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
class SITUATIONMAPELEVATIONMAP_EXPORT ObserverTargetData
{
public:
explicit ObserverTargetData(const Point3D &oberserver = Point3D(), // z-value: incl. observerHeight
const Point3D &target = Point3D()) // z-value: incl. targetHeight
: m_oberserver(oberserver)
, m_target(target)
{}
const Point3D &observer() const { return m_oberserver; }
const Point3D &target() const { return m_target; }
const Point3D &obstacle() const { return m_obstacle; }
void setObstacle(const Point3D &obstacle) { m_obstacle = obstacle; }
LineOfSightVisibility lineOfSightVisibility() const { return m_visible; }
void setLineOfSightVisibility(const LineOfSightVisibility &visible) { m_visible = visible; }
bool isObserverAndTargetValid() const { return (!m_oberserver.isNull()) && (!m_target.isNull()); }
private:
Point3D m_oberserver;
Point3D m_target;
Point3D m_obstacle;
LineOfSightVisibility m_visible = LineOfSightVisibility::Undefined;
};
//----------------------------------------------------------------------------
class SITUATIONMAPELEVATIONMAP_EXPORT LineOfSightCalculation
{
public:
explicit LineOfSightCalculation(ElevationMap *elevationMap);
~LineOfSightCalculation();
bool calculate(ObserverTargetData &observerTarget, unsigned int sampleIntervallMeter = 5);
protected:
static Point3D movePoint(const Point3D &oberserver,
const Point3D &target,
double distanceFromObserverToTargetAsFraction);
static double distanceMeter(const Point3D &oberserver,
const Point3D &target);
private:
LOGGER_T m_logger; ///< Interface of the Logging-Facility
ElevationMap *m_elevationMap = nullptr;
ObserverTargetData m_observerTarget;
};
} // namespace SituationMap