36#include "moc_qgsmaptooleditblanksegments.cpp"
58 QgsSymbolLayerFinder(
const QString &symbolLayerId )
59 : mSymbolLayerId( symbolLayerId ) {}
72 bool visitSymbol(
const QgsSymbol *symbol )
74 if ( symbol && !mSymbol )
80 if ( sl->
id() == mSymbolLayerId )
83 mSymbolLayerIndex = mSymbolLayer ? idx : -1;
89 subSymbol && !visitSymbol( subSymbol ) )
103 if ( symbolEntity->symbol() )
104 visitSymbol( symbolEntity->symbol() );
112 const QgsSymbol *symbol()
const {
return mSymbol; }
122 int symbolLayerIndex()
const {
return mSymbolLayerIndex; }
127 int mSymbolLayerIndex = -1;
128 const QString &mSymbolLayerId;
135 const QPointF &pointAt(
const QList<QList<QPolygonF>> &points,
int partIndex,
int ringIndex,
int pointIndex )
137 if ( partIndex < 0 || partIndex >= points.count() )
138 throw std::invalid_argument(
"Blank segments internal error : Invalid part index" );
140 const QList<QPolygonF> &rings = points.at( partIndex );
141 if ( ringIndex < 0 || ringIndex >= rings.count() )
142 throw std::invalid_argument(
"blank segments internal error : Invalid ring index" );
144 const QPolygonF &pts = rings.at( ringIndex );
145 if ( pointIndex < 0 || pointIndex >= pts.count() )
146 throw std::invalid_argument(
"blank segments internal error : Invalid point index" );
148 return pts.at( pointIndex );
151 enum ProjectedPointStatus
162 QPointF projectedPoint(
const QPointF &lineStartPt,
const QPointF &lineEndPt,
const QPointF &point,
double &distance, ProjectedPointStatus &status )
164 status = ProjectedPointStatus::OK;
167 const double Ax = lineStartPt.x();
168 const double Ay = lineStartPt.y();
169 const double Bx = lineEndPt.x();
170 const double By = lineEndPt.y();
171 const double Cx = point.x();
172 const double Cy = point.y();
177 status = ProjectedPointStatus::LINE_EMPTY;
181 const double r = ( ( Cx - Ax ) * ( Bx - Ax ) + ( Cy - Ay ) * ( By - Ay ) ) / std::pow( length, 2 );
182 if ( r < 0 or r > 1 )
184 status = ProjectedPointStatus::NOT_ON_SEGMENT;
188 const double Px = Ax + r * ( Bx - Ax );
189 const double Py = Ay + r * ( By - Ay );
193 return QPointF( Px, Py );
236 void setPoints(
int partIndex,
int ringIndex,
int startIndex,
int endIndex, QPointF startPt, QPointF endPt );
291 const QPointF &
pointAt(
int index )
const;
301 int mStartIndex = -1;
305 bool mNeedSwap =
false;
312 , mSymbolLayerId( symbolLayer->id() )
313 , mBlankSegmentsFieldIndex( blankSegmentFieldIndex )
324 initRubberBand( mStartRubberBand );
325 initRubberBand( mEndRubberBand );
327 mEditedBlankSegment->setHighlighted(
true );
334 if ( !mLayer || !mLayer->renderer() )
337 if ( !mSymbol || !mSymbolLayer )
340 QgsSymbolLayerFinder
finder( mSymbolLayerId );
341 mLayer->renderer()->accept( &
finder );
344 mSymbol.reset(
finder.symbol()->clone() );
345 if ( mSymbolLayer = createRenderedPointsSymbolLayer(
finder.symbolLayer() ); mSymbolLayer )
348 mSymbol->changeSymbolLayer(
finder.symbolLayerIndex(), mSymbolLayer );
353 QgsDebugError(
"Fail to create fake templated line symbol layer" );
358 QgsDebugError(
"Fail to retrieve symbol and templated line symbol layer" );
367 if ( !mSymbol ||
mPoints.isEmpty() )
370 const QPoint &pos = e->pos();
372 if (
canvas()->extent() != mExtent )
379 case State::SelectFeature:
380 case State::BlankSegmentSelected:
381 case State::FeatureSelected:
384 case State::BlankSegmentCreationStarted:
385 mState = State::FeatureSelected;
386 setCurrentBlankSegment( -1 );
389 case State::BlankSegmentModificationStarted:
390 mState = State::BlankSegmentSelected;
392 setCurrentBlankSegment( mCurrentBlankSegmentIndex );
399 case State::SelectFeature:
402 case State::BlankSegmentSelected:
403 updateHoveredBlankSegment( pos );
406 case State::FeatureSelected:
408 updateHoveredBlankSegment( pos );
411 mStartRubberBand->setVisible( mHoveredBlankSegmentIndex == -1 );
412 if ( mHoveredBlankSegmentIndex == -1 )
417 int partIndex = -1, ringIndex = -1, pointIndex = -1;
418 const QPointF closestPt = closestPoint( pos, distance, partIndex, ringIndex, pointIndex );
421 mStartRubberBand->addPoint( m2p.
toMapCoordinates( closestPt.x(), closestPt.y() ) );
427 case State::BlankSegmentModificationStarted:
428 case State::BlankSegmentCreationStarted:
430 double distance = -1;
434 QPointF P = closestPoint( pos, distance, partIndex, ringIndex, pointIndex );
435 if ( distance > -1 && pointIndex > -1 )
437 mEditedBlankSegment->setPoints( partIndex, ringIndex, mEditedBlankSegment->getStartIndex(), pointIndex, mEditedBlankSegment->getStartPoint(), P );
438 updateStartEndRubberBand();
449 case State::SelectFeature:
455 emit
messageEmitted( tr(
"No feature was detected at the clicked position. Please click closer to the feature or enhance the search tolerance under Settings->Options->Digitizing->Search radius for vertex edits" ),
Qgis::MessageLevel::Critical );
461 updateHoveredBlankSegment( e->pos() );
463 mState = State::FeatureSelected;
466 case State::FeatureSelected:
469 if ( mHoveredBlankSegmentIndex > -1 )
471 mState = State::BlankSegmentSelected;
472 setCurrentBlankSegment( mHoveredBlankSegmentIndex );
477 mState = State::BlankSegmentCreationStarted;
478 double distance = -1;
482 QPointF P = closestPoint( e->pos(), distance, partIndex, ringIndex, pointIndex );
483 if ( distance > -1 && pointIndex > -1 )
485 mEditedBlankSegment->setPoints( partIndex, ringIndex, pointIndex, pointIndex, P, P );
486 updateStartEndRubberBand();
492 case State::BlankSegmentSelected:
494 Q_ASSERT( mCurrentBlankSegmentIndex > -1 && mCurrentBlankSegmentIndex <
static_cast<int>( mBlankSegments.size() ) );
498 if ( mHoveredBlankSegmentIndex > -1 && mHoveredBlankSegmentIndex != mCurrentBlankSegmentIndex )
500 setCurrentBlankSegment( mHoveredBlankSegmentIndex );
504 const double distanceFromStart = ( currentBlankSegment->getStartPoint() - e->pos() ).manhattanLength();
505 const double distanceFromEnd = ( currentBlankSegment->getEndPoint() - e->pos() ).manhattanLength();
508 if ( std::min( distanceFromStart, distanceFromEnd ) <
TOLERANCE )
510 if ( distanceFromStart < distanceFromEnd )
513 mEditedBlankSegment->setPoints( currentBlankSegment->getPartIndex(), currentBlankSegment->getRingIndex(), currentBlankSegment->getEndIndex(), currentBlankSegment->getStartIndex(), currentBlankSegment->getEndPoint(), currentBlankSegment->getStartPoint() );
516 mState = State::BlankSegmentModificationStarted;
523 case State::BlankSegmentModificationStarted:
524 case State::BlankSegmentCreationStarted:
527 if ( mCurrentBlankSegmentIndex < 0 )
530 mBlankSegments.back()->copyFrom( *mEditedBlankSegment );
531 mState = State::FeatureSelected;
537 blankSegment->copyFrom( *mEditedBlankSegment );
538 mState = State::BlankSegmentSelected;
553 case State::SelectFeature:
556 case State::BlankSegmentSelected:
557 if ( e->matches( QKeySequence::Delete ) && mCurrentBlankSegmentIndex > -1 )
559 mState = State::FeatureSelected;
560 int toRemoveIndex = mCurrentBlankSegmentIndex;
561 setCurrentBlankSegment( -1 );
562 mBlankSegments.erase( mBlankSegments.begin() + toRemoveIndex );
566 else if ( e->matches( QKeySequence::Cancel ) )
568 mState = State::FeatureSelected;
569 setCurrentBlankSegment( -1 );
575 case State::FeatureSelected:
576 if ( e->matches( QKeySequence::Cancel ) )
579 mState = State::SelectFeature;
585 case State::BlankSegmentCreationStarted:
586 if ( e->matches( QKeySequence::Cancel ) )
588 mState = State::FeatureSelected;
589 setCurrentBlankSegment( -1 );
594 case State::BlankSegmentModificationStarted:
595 if ( e->matches( QKeySequence::Cancel ) )
597 mState = State::BlankSegmentSelected;
599 setCurrentBlankSegment( mCurrentBlankSegmentIndex );
608 double startDistance = 0;
609 const int startIndex = mNeedSwap ? mEndIndex : mStartIndex;
610 const QPointF startPt = mNeedSwap ? mEndPt : mStartPt;
611 for (
int i = 1; i < startIndex; i++ )
618 double endDistance = startDistance;
629 return QPair<double, double>( startDistance, endDistance );
632int QgsMapToolEditBlankSegmentsBase::closestBlankSegmentIndex(
const QPointF &point,
double &distance )
const
636 int iBlankSegment = -1;
637 for (
int i = 0; i < static_cast<int>( mBlankSegments.size() ); i++ )
640 for (
int iPoint = 1; iPoint < blankSegment->pointsCount(); iPoint++ )
643 ProjectedPointStatus status = ProjectedPointStatus::OK;
647 projectedPoint( blankSegment->pointAt( iPoint - 1 ), blankSegment->pointAt( iPoint ), point, d, status );
649 catch ( std::invalid_argument &e )
657 case ProjectedPointStatus::LINE_EMPTY:
660 case ProjectedPointStatus::NOT_ON_SEGMENT:
661 d = std::min( ( blankSegment->getStartPoint() - point ).manhattanLength(), ( blankSegment->getEndPoint() - point ).manhattanLength() );
664 case ProjectedPointStatus::OK:
668 if ( distance == -1 || d < distance )
676 return iBlankSegment;
679QPointF QgsMapToolEditBlankSegmentsBase::closestPoint(
const QPointF &point,
double &distance,
int &partIndex,
int &ringIndex,
int &pointIndex )
const
682 QPointF currentPoint;
685 for (
int iPart = 0; iPart <
mPoints.count(); iPart++ )
687 const QList<QPolygonF> &rings =
mPoints.at( iPart );
688 for (
int iRing = 0; iRing < rings.count(); iRing++ )
690 const QPolygonF &points = rings.at( iRing );
691 for (
int i = 1; i < points.count(); i++ )
694 ProjectedPointStatus status = ProjectedPointStatus::OK;
695 QPointF P = projectedPoint( points.at( i - 1 ), points.at( i ), point, d, status );
698 case ProjectedPointStatus::LINE_EMPTY:
699 case ProjectedPointStatus::NOT_ON_SEGMENT:
702 case ProjectedPointStatus::OK:
706 if ( distance == -1 || d < distance )
721void QgsMapToolEditBlankSegmentsBase::updateStartEndRubberBand()
726 bool displayEndPoint =
true;
729 case State::SelectFeature:
730 case State::BlankSegmentCreationStarted:
733 case State::FeatureSelected:
734 displayEndPoint =
false;
737 case State::BlankSegmentSelected:
738 case State::BlankSegmentModificationStarted:
744 const QPointF &startPoint = mEditedBlankSegment->getStartPoint();
745 mStartRubberBand->addPoint( m2p.
toMapCoordinates( startPoint.x(), startPoint.y() ) );
747 if ( displayEndPoint )
749 const QPointF &endPoint = mEditedBlankSegment->getEndPoint();
750 mEndRubberBand->addPoint( m2p.
toMapCoordinates( endPoint.x(), endPoint.y() ) );
754void QgsMapToolEditBlankSegmentsBase::updateHoveredBlankSegment(
const QPoint &pos )
756 double distance = -1;
757 int iBlankSegment = closestBlankSegmentIndex( pos, distance );
759 if ( mHoveredBlankSegmentIndex > -1
760 && mHoveredBlankSegmentIndex <
static_cast<int>( mBlankSegments.size() )
761 && mHoveredBlankSegmentIndex != mCurrentBlankSegmentIndex )
763 mBlankSegments.at( mHoveredBlankSegmentIndex )->setHighlighted(
false );
767 if ( iBlankSegment > -1 && distance <
TOLERANCE )
769 mHoveredBlankSegmentIndex = iBlankSegment;
770 if ( mHoveredBlankSegmentIndex <
static_cast<int>( mBlankSegments.size() ) )
772 mBlankSegments.at( mHoveredBlankSegmentIndex )->setHighlighted(
true );
778 mHoveredBlankSegmentIndex = -1;
782void QgsMapToolEditBlankSegmentsBase::setCurrentBlankSegment(
int currentBlankSegmentIndex )
786 if ( mCurrentBlankSegmentIndex > -1
787 && mCurrentBlankSegmentIndex <
static_cast<int>( mBlankSegments.size() ) )
789 mBlankSegments.at( mCurrentBlankSegmentIndex )->setVisible(
true );
790 mBlankSegments.at( mCurrentBlankSegmentIndex )->setHighlighted(
false );
793 mCurrentBlankSegmentIndex = currentBlankSegmentIndex;
794 if ( mCurrentBlankSegmentIndex > -1 && mCurrentBlankSegmentIndex <
static_cast<int>( mBlankSegments.size() ) )
796 mBlankSegments.at( mCurrentBlankSegmentIndex )->setVisible(
false );
797 mEditedBlankSegment->copyFrom( *( mBlankSegments.at( mCurrentBlankSegmentIndex ).get() ) );
801 mEditedBlankSegment->setVisible(
false );
804 updateStartEndRubberBand();
807void QgsMapToolEditBlankSegmentsBase::updateAttribute()
812 QList<QList<QgsBlankSegmentUtils::BlankSegments>> blankSegments;
813 for (
const QObjectUniquePtr<QgsBlankSegmentRubberBand> &blankSegment : mBlankSegments )
817 QPair<double, double> startEndDistance = blankSegment->getStartEndDistance( mSymbolLayer->blankSegmentsUnit() );
819 const int partIndex = blankSegment->getPartIndex();
820 if ( partIndex >= blankSegments.count() )
821 blankSegments.resize( partIndex + 1 );
823 QList<QgsBlankSegmentUtils::BlankSegments> &rings = blankSegments[partIndex];
824 const int ringIndex = blankSegment->getRingIndex();
825 if ( ringIndex >= rings.count() )
826 rings.resize( ringIndex + 1 );
829 segments << startEndDistance;
831 catch ( std::invalid_argument &e )
838 QStringList strParts;
839 for ( QList<QgsBlankSegmentUtils::BlankSegments> &part : blankSegments )
841 QStringList strRings;
844 std::sort( ring.begin(), ring.end() );
845 QStringList strDistances;
846 for (
const QPair<double, double> &distance : ring )
848 strDistances << ( QString::number( distance.first ) +
" " + QString::number( distance.second ) );
851 strRings <<
"(" + strDistances.join(
"," ) +
")";
854 strParts <<
"(" + strRings.join(
"," ) +
")";
857 QString strNewBlankSegments =
"(" + strParts.join(
"," ) +
")";
859 mLayer->beginEditCommand( tr(
"Set blank segment list" ) );
860 if ( mLayer->changeAttributeValue( mCurrentFeatureId, mBlankSegmentsFieldIndex, strNewBlankSegments ) )
862 mLayer->endEditCommand();
863 mLayer->triggerRepaint();
867 mLayer->destroyEditCommand();
871void QgsMapToolEditBlankSegmentsBase::loadFeaturePoints()
875 mBlankSegments.clear();
876 setCurrentBlankSegment( -1 );
878 if (
FID_IS_NULL( mCurrentFeatureId ) || !mSymbolLayer )
882 feature = mLayer->getFeature( mCurrentFeatureId );
883 QString currentBlankSegments = feature.
attribute( mBlankSegmentsFieldIndex ).toString();
888 QgsCoordinateTransform transform(
canvas()->mapSettings().layerTransform( mLayer ) );
889 QgsNullPaintDevice nullPaintDevice;
890 QPainter painter( &nullPaintDevice );
893 mSymbol->startRender( context );
894 mSymbol->renderFeature( feature, context );
895 mSymbol->stopRender( context );
902 if ( !error.isEmpty() )
909 for (
int iPart = 0; iPart <
mPoints.count(); iPart++ )
911 const QList<QPolygonF> &rings =
mPoints.at( iPart );
912 for (
int iRing = 0; iRing < rings.count(); iRing++ )
914 if ( iPart >= allBlankSegments.count() || iRing >= allBlankSegments.at( iPart ).count() )
919 double currentLength = 0;
921 const QPolygonF &points = rings.at( iRing );
922 for ( QPair<double, double> ba : blankSegments )
924 while ( iPoint < points.count() - 1 && currentLength < ba.first )
930 if ( iPoint == points.count() )
933 int startIndex = iPoint;
934 Line l( points.at( iPoint ), points.at( iPoint - 1 ) );
935 QPointF startPt = points.at( iPoint ) + l.diffForInterval( currentLength - ba.first );
937 while ( iPoint < points.count() - 1 && currentLength < ba.second )
943 if ( iPoint == points.count() )
946 int endIndex = iPoint;
947 Line l2( points.at( iPoint ), points.at( iPoint - 1 ) );
948 QPointF endPt = points.at( iPoint ) + l2.diffForInterval( currentLength - ba.second );
967 setPoints( partIndex, ringIndex, startIndex, endIndex, startPt, endPt );
972 mPartIndex = partIndex;
973 mRingIndex = ringIndex;
974 mStartIndex = startIndex;
975 mEndIndex = endIndex;
980 if ( mStartIndex == mEndIndex )
984 if (
const QPointF &startIndexPoint =
::pointAt( mPoints, mPartIndex, mRingIndex, mStartIndex );
990 catch ( std::invalid_argument &e )
995 else if ( mStartIndex > mEndIndex )
1003void QgsMapToolEditBlankSegmentsBase::QgsBlankSegmentRubberBand::updatePoints()
1005 const QgsMapToPixel &m2p = *( mMapCanvas->getCoordinateTransform() );
1008 for (
int iPoint = 0; iPoint < pointsCount(); iPoint++ )
1012 const QPointF &point = pointAt( iPoint );
1014 addPoint( mapPoint, iPoint == pointsCount() - 1 );
1016 catch ( std::invalid_argument &e )
1068 return std::abs( mEndIndex - mStartIndex ) + 2;
1075 ( mNeedSwap ? mEndPt : mStartPt )
1077 : ( index ==
pointsCount() - 1 ? ( mNeedSwap ? mStartPt : mEndPt )
1079 :
::pointAt( mPoints, mPartIndex, mRingIndex, ( mNeedSwap ? mEndIndex : mStartIndex ) + index - 1 ) );
Keeps a pointer to a QObject and deletes it whenever this object is deleted.
Provides global constants and enumerations for use throughout the application.
@ Critical
Critical/error message.
RenderUnit
Rendering size units.
QList< QPair< double, double > > BlankSegments
static QList< QList< BlankSegments > > parseBlankSegments(const QString &strBlankSegments, const QgsRenderContext &renderContext, Qgis::RenderUnit unit, QString &error)
Parse blank segments string representation strBlankSegments.
Q_INVOKABLE QVariant attribute(const QString &name) const
Lookup attribute value by attribute name.
static double distance2D(double x1, double y1, double x2, double y2)
Returns the 2D distance between (x1, y1) and (x2, y2).
Abstract base class for line symbol layers.
QgsMapCanvas * mMapCanvas
pointer to map canvas
const QgsMapToPixel * getCoordinateTransform()
Gets the current coordinate transform.
QgsRectangle extent() const
Returns the current zoom extent of the map canvas.
A mouse event which is the result of a user interaction with a QgsMapCanvas.
Perform transforms between map coordinates and device coordinates.
QgsPointXY toMapCoordinates(int x, int y) const
Transforms device coordinates to map (world) coordinates.
@ Area
Snapped to an area.
Contains information about the context of a rendering operation.
void setCoordinateTransform(const QgsCoordinateTransform &t)
Sets the current coordinate transform for the context.
double convertFromPainterUnits(double size, Qgis::RenderUnit unit) const
Converts a size from painter units (pixels) to the specified render unit.
void setPainter(QPainter *p)
Sets the destination QPainter for the render operation.
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
Responsible for drawing transient features (e.g.
QgsRubberBand(QgsMapCanvas *mapCanvas, Qgis::GeometryType geometryType=Qgis::GeometryType::Line)
Creates a new RubberBand.
void setWidth(double width)
Sets the width of the line.
@ ICON_BOX
A box is used to highlight points (□).
void setColor(const QColor &color)
Sets the color for the rubberband.
static const QgsSettingsEntryColor * settingsDigitizingLineColor
Settings entry digitizing line color.
virtual QgsStyle::StyleEntity type() const =0
Returns the type of style entity.
An interface for classes which can visit style entity (e.g.
@ SymbolRule
Rule based symbology or label child rule.
virtual bool visitEnter(const QgsStyleEntityVisitorInterface::Node &node)
Called when the visitor starts visiting a node.
virtual bool visit(const QgsStyleEntityVisitorInterface::StyleLeaf &entity)
Called when the visitor will visit a style entity.
A symbol entity for QgsStyle databases.
Abstract base class for symbol layers.
QString id() const
Returns symbol layer identifier This id is unique in the whole project.
Abstract base class for all rendered symbols.
QgsSymbolLayer * symbolLayer(int layer)
Returns the symbol layer at the specified index.
int symbolLayerCount() const
Returns the total number of symbol layers contained in the symbol.
Base class for templated line symbols, e.g.
Represents a vector layer which manages a vector based dataset.
int scaleIconSize(int standardSize)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly,...
#define QgsDebugError(str)
QgsFeatureId featureId() const
The id of the feature to which the snapped geometry belongs.
Contains information relating to a node (i.e.
QgsStyleEntityVisitorInterface::NodeType type
Node type.
Contains information relating to the style entity currently being visited.
const QgsStyleEntityInterface * entity
Reference to style entity being visited.