/** ** ** @RDE_HEADER@ ** Definition of rubberband class to define multi point feature geometry with an additional width coordinate ** $Id$ ** $HeadURL$ ** $Author$ ** ** ----------------------------------------------------------------------------------------------- ** ** Hardware-Environment: Intel-PC ** Software-Environment: Linux, Windows 7, Qt 5.7. ArcGISRuntimeQt 100 ** ** Language ........: C++ 11 ** ** ----------------------------------------------------------------------------------------------- ** ** Author ...........: Jörg Turla, SPD G, Tel. 3132 ** ** ----------------------------------------------------------------------------------------------- ** ** Notes .........: ** ** 7.12.2016, JT : First edition for use with Qt5 ** ** ----------------------------------------------------------------------------------------------- */ #include "rubberband_polyline_width.h" #include "rubberband_point_handle.h" #include "rubberband_internal.h" #include "rubberband_base_internal.h" #include #include namespace SituationMap { namespace RB { struct RubberbandPolylineWidth::InternalData { SituationMap::RefPointer _widthItem; }; /* RubberbandPolylineWidth::InternalData*/ RubberbandPolylineWidth::RubberbandPolylineWidth( SituationMap::Map * map, QObject * parent ) : RubberbandMultiPointFeature(map,true,parent), _internal( new InternalData() ) { } /* RubberbandPolylineWidth::RubberbandPolylineWidth */ RubberbandPolylineWidth::~RubberbandPolylineWidth() { delete _internal; } /* RubberbandPolylineWidth::~RubberbandPolylineWidth */ void RubberbandPolylineWidth::buildWidthItem(const QPointF & mapPos1, const QPointF & mapPos2) const { double dx = mapPos2.x() - mapPos1.x(); double dy = mapPos2.y() - mapPos1.y(); QPointF handleMapPos = mapPos1 + QPointF(dx,dy)*0.35 + QPointF(dy,dx)*0.35; setUpWidthItem(handleMapPos); } /* RubberbandPolylineWidth::buildWidthItem */ void RubberbandPolylineWidth::setUpWidthItem(const QPointF & mapPos) const { RubberbandHandle::RubberbandCoordinate coordinate( Rubberband::_internal->_mapView, convert(mapPos,Rubberband::_internal->_mapView->map()->spatialReference()) ); _internal->_widthItem = new RubberbandPointHandle(Rubberband::_internal->_mapView, Rubberband::_internal->_mapScene, coordinate._sceneCoordinate); } /* RubberbandPolylineWidth::setUpWidthItem */ QVector RubberbandPolylineWidth::coordinateVector( CoordinateSystem cs ) const { QVector polylineCoords = RubberbandMultiPointFeature::coordinateVector(cs); if (polylineCoords.size() > 1) { if (_internal->_widthItem == nullptr) { RubberbandPointHandle * pointHandle1Ptr = static_cast(Rubberband::_internal->_handleList[0].get()); RubberbandPointHandle * pointHandle2Ptr = static_cast(Rubberband::_internal->_handleList[1].get()); Esri::ArcGISRuntime::Point p1 = pointHandle1Ptr->getCoordinate()._mapCoordinate; Esri::ArcGISRuntime::Point p2 = pointHandle2Ptr->getCoordinate()._mapCoordinate; buildWidthItem( QPointF(p1.x(),p1.y()), QPointF(p2.x(),p2.y()) ); } polylineCoords += projectCoordinateFromMap(cs, _internal->_widthItem->getCoordinate()._mapCoordinate); } return polylineCoords; } /* RubberbandPolylineWidth::coordinateVector */ QPointF RubberbandPolylineWidth::singleCoordinate( unsigned int idx, CoordinateSystem cs ) const { if (static_cast(Rubberband::_internal->_handleList.size()) == idx) { return projectCoordinateFromMap(cs, _internal->_widthItem->getCoordinate()._mapCoordinate); } return RubberbandMultiPointFeature::singleCoordinate(idx, cs); } /* RubberbandPolylineWidth::singleCoordinate */ void RubberbandPolylineWidth::setCoordinateVector( const QVector & coordinateVector, CoordinateSystem cs ) { if (coordinateVector.size() > 2) { QVector lineCoords = coordinateVector; setUpWidthItem( projectCoordinateToMap(cs, lineCoords.last()) ); lineCoords.removeLast(); RubberbandMultiPointFeature::setCoordinateVector(lineCoords, cs); return; } if (coordinateVector.size() == 2) { const QPointF & mapP1 = projectCoordinateToMap(cs, coordinateVector[0]); const QPointF & mapP2 = projectCoordinateToMap(cs, coordinateVector[1]); buildWidthItem(mapP1, mapP2); } RubberbandMultiPointFeature::setCoordinateVector(coordinateVector,cs); } /* RubberbandPolylineWidth::setCoordinateVector */ bool RubberbandPolylineWidth::selectAction( AbstractEventDispatcher * causer, int x, int y ) { if (rubberbandIsAlive() && (nullptr != _internal->_widthItem.get())) { _internal->_widthItem->setSelection(false); QPointF scenePos(pixel2ScenePosition(x, y)); if (_baseInternal->buttonProxyActive(scenePos)) return false; if (_internal->_widthItem->isPositionInside(scenePos)) { _internal->_widthItem->setSelection(); _baseInternal->_lastClickPosition = scenePos; update(); return true; } } return RubberbandMultiPointFeature::selectAction(causer,x,y); } /* RubberbandPolylineWidth::selectAction */ bool RubberbandPolylineWidth::setCoordinateAction( AbstractEventDispatcher * causer, int x, int y ) { if (rubberbandIsAlive() && (nullptr != _internal->_widthItem.get())) { if (_internal->_widthItem->isSelected()) { QPointF scenePos(pixel2ScenePosition(x, y)); QPointF deltaMove = scenePos - _baseInternal->_lastClickPosition; _internal->_widthItem->setSelection(false); _internal->_widthItem->moveCoordinate(deltaMove); update(); return true; } } return RubberbandMultiPointFeature::setCoordinateAction(causer, x, y); } /* RubberbandPolylineWidth::setCoordinateAction */ bool RubberbandPolylineWidth::movePickedAction( AbstractEventDispatcher * causer, int x, int y ) { if (rubberbandIsAlive() && (nullptr != _internal->_widthItem.get())) { if (_internal->_widthItem->isSelected()) { adjustConnectedPreviewPixmap(x, y); QPointF scenePos(pixel2ScenePosition(x, y)); QPointF deltaMove = scenePos - _baseInternal->_lastClickPosition; _baseInternal->_lastClickPosition = scenePos; _internal->_widthItem->moveCoordinate(deltaMove); update(); return false; } else { RubberbandPointHandle * handlePoint1Ptr = dynamic_cast(Rubberband::_internal->_handleList[0].get()); RubberbandPointHandle * handlePoint2Ptr = dynamic_cast(Rubberband::_internal->_handleList[1].get()); if (handlePoint1Ptr && handlePoint2Ptr) { const RubberbandDrawable::RubberbandCoordinate & loc1Start = handlePoint1Ptr->getCoordinate(); const RubberbandDrawable::RubberbandCoordinate & loc2Start = handlePoint2Ptr->getCoordinate(); bool ret = RubberbandMultiPointFeature::movePickedAction(causer, x, y); const RubberbandDrawable::RubberbandCoordinate & loc1End = handlePoint1Ptr->getCoordinate(); const RubberbandDrawable::RubberbandCoordinate & loc2End = handlePoint2Ptr->getCoordinate(); QPointF deltaMove1 = loc1End._sceneCoordinate - loc1Start._sceneCoordinate; QPointF deltaMove2 = loc2End._sceneCoordinate - loc2Start._sceneCoordinate; if ((fabs(deltaMove1.x()) >= 1) || (fabs(deltaMove1.y()) >= 1) || (fabs(deltaMove2.x()) >= 1) || (fabs(deltaMove2.y()) >= 1)) { /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * First or second point have been moved. Now recalculate the coordinate of the width handle point as follows: * The width handle "C" and the first two coordinates ("A","B") are providing a triangle: * B * / \ * / \ * /sinB \ * / cosB \ * / \ * / \ * c / \ a * / \ * / \ * / \ * / \ * / sinA sinC \ * / cosA cosC \ * A ------------------------- C * b * If the coordinates of "A" or "B" are changed, try to keep the ratio of angles and sides of the triangle constant. For this * use some trigonometric identities. * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ auto rotate = []( const QPointF & p, qreal sinPhi, qreal cosPhi ) -> QPointF { return QPointF( p.x()*cosPhi - p.y()*sinPhi, p.y()*cosPhi + p.x()*sinPhi ); }; QPointF A = loc2Start._sceneCoordinate; QPointF B = loc1Start._sceneCoordinate; QPointF C = _internal->_widthItem->getCoordinate()._sceneCoordinate; qreal a = QLineF(B,C).length(); // side length of triangle qreal b = QLineF(A,C).length(); qreal c = QLineF(A,B).length(); qreal cosA = (b*b+c*c-a*a)/(b*c*2); // cosin rule qreal cosB = (a*a+c*c-b*b)/(a*c*2); qreal cosC = (a*a+b*b-c*c)/(a*b*2); qreal sinA = sqrt(1-cosA*cosA); // pythagorean trigonometric identity qreal sinB = sqrt(1-cosB*cosB); qreal sinC = sqrt(1-cosC*cosC); // now we have all angle of the triangle, set A and B to the new coordinates ... A = loc2End._sceneCoordinate; B = loc1End._sceneCoordinate; // ... and calculate the coordinate of C with the same ratio of angles as before ... QPointF AB = B-A; // c = QLineF(QPointF(0,0),AB).length(); if (sinC != 0.0) { // ... rotate AB and scale it to the right length C = A + rotate(AB,sinA,cosA) * sinB / sinC; // the RubberbandPointHandle dosn't has a set to set scene coordinate. Using the moveCoordinate methode instead. _internal->_widthItem->moveCoordinate( C - _internal->_widthItem->getCoordinate()._sceneCoordinate ); } // else could only happen when the triangle is degenrated to a line update(); } return ret; } } } return RubberbandMultiPointFeature::movePickedAction(causer, x, y); } /* RubberbandPolylineWidth::movePickedAction */ bool RubberbandPolylineWidth::moveAction( AbstractEventDispatcher * causer, int x, int y ) { if (rubberbandIsAlive() && (nullptr != _internal->_widthItem.get())) { QPointF scenePos(pixel2ScenePosition(x, y)); _internal->_widthItem->setSelection( _internal->_widthItem->isPositionInside(scenePos) ); } return RubberbandMultiPointFeature::moveAction( causer, x, y); } /* RubberbandPolylineWidth::moveAction */ void RubberbandPolylineWidth::update() { if ((_internal->_widthItem.get() == nullptr) && (Rubberband::_internal->_handleList.size() > 1)) { RubberbandPointHandle * pointHandle1Ptr = static_cast(Rubberband::_internal->_handleList[0].get()); RubberbandPointHandle * pointHandle2Ptr = static_cast(Rubberband::_internal->_handleList[1].get()); Esri::ArcGISRuntime::Point p1 = pointHandle1Ptr->getCoordinate()._mapCoordinate; Esri::ArcGISRuntime::Point p2 = pointHandle2Ptr->getCoordinate()._mapCoordinate; buildWidthItem(QPointF(p1.x(), p1.y()), QPointF(p2.x(), p2.y())); } if (_internal->_widthItem) { Rubberband::_internal->_handleList += static_cast>(_internal->_widthItem); RubberbandMultiPointFeature::update(); Rubberband::_internal->_handleList.removeLast(); } else RubberbandMultiPointFeature::update(); } /* RubberbandPolylineWidth::update */ } } /* namespace SituationMap::RB */