36#include "moc_qgsmaptooleditblanksegments.cpp"
58 QgsSymbolLayerFinder(
const QString &symbolLayerId )
59 : mSymbolLayerId( symbolLayerId )
73 bool visitSymbol(
const QgsSymbol *symbol )
75 if ( symbol && !mSymbol )
81 if ( sl->
id() == mSymbolLayerId )
84 mSymbolLayerIndex = mSymbolLayer ? idx : -1;
89 if (
const QgsSymbol *subSymbol =
const_cast<QgsSymbolLayer *
>( sl )->subSymbol(); 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:
456 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 );
462 updateHoveredBlankSegment( e->pos() );
464 mState = State::FeatureSelected;
467 case State::FeatureSelected:
470 if ( mHoveredBlankSegmentIndex > -1 )
472 mState = State::BlankSegmentSelected;
473 setCurrentBlankSegment( mHoveredBlankSegmentIndex );
478 mState = State::BlankSegmentCreationStarted;
479 double distance = -1;
483 QPointF P = closestPoint( e->pos(), distance, partIndex, ringIndex, pointIndex );
484 if ( distance > -1 && pointIndex > -1 )
486 mEditedBlankSegment->setPoints( partIndex, ringIndex, pointIndex, pointIndex, P, P );
487 updateStartEndRubberBand();
493 case State::BlankSegmentSelected:
495 Q_ASSERT( mCurrentBlankSegmentIndex > -1 && mCurrentBlankSegmentIndex <
static_cast<int>( mBlankSegments.size() ) );
499 if ( mHoveredBlankSegmentIndex > -1 && mHoveredBlankSegmentIndex != mCurrentBlankSegmentIndex )
501 setCurrentBlankSegment( mHoveredBlankSegmentIndex );
505 const double distanceFromStart = ( currentBlankSegment->getStartPoint() - e->pos() ).manhattanLength();
506 const double distanceFromEnd = ( currentBlankSegment->getEndPoint() - e->pos() ).manhattanLength();
509 if ( std::min( distanceFromStart, distanceFromEnd ) <
TOLERANCE )
511 if ( distanceFromStart < distanceFromEnd )
514 mEditedBlankSegment->setPoints(
515 currentBlankSegment->getPartIndex(),
516 currentBlankSegment->getRingIndex(),
517 currentBlankSegment->getEndIndex(),
518 currentBlankSegment->getStartIndex(),
519 currentBlankSegment->getEndPoint(),
520 currentBlankSegment->getStartPoint()
524 mState = State::BlankSegmentModificationStarted;
531 case State::BlankSegmentModificationStarted:
532 case State::BlankSegmentCreationStarted:
535 if ( mCurrentBlankSegmentIndex < 0 )
538 mBlankSegments.back()->copyFrom( *mEditedBlankSegment );
539 mState = State::FeatureSelected;
545 blankSegment->copyFrom( *mEditedBlankSegment );
546 mState = State::BlankSegmentSelected;
561 case State::SelectFeature:
564 case State::BlankSegmentSelected:
565 if ( e->matches( QKeySequence::Delete ) && mCurrentBlankSegmentIndex > -1 )
567 mState = State::FeatureSelected;
568 int toRemoveIndex = mCurrentBlankSegmentIndex;
569 setCurrentBlankSegment( -1 );
570 mBlankSegments.erase( mBlankSegments.begin() + toRemoveIndex );
574 else if ( e->matches( QKeySequence::Cancel ) )
576 mState = State::FeatureSelected;
577 setCurrentBlankSegment( -1 );
583 case State::FeatureSelected:
584 if ( e->matches( QKeySequence::Cancel ) )
587 mState = State::SelectFeature;
593 case State::BlankSegmentCreationStarted:
594 if ( e->matches( QKeySequence::Cancel ) )
596 mState = State::FeatureSelected;
597 setCurrentBlankSegment( -1 );
602 case State::BlankSegmentModificationStarted:
603 if ( e->matches( QKeySequence::Cancel ) )
605 mState = State::BlankSegmentSelected;
607 setCurrentBlankSegment( mCurrentBlankSegmentIndex );
616 double startDistance = 0;
617 const int startIndex = mNeedSwap ? mEndIndex : mStartIndex;
618 const QPointF startPt = mNeedSwap ? mEndPt : mStartPt;
619 for (
int i = 1; i < startIndex; i++ )
626 double endDistance = startDistance;
637 return QPair<double, double>( startDistance, endDistance );
640int QgsMapToolEditBlankSegmentsBase::closestBlankSegmentIndex(
const QPointF &point,
double &distance )
const
644 int iBlankSegment = -1;
645 for (
int i = 0; i < static_cast<int>( mBlankSegments.size() ); i++ )
648 for (
int iPoint = 1; iPoint < blankSegment->pointsCount(); iPoint++ )
651 ProjectedPointStatus status = ProjectedPointStatus::OK;
655 projectedPoint( blankSegment->pointAt( iPoint - 1 ), blankSegment->pointAt( iPoint ), point, d, status );
657 catch ( std::invalid_argument &e )
665 case ProjectedPointStatus::LINE_EMPTY:
668 case ProjectedPointStatus::NOT_ON_SEGMENT:
669 d = std::min( ( blankSegment->getStartPoint() - point ).manhattanLength(), ( blankSegment->getEndPoint() - point ).manhattanLength() );
672 case ProjectedPointStatus::OK:
676 if ( distance == -1 || d < distance )
684 return iBlankSegment;
687QPointF QgsMapToolEditBlankSegmentsBase::closestPoint(
const QPointF &point,
double &distance,
int &partIndex,
int &ringIndex,
int &pointIndex )
const
690 QPointF currentPoint;
693 for (
int iPart = 0; iPart <
mPoints.count(); iPart++ )
695 const QList<QPolygonF> &rings =
mPoints.at( iPart );
696 for (
int iRing = 0; iRing < rings.count(); iRing++ )
698 const QPolygonF &points = rings.at( iRing );
699 for (
int i = 1; i < points.count(); i++ )
702 ProjectedPointStatus status = ProjectedPointStatus::OK;
703 QPointF P = projectedPoint( points.at( i - 1 ), points.at( i ), point, d, status );
706 case ProjectedPointStatus::LINE_EMPTY:
707 case ProjectedPointStatus::NOT_ON_SEGMENT:
710 case ProjectedPointStatus::OK:
714 if ( distance == -1 || d < distance )
729void QgsMapToolEditBlankSegmentsBase::updateStartEndRubberBand()
734 bool displayEndPoint =
true;
737 case State::SelectFeature:
738 case State::BlankSegmentCreationStarted:
741 case State::FeatureSelected:
742 displayEndPoint =
false;
745 case State::BlankSegmentSelected:
746 case State::BlankSegmentModificationStarted:
752 const QPointF &startPoint = mEditedBlankSegment->getStartPoint();
753 mStartRubberBand->addPoint( m2p.
toMapCoordinates( startPoint.x(), startPoint.y() ) );
755 if ( displayEndPoint )
757 const QPointF &endPoint = mEditedBlankSegment->getEndPoint();
758 mEndRubberBand->addPoint( m2p.
toMapCoordinates( endPoint.x(), endPoint.y() ) );
762void QgsMapToolEditBlankSegmentsBase::updateHoveredBlankSegment(
const QPoint &pos )
764 double distance = -1;
765 int iBlankSegment = closestBlankSegmentIndex( pos, distance );
767 if ( mHoveredBlankSegmentIndex > -1 && mHoveredBlankSegmentIndex <
static_cast<int>( mBlankSegments.size() ) && mHoveredBlankSegmentIndex != mCurrentBlankSegmentIndex )
769 mBlankSegments.at( mHoveredBlankSegmentIndex )->setHighlighted(
false );
773 if ( iBlankSegment > -1 && distance <
TOLERANCE )
775 mHoveredBlankSegmentIndex = iBlankSegment;
776 if ( mHoveredBlankSegmentIndex <
static_cast<int>( mBlankSegments.size() ) )
778 mBlankSegments.at( mHoveredBlankSegmentIndex )->setHighlighted(
true );
784 mHoveredBlankSegmentIndex = -1;
788void QgsMapToolEditBlankSegmentsBase::setCurrentBlankSegment(
int currentBlankSegmentIndex )
792 if ( mCurrentBlankSegmentIndex > -1 && mCurrentBlankSegmentIndex <
static_cast<int>( mBlankSegments.size() ) )
794 mBlankSegments.at( mCurrentBlankSegmentIndex )->setVisible(
true );
795 mBlankSegments.at( mCurrentBlankSegmentIndex )->setHighlighted(
false );
798 mCurrentBlankSegmentIndex = currentBlankSegmentIndex;
799 if ( mCurrentBlankSegmentIndex > -1 && mCurrentBlankSegmentIndex <
static_cast<int>( mBlankSegments.size() ) )
801 mBlankSegments.at( mCurrentBlankSegmentIndex )->setVisible(
false );
802 mEditedBlankSegment->copyFrom( *( mBlankSegments.at( mCurrentBlankSegmentIndex ).get() ) );
806 mEditedBlankSegment->setVisible(
false );
809 updateStartEndRubberBand();
812void QgsMapToolEditBlankSegmentsBase::updateAttribute()
817 QList<QList<QgsBlankSegmentUtils::BlankSegments>> blankSegments;
818 for (
const QObjectUniquePtr<QgsBlankSegmentRubberBand> &blankSegment : mBlankSegments )
822 QPair<double, double> startEndDistance = blankSegment->getStartEndDistance( mSymbolLayer->blankSegmentsUnit() );
824 const int partIndex = blankSegment->getPartIndex();
825 if ( partIndex >= blankSegments.count() )
826 blankSegments.resize( partIndex + 1 );
828 QList<QgsBlankSegmentUtils::BlankSegments> &rings = blankSegments[partIndex];
829 const int ringIndex = blankSegment->getRingIndex();
830 if ( ringIndex >= rings.count() )
831 rings.resize( ringIndex + 1 );
834 segments << startEndDistance;
836 catch ( std::invalid_argument &e )
843 QStringList strParts;
844 for ( QList<QgsBlankSegmentUtils::BlankSegments> &part : blankSegments )
846 QStringList strRings;
849 std::sort( ring.begin(), ring.end() );
850 QStringList strDistances;
851 for (
const QPair<double, double> &distance : ring )
853 strDistances << ( QString::number( distance.first ) +
" " + QString::number( distance.second ) );
856 strRings <<
"(" + strDistances.join(
"," ) +
")";
859 strParts <<
"(" + strRings.join(
"," ) +
")";
862 QString strNewBlankSegments =
"(" + strParts.join(
"," ) +
")";
864 mLayer->beginEditCommand( tr(
"Set blank segment list" ) );
865 if ( mLayer->changeAttributeValue( mCurrentFeatureId, mBlankSegmentsFieldIndex, strNewBlankSegments ) )
867 mLayer->endEditCommand();
868 mLayer->triggerRepaint();
872 mLayer->destroyEditCommand();
876void QgsMapToolEditBlankSegmentsBase::loadFeaturePoints()
880 mBlankSegments.clear();
881 setCurrentBlankSegment( -1 );
883 if (
FID_IS_NULL( mCurrentFeatureId ) || !mSymbolLayer )
887 feature = mLayer->getFeature( mCurrentFeatureId );
888 QString currentBlankSegments = feature.
attribute( mBlankSegmentsFieldIndex ).toString();
893 QgsCoordinateTransform transform(
canvas()->mapSettings().layerTransform( mLayer ) );
894 QgsNullPaintDevice nullPaintDevice;
895 QPainter painter( &nullPaintDevice );
898 mSymbol->startRender( context );
899 mSymbol->renderFeature( feature, context );
900 mSymbol->stopRender( context );
907 if ( !error.isEmpty() )
914 for (
int iPart = 0; iPart <
mPoints.count(); iPart++ )
916 const QList<QPolygonF> &rings =
mPoints.at( iPart );
917 for (
int iRing = 0; iRing < rings.count(); iRing++ )
919 if ( iPart >= allBlankSegments.count() || iRing >= allBlankSegments.at( iPart ).count() )
924 double currentLength = 0;
926 const QPolygonF &points = rings.at( iRing );
927 for ( QPair<double, double> ba : blankSegments )
929 while ( iPoint < points.count() - 1 && currentLength < ba.first )
935 if ( iPoint == points.count() )
938 int startIndex = iPoint;
939 Line l( points.at( iPoint ), points.at( iPoint - 1 ) );
940 QPointF startPt = points.at( iPoint ) + l.diffForInterval( currentLength - ba.first );
942 while ( iPoint < points.count() - 1 && currentLength < ba.second )
948 if ( iPoint == points.count() )
951 int endIndex = iPoint;
952 Line l2( points.at( iPoint ), points.at( iPoint - 1 ) );
953 QPointF endPt = points.at( iPoint ) + l2.diffForInterval( currentLength - ba.second );
974 setPoints( partIndex, ringIndex, startIndex, endIndex, startPt, endPt );
979 mPartIndex = partIndex;
980 mRingIndex = ringIndex;
981 mStartIndex = startIndex;
982 mEndIndex = endIndex;
987 if ( mStartIndex == mEndIndex )
991 if (
const QPointF &startIndexPoint =
::pointAt( mPoints, mPartIndex, mRingIndex, mStartIndex );
997 catch ( std::invalid_argument &e )
1002 else if ( mStartIndex > mEndIndex )
1010void QgsMapToolEditBlankSegmentsBase::QgsBlankSegmentRubberBand::updatePoints()
1012 const QgsMapToPixel &m2p = *( mMapCanvas->getCoordinateTransform() );
1015 for (
int iPoint = 0; iPoint < pointsCount(); iPoint++ )
1019 const QPointF &point = pointAt( iPoint );
1021 addPoint( mapPoint, iPoint == pointsCount() - 1 );
1023 catch ( std::invalid_argument &e )
1075 return std::abs( mEndIndex - mStartIndex ) + 2;
1082 ( mNeedSwap ? mEndPt : mStartPt )
1084 : ( index ==
pointsCount() - 1 ? ( mNeedSwap ? mStartPt : mEndPt )
1086 :
::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.