/** ** ** @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 #include #include #include #include // ESRI #include #include //#include #include #include #include #include #include #include #include namespace SituationMap { namespace MathUtils { // // Line // GeometryEngine::Geometry *GeometryEngine::createLine(Map *map, const QPointF &p1, const QPointF &p2) { QVector vertices; vertices << p1 << p2; return SituationMap::MathUtils::GeometryEngine::createPolyline(map, vertices); } // GeometryEngine::Geometry *GeometryEngine::createLine( Map * map, QPointF p1, QPointF p2 ) template inline GeometryEngine::Geometry * createPolyline(Map * map, const T & verticies) { MapImpl * mapImpl = dynamic_cast(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 &vertices ) { return SituationMap::MathUtils::createPolyline>(map, vertices); } // GeometryEngine::Geometry * GeometryEngine::createPolyline( Map * map, const QVector & verticies ) GeometryEngine::Geometry *GeometryEngine::createPolyline(Map *map, const QList &vertices) { return SituationMap::MathUtils::createPolyline>(map, vertices); } // GeometryEngine::Geometry * GeometryEngine::createPolyline( Map * map, const QVector & verticies ) // // Polygon // template inline GeometryEngine::Geometry *createPolygon(Map *map, const T &verticies) { MapImpl * mapImpl = dynamic_cast(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 & verticies ) GeometryEngine::Geometry * GeometryEngine::createPolygon(Map * map, const QVector & vertices ) { return SituationMap::MathUtils::createPolygon>( map, vertices ); } // GeometryEngine::Geometry * GeometryEngine::createPolygon( Map * map, const QVector & verticies ) GeometryEngine::Geometry * GeometryEngine::createPolygon(Map * map, const QList & vertices ) { return SituationMap::MathUtils::createPolygon>( map, vertices ); } // GeometryEngine::Geometry * GeometryEngine::createPolygon( Map * map, const QVector & 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 GeometryEngine::parallelTranslation(SituationMap::CoordTranslationFunctor *mapCtFunctor, const QVector &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 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 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(); } 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(); } } } QVector pLine; // Transform coordinates back to geographic ... pLine.reserve(transformedLine.size()); for (auto &p : qAsConst(transformedLine)) { pLine << mapCtFunctor->mapToGeographic(p); } return pLine; } // QVector GeometryEngine::parallelTranslation(SituationMap::CoordTranslationFunctor *mapCtFunctor, const QVector &line, double distance) QVector GeometryEngine::buffer(AbstractMapObject *mapObj, double distance) { if (!mapObj) return QVector(); AbstractMapObjectImpl *mapObjImpl = dynamic_cast(mapObj); if (!mapObjImpl) return QVector(); 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(); const Esri::ArcGISRuntime::ImmutablePart &immutablePart = polyline.parts().part(0); const int size = immutablePart.pointCount(); QVector 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