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;
795 if ( mCurrentBlankSegmentIndex > -1 && mCurrentBlankSegmentIndex <
static_cast<int>( mBlankSegments.size() ) )
797 mBlankSegments.at( mCurrentBlankSegmentIndex )->setVisible(
false );
798 mEditedBlankSegment->copyFrom( *( mBlankSegments.at( mCurrentBlankSegmentIndex ).get() ) );
802 mEditedBlankSegment->setVisible(
false );
806 updateStartEndRubberBand();
809void QgsMapToolEditBlankSegmentsBase::updateAttribute()
814 QList<QList<QgsBlankSegmentUtils::BlankSegments>> blankSegments;
815 for (
const QObjectUniquePtr<QgsBlankSegmentRubberBand> &blankSegment : mBlankSegments )
819 QPair<double, double> startEndDistance = blankSegment->getStartEndDistance( mSymbolLayer->blankSegmentsUnit() );
821 const int partIndex = blankSegment->getPartIndex();
822 if ( partIndex >= blankSegments.count() )
823 blankSegments.resize( partIndex + 1 );
825 QList<QgsBlankSegmentUtils::BlankSegments> &rings = blankSegments[partIndex];
826 const int ringIndex = blankSegment->getRingIndex();
827 if ( ringIndex >= rings.count() )
828 rings.resize( ringIndex + 1 );
831 segments << startEndDistance;
833 catch ( std::invalid_argument &e )
840 QStringList strParts;
841 for ( QList<QgsBlankSegmentUtils::BlankSegments> &part : blankSegments )
843 QStringList strRings;
846 std::sort( ring.begin(), ring.end() );
847 QStringList strDistances;
848 for (
const QPair<double, double> &distance : ring )
850 strDistances << ( QString::number( distance.first ) +
" " + QString::number( distance.second ) );
853 strRings <<
"(" + strDistances.join(
"," ) +
")";
856 strParts <<
"(" + strRings.join(
"," ) +
")";
859 QString strNewBlankSegments =
"(" + strParts.join(
"," ) +
")";
861 mLayer->beginEditCommand( tr(
"Set blank segment list" ) );
862 if ( mLayer->changeAttributeValue( mCurrentFeatureId, mBlankSegmentsFieldIndex, strNewBlankSegments ) )
864 mLayer->endEditCommand();
865 mLayer->triggerRepaint();
869 mLayer->destroyEditCommand();
873void QgsMapToolEditBlankSegmentsBase::loadFeaturePoints()
877 mBlankSegments.clear();
878 setCurrentBlankSegment( -1 );
880 if (
FID_IS_NULL( mCurrentFeatureId ) || !mSymbolLayer )
884 feature = mLayer->getFeature( mCurrentFeatureId );
885 QString currentBlankSegments = feature.
attribute( mBlankSegmentsFieldIndex ).toString();
890 QgsCoordinateTransform transform(
canvas()->mapSettings().layerTransform( mLayer ) );
891 QgsNullPaintDevice nullPaintDevice;
892 QPainter painter( &nullPaintDevice );
895 mSymbol->startRender( context );
896 mSymbol->renderFeature( feature, context );
897 mSymbol->stopRender( context );
904 if ( !error.isEmpty() )
911 for (
int iPart = 0; iPart <
mPoints.count(); iPart++ )
913 const QList<QPolygonF> &rings =
mPoints.at( iPart );
914 for (
int iRing = 0; iRing < rings.count(); iRing++ )
916 if ( iPart >= allBlankSegments.count() || iRing >= allBlankSegments.at( iPart ).count() )
921 double currentLength = 0;
923 const QPolygonF &points = rings.at( iRing );
924 for ( QPair<double, double> ba : blankSegments )
926 while ( iPoint < points.count() - 1 && currentLength < ba.first )
932 if ( iPoint == points.count() )
935 int startIndex = iPoint;
936 Line l( points.at( iPoint ), points.at( iPoint - 1 ) );
937 QPointF startPt = points.at( iPoint ) + l.diffForInterval( currentLength - ba.first );
939 while ( iPoint < points.count() - 1 && currentLength < ba.second )
945 if ( iPoint == points.count() )
948 int endIndex = iPoint;
949 Line l2( points.at( iPoint ), points.at( iPoint - 1 ) );
950 QPointF endPt = points.at( iPoint ) + l2.diffForInterval( currentLength - ba.second );
969 setPoints( partIndex, ringIndex, startIndex, endIndex, startPt, endPt );
974 mPartIndex = partIndex;
975 mRingIndex = ringIndex;
976 mStartIndex = startIndex;
977 mEndIndex = endIndex;
982 if ( mStartIndex == mEndIndex )
986 if (
const QPointF &startIndexPoint =
::pointAt( mPoints, mPartIndex, mRingIndex, mStartIndex );
992 catch ( std::invalid_argument &e )
997 else if ( mStartIndex > mEndIndex )
1005void QgsMapToolEditBlankSegmentsBase::QgsBlankSegmentRubberBand::updatePoints()
1007 const QgsMapToPixel &m2p = *( mMapCanvas->getCoordinateTransform() );
1010 for (
int iPoint = 0; iPoint < pointsCount(); iPoint++ )
1014 const QPointF &point = pointAt( iPoint );
1016 addPoint( mapPoint, iPoint == pointsCount() - 1 );
1018 catch ( std::invalid_argument &e )
1070 return std::abs( mEndIndex - mStartIndex ) + 2;
1077 ( mNeedSwap ? mEndPt : mStartPt )
1079 : ( index ==
pointsCount() - 1 ? ( mNeedSwap ? mStartPt : mEndPt )
1081 :
::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.