Files
qt5-legatus-legacy/Lts/Viewer/SituationMap/MathUtils/geometry_engine.cpp
T
2026-08-13 18:33:10 +02:00

420 lines
13 KiB
C++
Executable File

/**
**
** @RDE_HEADER@
** Implementation of 2d matrix operations
** $Id$
** $HeadURL$
** $Author$
**
** -----------------------------------------------------------------------------------------------
**
** Hardware-Environment: Intel-PC
** Software-Environment: Windows 7, Qt 5.4. ArcGIS 10.2.5
**
** Language ........: C++ 11
**
** -----------------------------------------------------------------------------------------------
**
** Author ...........: Jörg Turla, SPD G, Tel. 3132
**
** -----------------------------------------------------------------------------------------------
**
** Notes .........:
**
** 22.06.2016, JT : First edition for use with Qt5
**
** -----------------------------------------------------------------------------------------------
*/
#include "geometry_engine.h"
#include <Map/coord_translation_functor.h>
#include <Map/map.h>
#include <MapImpl/map_impl.h>
#include <MapImpl/abstract_map_object_impl.h>
#include <MathUtils/clipper.hpp>
// ESRI
#include <arcgis/Geometry.h>
#include <arcgis/GeometryEngine.h>
//#include <arcgis/Line.h>
#include <arcgis/Polyline.h>
#include <arcgis/PolylineBuilder.h>
#include <arcgis/Polygon.h>
#include <arcgis/PolygonBuilder.h>
#include <QPointF>
#include <QVector>
#include <QDebug>
namespace SituationMap { namespace MathUtils
{
//
// Line
//
GeometryEngine::Geometry *GeometryEngine::createLine(Map *map, const QPointF &p1, const QPointF &p2)
{
QVector<QPointF> vertices;
vertices << p1 << p2;
return SituationMap::MathUtils::GeometryEngine::createPolyline(map, vertices);
} // GeometryEngine::Geometry *GeometryEngine::createLine( Map * map, QPointF p1, QPointF p2 )
template<class T>
inline GeometryEngine::Geometry * createPolyline(Map * map, const T & verticies)
{
MapImpl * mapImpl = dynamic_cast<MapImpl*>(map);
if (! mapImpl)
return nullptr;
SituationMap::CoordTranslationFunctor *mapCtFunctor = map->coordTranslationFunctor();
if (! mapCtFunctor)
return nullptr;
Esri::ArcGISRuntime::PolylineBuilder builder ( mapImpl->getEsriSpatialReference() );
for (auto pit = verticies.cbegin(); pit != verticies.cend(); ++pit)
{
const QPointF p(mapCtFunctor->geographicToMap(*pit));
builder.addPoint(p.x(),p.y());
}
Esri::ArcGISRuntime::Polyline * esriPolyline = new Esri::ArcGISRuntime::Polyline( builder.toGeometry() );
return esriPolyline;
} // GeometryEngine::Geometry * createPolyline(Map * map, const T & verticies)
GeometryEngine::Geometry *GeometryEngine::createPolyline(Map *map, const QVector<QPointF> &vertices )
{
return SituationMap::MathUtils::createPolyline<QVector<QPointF>>(map, vertices);
} // GeometryEngine::Geometry * GeometryEngine::createPolyline( Map * map, const QVector<QPointF> & verticies )
GeometryEngine::Geometry *GeometryEngine::createPolyline(Map *map, const QList<QPointF> &vertices)
{
return SituationMap::MathUtils::createPolyline<QList<QPointF>>(map, vertices);
} // GeometryEngine::Geometry * GeometryEngine::createPolyline( Map * map, const QVector<QPointF> & verticies )
//
// Polygon
//
template<class T>
inline GeometryEngine::Geometry *createPolygon(Map *map, const T &verticies)
{
MapImpl * mapImpl = dynamic_cast<MapImpl*>(map);
if (! mapImpl) return nullptr;
SituationMap::CoordTranslationFunctor * mapCtFunctor = map->coordTranslationFunctor();
if (! mapCtFunctor) return nullptr;
Esri::ArcGISRuntime::PolygonBuilder builder( mapImpl->getEsriSpatialReference() );
for (auto pit = verticies.cbegin(); pit != verticies.cend(); ++pit )
{
const QPointF p(mapCtFunctor->geographicToMap(*pit));
builder.addPoint(p.x(),p.y());
}
Esri::ArcGISRuntime::Polygon * esriPolygon = new Esri::ArcGISRuntime::Polygon( builder.toGeometry() );
return esriPolygon;
} // GeometryEngine::Geometry * GeometryEngine::createPolygon( Map * map, const QVector<QPointF> & verticies )
GeometryEngine::Geometry * GeometryEngine::createPolygon(Map * map, const QVector<QPointF> & vertices )
{
return SituationMap::MathUtils::createPolygon<QVector<QPointF>>( map, vertices );
} // GeometryEngine::Geometry * GeometryEngine::createPolygon( Map * map, const QVector<QPointF> & verticies )
GeometryEngine::Geometry * GeometryEngine::createPolygon(Map * map, const QList<QPointF> & vertices )
{
return SituationMap::MathUtils::createPolygon<QList<QPointF>>( map, vertices );
} // GeometryEngine::Geometry * GeometryEngine::createPolygon( Map * map, const QVector<QPointF> & verticies )
//
// cleanup etc.
//
void GeometryEngine::deleteGeometry(Geometry *geom)
{
delete geom;
} // void GeometryEngine::deleteGeoemtry( Geometry * geom )
bool GeometryEngine::contains( AbstractMapObject *mapObj, const Geometry &hull)
{
if (nullptr == mapObj) return false;
return mapObj->geometryIsInHull( hull );
} //bool GeometryEngine::contains( AbstractMapObject * mapObj, const Geometry & hull )
bool GeometryEngine::contains(const Geometry &geom, const Geometry &hull)
{
if (geom.isEmpty() || hull.isEmpty())
return false;
return Esri::ArcGISRuntime::GeometryEngine::contains(hull, geom);
} // bool GeometryEngine::contains(const Geometry & geom, const Geometry & hull)
bool GeometryEngine::intersect( AbstractMapObject *mapObj, const Geometry &intersector)
{
if (! mapObj)
return false;
return mapObj->geometryIntersect(intersector);
} // bool GeometryEngine::intersect( const Geometry & inputGeom, const Geometry & intersector )
bool GeometryEngine::intersect(const Geometry &inputGeom, const Geometry &intersector)
{
if (inputGeom.isEmpty() || intersector.isEmpty())
return false;
#ifdef DEBUGGERTEST
if (inputGeom.type() == EsriRuntimeQt::GeometryType::Polyline)
{
const EsriRuntimeQt::Polyline & l = inputGeom;
int numPoints = l.pointCount();
for (int i = 0; i < numPoints; ++i)
{
QPointF p = l.qxy(i);
__asm int 3
}
}
if (intersector.type() == EsriRuntimeQt::GeometryType::Line)
{
const EsriRuntimeQt::Line & l = intersector;
double startx = l.startX();
double starty = l.startY();
double endx = l.endX();
double endy = l.endY();
__asm int 3
}
#endif
return Esri::ArcGISRuntime::GeometryEngine::crosses(inputGeom, intersector);
} // bool GeometryEngine::intersect( const Geometry & inputGeom, const Geometry & intersector )
QVector<QPointF> GeometryEngine::parallelTranslation(SituationMap::CoordTranslationFunctor *mapCtFunctor, const QVector<QPointF> &line, double distance)
{
/* ~ first lokal functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
auto length = [](const QPointF &v) -> double { return sqrt(v.x()*v.x() + v.y()*v.y()); };
auto normalize = [](const QPointF &v) -> QPointF { return v / sqrt(v.x()*v.x() + v.y()*v.y()); };
auto intersection = [](const QPointF &p1, const QPointF &r1, const QPointF &p2, const QPointF &r2) -> QPointF
{
double f = r2.y()*r1.x() - r1.y()*r2.x();
double a = (p1.x() + r1.x()) * p1.y() - p1.x() * (p1.y() + r1.y());
double b = (p2.x() + r2.x()) * p2.y() - p2.x() * (p2.y() + r2.y());
double sx = (r2.x() * a - r1.x() * b) / f;
double sy = (-r1.y() * b + r2.y() * a) / f;
return QPointF(sx, sy);
}; // intersection
auto orientation = [](const QPointF &p, const QPointF &q, const QPointF &r) -> int
{
// See https://www.geeksforgeeks.org/orientation-3-ordered-points/
// for details of below formula.
double f = (q.y() - p.y()) * (r.x() - q.x()) - (q.x() - p.x()) * (r.y() - q.y());
if (qFuzzyIsNull(f)) return 0; // colinear
return (f > 0) ? 1 : 2; // clock or counterclock wise
}; // orientation
auto testIntersection = [&orientation, &intersection, &normalize](const QPointF &p1, const QPointF &q1, const QPointF &p2, const QPointF &q2) -> bool
{
// Find the four orientations needed for general and
// special cases
int o1 = orientation(p1, q1, p2);
int o2 = orientation(p1, q1, q2);
int o3 = orientation(p2, q2, p1);
int o4 = orientation(p2, q2, q1);
// General case
if (o1 != o2 && o3 != o4)
{
const QPointF & n1 = q1 - p1;
const QPointF & n2 = q2 - p2;
const QPointF & s = intersection(p1, normalize(n1), p2, normalize(n2));
double f1 = (!qFuzzyIsNull(n1.x())) ? (s.x() - p1.x()) / n1.x() : (s.y() - p1.y()) / n1.y();
double f2 = (!qFuzzyIsNull(n2.x())) ? (s.x() - p2.x()) / n2.x() : (s.y() - p2.y()) / n2.y();
return ((f1 >= 0) && (f1 <= 1.0) && (f2 >= 0) && (f2 <= 1.0));
}
return false; // Doesn't fall in any of the above cases
}; // testIntersection
/* ~ now let's do the job ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
if (line.size() < 2)
{
// Nothing to do --> return input line
return line;
}
QVector<QPointF> transformedLine;
transformedLine.reserve(line.size());
for (auto &point : qAsConst(line))
{
// Let's transform the coordinates to maps coordinate system ...
transformedLine << mapCtFunctor->geographicToMap(point);
}
struct LineSegment
{
QPointF m_p1;
QPointF m_p2;
QPointF m_t;
LineSegment() = default;
LineSegment(const QPointF &p1, const QPointF &p2, const QPointF &t)
: m_p1(p1), m_p2(p2), m_t(t)
{}
};
// build single line segments to translate along it's normal
QVector<LineSegment> multiSegmLine;
multiSegmLine.reserve(transformedLine.size() - 1);
for (int i = 1; i < transformedLine.size(); ++i)
{
const QPointF & p1 = transformedLine[i - 1];
const QPointF & p2 = transformedLine[i];
const QPointF & t = normalize(p2 - p1);
QPointF n = QPointF(t.y()*distance, -t.x()*distance);
multiSegmLine << LineSegment(p1 + n, p2 + n, t);
}
transformedLine.clear();
transformedLine << multiSegmLine.first().m_p1;
for (int i = 1; i < multiSegmLine.size(); ++i)
{
const LineSegment & l1 = multiSegmLine[i - 1];
const LineSegment & l2 = multiSegmLine[i];
const QPointF & dt = l1.m_t - l2.m_t;
transformedLine << ((qFuzzyIsNull(dt.x()) && qFuzzyIsNull(dt.y())) ? l1.m_p2 : intersection(l1.m_p2, l1.m_t, l2.m_p1, l2.m_t));
}
transformedLine << multiSegmLine.last().m_p2;
// check result
for (int i = 1; i < transformedLine.size(); ++i)
{
const QPointF & p1 = transformedLine[i - 1];
const QPointF & p2 = transformedLine[i];
if (length(p2 - p1) < distance)
{
// failure segment is to short relative to distance
return QVector<QPointF>();
}
for (int j = 1; j < transformedLine.size(); ++j)
{
if (j == i) continue;
const QPointF & g1 = transformedLine[j-1];
const QPointF & g2 = transformedLine[j];
if (testIntersection(p1, p2, g1, g2))
{
// failure resulting line has self intersections
return QVector<QPointF>();
}
}
}
QVector<QPointF> pLine;
// Transform coordinates back to geographic ...
pLine.reserve(transformedLine.size());
for (auto &p : qAsConst(transformedLine))
{
pLine << mapCtFunctor->mapToGeographic(p);
}
return pLine;
}
// QVector<QPointF> GeometryEngine::parallelTranslation(SituationMap::CoordTranslationFunctor *mapCtFunctor, const QVector<QPointF> &line, double distance)
QVector<Point3D> GeometryEngine::buffer(AbstractMapObject *mapObj, double distance)
{
if (!mapObj)
return QVector<Point3D>();
AbstractMapObjectImpl *mapObjImpl = dynamic_cast<AbstractMapObjectImpl*>(mapObj);
if (!mapObjImpl)
return QVector<Point3D>();
const Esri::ArcGISRuntime::Geometry &esriGeometry = mapObjImpl->getEsriGeometry();
// Esri::ArcGISRuntime::GeometryEngine::buffer -> works for 2D
// Esri::ArcGISRuntime::GeometryEngine::bufferGeodetic -> works for 2D and 3D!?
const Esri::ArcGISRuntime::Polygon polyline = Esri::ArcGISRuntime::GeometryEngine::bufferGeodetic(esriGeometry,
distance,
Esri::ArcGISRuntime::LinearUnitId::Meters,
1,
Esri::ArcGISRuntime::GeodeticCurveType::Geodesic);
if (polyline.parts().isEmpty())
return QVector<Point3D>();
const Esri::ArcGISRuntime::ImmutablePart &immutablePart = polyline.parts().part(0);
const int size = immutablePart.pointCount();
QVector<Point3D> resultVec;
resultVec.reserve(size);
for (int i = 0; i < size; ++i)
{
const Esri::ArcGISRuntime::Point &esriP = immutablePart.point(i);
resultVec.append(Point3D(esriP.x(), esriP.y(), esriP.z()));
}
// qCritical() << " resultVec = " << resultVec.size();
// qCritical() << " res = " << polyline.toJson();
return resultVec;
}
} } // namespace SituationMap::MathUtils