415 lines
13 KiB
C++
Executable File
415 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 <Geometry.h>
|
|
#include <GeometryEngine.h>
|
|
//#include <Line.h>
|
|
#include <Polyline.h>
|
|
#include <PolylineBuilder.h>
|
|
#include <Polygon.h>
|
|
#include <PolygonBuilder.h>
|
|
#include <Point.h>
|
|
#include <ImmutablePartCollection.h>
|
|
#include <ImmutablePart.h>
|
|
#include <LinearUnit.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 )
|
|
|
|
|
|
//
|
|
// 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 )
|
|
|
|
|
|
//
|
|
// 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 : std::as_const(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 : std::as_const(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::LinearUnit linearUnit(Esri::ArcGISRuntime::LinearUnitId::Meters);
|
|
const Esri::ArcGISRuntime::Polygon polyline = Esri::ArcGISRuntime::GeometryEngine::bufferGeodetic(esriGeometry,
|
|
distance,
|
|
linearUnit,
|
|
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
|
|
|