void testLayerUpdates() { QgsPointLocator loc( mVL ); QgsPointLocator::Match mAddV0 = loc.nearestVertex( QgsPointXY( 12, 12 ), 999 ); QVERIFY( mAddV0.isValid() ); QCOMPARE( mAddV0.point(), QgsPointXY( 1, 1 ) ); mVL->startEditing(); // add a new feature QgsFeature ff( 0 ); QgsPolygon polygon; QgsPolyline polyline; polyline << QgsPointXY( 10, 11 ) << QgsPointXY( 11, 10 ) << QgsPointXY( 11, 11 ) << QgsPointXY( 10, 11 ); polygon << polyline; QgsGeometry ffGeom = QgsGeometry::fromPolygon( polygon ) ; ff.setGeometry( ffGeom ); QgsFeatureList flist; flist << ff; bool resA = mVL->addFeature( ff ); QVERIFY( resA ); // verify it is added in the point locator QgsPointLocator::Match mAddV = loc.nearestVertex( QgsPointXY( 12, 12 ), 999 ); QVERIFY( mAddV.isValid() ); QCOMPARE( mAddV.point(), QgsPointXY( 11, 11 ) ); QgsPointLocator::Match mAddE = loc.nearestEdge( QgsPointXY( 11.1, 10.5 ), 999 ); QVERIFY( mAddE.isValid() ); QCOMPARE( mAddE.point(), QgsPointXY( 11, 10.5 ) ); QgsPointLocator::MatchList mAddA = loc.pointInPolygon( QgsPointXY( 10.8, 10.8 ) ); QVERIFY( mAddA.count() == 1 ); // change geometry QgsGeometry *newGeom = new QgsGeometry( ff.geometry() ); newGeom->moveVertex( 10, 10, 2 ); // change 11,11 to 10,10 mVL->changeGeometry( ff.id(), *newGeom ); delete newGeom; // verify it is changed in the point locator QgsPointLocator::Match mChV = loc.nearestVertex( QgsPointXY( 12, 12 ), 999 ); QVERIFY( mChV.isValid() ); QVERIFY( mChV.point() != QgsPointXY( 11, 11 ) ); // that point does not exist anymore mChV = loc.nearestVertex( QgsPointXY( 9, 9 ), 999 ); QVERIFY( mChV.isValid() ); QVERIFY( mChV.point() == QgsPointXY( 10, 10 ) ); // updated point // delete feature bool resD = mVL->deleteFeature( ff.id() ); QVERIFY( resD ); // verify it is deleted from the point locator QgsPointLocator::Match mDelV = loc.nearestVertex( QgsPointXY( 12, 12 ), 999 ); QVERIFY( mDelV.isValid() ); QCOMPARE( mDelV.point(), QgsPointXY( 1, 1 ) ); mVL->rollBack(); }
QList<QgsPointXY> QgsAdvancedDigitizingDockWidget::snapSegmentToAllLayers( const QgsPointXY &originalMapPoint, bool *snapped ) const { QList<QgsPointXY> segment; QgsPointXY pt1, pt2; QgsPointLocator::Match match; QgsSnappingUtils *snappingUtils = mMapCanvas->snappingUtils(); QgsSnappingConfig canvasConfig = snappingUtils->config(); QgsSnappingConfig localConfig = snappingUtils->config(); localConfig.setMode( QgsSnappingConfig::AllLayers ); localConfig.setType( QgsSnappingConfig::Segment ); snappingUtils->setConfig( localConfig ); match = snappingUtils->snapToMap( originalMapPoint ); snappingUtils->setConfig( canvasConfig ); if ( match.isValid() && match.hasEdge() ) { match.edgePoints( pt1, pt2 ); segment << pt1 << pt2; } if ( snapped ) { *snapped = segment.count() == 2; } return segment; }
void QgsMapToolOffsetCurve::canvasMoveEvent( QgsMapMouseEvent* e ) { delete mSnapVertexMarker; mSnapVertexMarker = nullptr; if ( !mOriginalGeometry || !mRubberBand ) { return; } QgsVectorLayer* layer = currentVectorLayer(); if ( !layer ) { return; } mGeometryModified = true; //get offset from current position rectangular to feature QgsPoint layerCoords = toLayerCoordinates( layer, e->pos() ); //snap cursor to background layers QgsPointLocator::Match m = mCanvas->snappingUtils()->snapToMap( e->pos() ); if ( m.isValid() ) { if (( m.layer() && m.layer()->id() != mSourceLayerId ) || m.featureId() != mModifiedFeature ) { layerCoords = toLayerCoordinates( layer, m.point() ); mSnapVertexMarker = new QgsVertexMarker( mCanvas ); mSnapVertexMarker->setIconType( QgsVertexMarker::ICON_CROSS ); mSnapVertexMarker->setColor( Qt::green ); mSnapVertexMarker->setPenWidth( 1 ); mSnapVertexMarker->setCenter( m.point() ); } } QgsPoint minDistPoint; int beforeVertex; double leftOf; double offset = sqrt( mOriginalGeometry->closestSegmentWithContext( layerCoords, minDistPoint, beforeVertex, &leftOf ) ); if ( offset == 0.0 ) { return; } if ( mDistanceWidget ) { // this will also set the rubber band mDistanceWidget->setValue( leftOf < 0 ? offset : -offset ); mDistanceWidget->setFocus( Qt::TabFocusReason ); } else { //create offset geometry using geos setOffsetForRubberBand( leftOf < 0 ? offset : -offset ); } }
void QgsMapCoordsDialog::maybeSetXY( const QgsPoint & xy, Qt::MouseButton button ) { // Only LeftButton should set point if ( Qt::LeftButton == button ) { QgsPoint mapCoordPoint = xy; if ( mQgisCanvas && mSnapToBackgroundLayerBox->isChecked() ) { QgsPointLocator::Match m = mQgisCanvas->snappingUtils()->snapToMap( xy ); if ( m.isValid() ) mapCoordPoint = m.point(); } leXCoord->clear(); leYCoord->clear(); leXCoord->setText( qgsDoubleToString( mapCoordPoint.x() ) ); leYCoord->setText( qgsDoubleToString( mapCoordPoint.y() ) ); } parentWidget()->showNormal(); parentWidget()->activateWindow(); parentWidget()->raise(); mPointFromCanvasPushButton->setChecked( false ); buttonBox->button( QDialogButtonBox::Ok )->setFocus(); activateWindow(); raise(); }
void QgsMapToolNodeTool::canvasPressEvent( QMouseEvent * e ) { QgsDebugCall; mClicked = true; mPressCoordinates = e->pos(); if ( !mSelectedFeature ) { QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mCanvas->currentLayer() ); if ( !vlayer ) return; mSelectAnother = false; QgsPointLocator::Match m = mCanvas->snappingUtils()->snapToCurrentLayer( e->pos(), QgsPointLocator::Vertex | QgsPointLocator::Edge ); if ( !m.isValid() ) { emit messageEmitted( tr( "could not snap to a segment on the current layer." ) ); return; } // remove previous warning emit messageDiscarded(); mSelectedFeature = new QgsSelectedFeature( m.featureId(), vlayer, mCanvas ); connect( QgisApp::instance()->layerTreeView(), SIGNAL( currentLayerChanged( QgsMapLayer* ) ), this, SLOT( currentLayerChanged( QgsMapLayer* ) ) ); connect( mSelectedFeature, SIGNAL( destroyed() ), this, SLOT( selectedFeatureDestroyed() ) ); connect( vlayer, SIGNAL( editingStopped() ), this, SLOT( editingToggled() ) ); mIsPoint = vlayer->geometryType() == QGis::Point; }
void testSnapModeAll() { QgsMapSettings mapSettings; mapSettings.setOutputSize( QSize( 100, 100 ) ); mapSettings.setExtent( QgsRectangle( 0, 0, 1, 1 ) ); QVERIFY( mapSettings.hasValidSettings() ); QgsSnappingUtils u; QgsSnappingConfig snappingConfig = u.config(); u.setMapSettings( mapSettings ); snappingConfig.setMode( QgsSnappingConfig::AllLayers ); u.setConfig( snappingConfig ); // right now there are no layers in map settings - snapping will fail QgsPointLocator::Match m = u.snapToMap( QPoint( 100, 100 ) ); QVERIFY( !m.isValid() ); // now check with our layer mapSettings.setLayers( QStringList() << mVL->id() ); u.setMapSettings( mapSettings ); QgsPointLocator::Match m2 = u.snapToMap( QPoint( 100, 100 ) ); QVERIFY( m2.isValid() ); QCOMPARE( m2.point(), QgsPoint( 1, 0 ) ); }
void testNearestVertex() { QgsPointLocator loc( mVL ); QgsPointXY pt( 2, 2 ); QgsPointLocator::Match m = loc.nearestVertex( pt, 999 ); QVERIFY( m.isValid() ); QVERIFY( m.hasVertex() ); QCOMPARE( m.layer(), mVL ); QCOMPARE( m.featureId(), ( QgsFeatureId )1 ); QCOMPARE( m.point(), QgsPointXY( 1, 1 ) ); QCOMPARE( m.distance(), std::sqrt( 2.0 ) ); QCOMPARE( m.vertexIndex(), 2 ); }
void testSnapOnIntersection() { // testing with a layer with two crossing linestrings // (0,1) x x (1,1) // \/ // /\ . // (0,0) x x (1,0) QgsVectorLayer* vl = new QgsVectorLayer( QStringLiteral( "LineString" ), QStringLiteral( "x" ), QStringLiteral( "memory" ) ); QgsPolyline polyline1, polyline2; polyline1 << QgsPoint( 0, 0 ) << QgsPoint( 1, 1 ); polyline2 << QgsPoint( 1, 0 ) << QgsPoint( 0, 1 ); QgsFeature f1; QgsGeometry f1g = QgsGeometry::fromPolyline( polyline1 ) ; f1.setGeometry( f1g ); QgsFeature f2; QgsGeometry f2g = QgsGeometry::fromPolyline( polyline2 ); f2.setGeometry( f2g ); QgsFeatureList flist; flist << f1 << f2; vl->dataProvider()->addFeatures( flist ); QVERIFY( vl->dataProvider()->featureCount() == 2 ); QgsMapSettings mapSettings; mapSettings.setOutputSize( QSize( 100, 100 ) ); mapSettings.setExtent( QgsRectangle( 0, 0, 1, 1 ) ); QVERIFY( mapSettings.hasValidSettings() ); QgsSnappingUtils u; u.setMapSettings( mapSettings ); QgsSnappingConfig snappingConfig = u.config(); snappingConfig.setMode( QgsSnappingConfig::AdvancedConfiguration ); QgsSnappingConfig::IndividualLayerSettings layerSettings( true, QgsSnappingConfig::Vertex, 0.1, QgsTolerance::ProjectUnits ); snappingConfig.setIndividualLayerSettings( vl, layerSettings ); u.setConfig( snappingConfig ); // no snapping on intersections by default - should find nothing QgsPointLocator::Match m = u.snapToMap( QgsPoint( 0.45, 0.5 ) ); QVERIFY( !m.isValid() ); snappingConfig.setIntersectionSnapping( true ); u.setConfig( snappingConfig ); QgsPointLocator::Match m2 = u.snapToMap( QgsPoint( 0.45, 0.5 ) ); QVERIFY( m2.isValid() ); QCOMPARE( m2.type(), QgsPointLocator::Vertex ); QCOMPARE( m2.point(), QgsPoint( 0.5, 0.5 ) ); delete vl; }
void testPointInPolygon() { QgsPointLocator loc( mVL ); QgsPointLocator::MatchList mValid = loc.pointInPolygon( QgsPointXY( 0.8, 0.8 ) ); QCOMPARE( mValid.count(), 1 ); QgsPointLocator::Match m = mValid[0]; QVERIFY( m.isValid() ); QVERIFY( m.hasArea() ); QCOMPARE( m.layer(), mVL ); QCOMPARE( m.featureId(), ( QgsFeatureId )1 ); QgsPointLocator::MatchList mInvalid = loc.pointInPolygon( QgsPointXY( 0, 0 ) ); QCOMPARE( mInvalid.count(), 0 ); }
QList<QgsPoint> QgsMapMouseEvent::snapSegment( SnappingMode snappingMode, bool* snapped , bool allLayers ) const { QList<QgsPoint> segment; QgsPoint pt1, pt2; // If there's a cached snapping result we use it if ( snappingMode == mSnappingMode && mSnapMatch.hasEdge() ) { mSnapMatch.edgePoints( pt1, pt2 ); segment << pt1 << pt2; } else if ( snappingMode != NoSnapping ) { QgsPointLocator::Match match; if ( snappingMode == SnapProjectConfig && !allLayers ) { // run snapToMap with only segments EdgesOnlyFilter filter; match = mMapCanvas->snappingUtils()->snapToMap( mOriginalMapPoint, &filter ); } else if ( snappingMode == SnapAllLayers || allLayers ) { // run snapToMap with only edges on all layers QgsSnappingUtils* snappingUtils = mMapCanvas->snappingUtils(); QgsSnappingUtils::SnapToMapMode canvasMode = snappingUtils->snapToMapMode(); int type; double tolerance; QgsTolerance::UnitType unit; snappingUtils->defaultSettings( type, tolerance, unit ); snappingUtils->setSnapToMapMode( QgsSnappingUtils::SnapAllLayers ); snappingUtils->setDefaultSettings( QgsPointLocator::Edge, tolerance, unit ); match = snappingUtils->snapToMap( mOriginalMapPoint ); snappingUtils->setSnapToMapMode( canvasMode ); snappingUtils->setDefaultSettings( type, tolerance, unit ); } if ( match.isValid() && match.hasEdge() ) { match.edgePoints( pt1, pt2 ); segment << pt1 << pt2; } } if ( snapped ) { *snapped = segment.count() == 2; } return segment; }
void testSnapModeCurrent() { QgsMapSettings mapSettings; mapSettings.setOutputSize( QSize( 100, 100 ) ); mapSettings.setExtent( QgsRectangle( 0, 0, 1, 1 ) ); QVERIFY( mapSettings.hasValidSettings() ); QgsSnappingUtils u; u.setMapSettings( mapSettings ); u.setCurrentLayer( mVL ); // first try with no snapping enabled QgsSnappingConfig snappingConfig = u.config(); snappingConfig.setEnabled( false ); snappingConfig.setTolerance( 10 ); snappingConfig.setUnits( QgsTolerance::Pixels ); snappingConfig.setMode( QgsSnappingConfig::ActiveLayer ); u.setConfig( snappingConfig ); QgsPointLocator::Match m0 = u.snapToMap( QPoint( 100, 100 ) ); QVERIFY( !m0.isValid() ); QVERIFY( !m0.hasVertex() ); // now enable snapping snappingConfig.setEnabled( true ); snappingConfig.setType( QgsSnappingConfig::Vertex ); u.setConfig( snappingConfig ); QgsPointLocator::Match m = u.snapToMap( QPoint( 100, 100 ) ); QVERIFY( m.isValid() ); QVERIFY( m.hasVertex() ); QCOMPARE( m.point(), QgsPoint( 1, 0 ) ); QgsPointLocator::Match m2 = u.snapToMap( QPoint( 0, 100 ) ); QVERIFY( !m2.isValid() ); QVERIFY( !m2.hasVertex() ); // do not consider edges in the following test - on 32-bit platforms // result was an edge match very close to (1,0) instead of being exactly (1,0) snappingConfig.setType( QgsSnappingConfig::Vertex ); u.setConfig( snappingConfig ); // test with filtering FilterExcludePoint myFilter( QgsPoint( 1, 0 ) ); QgsPointLocator::Match m3 = u.snapToMap( QPoint( 100, 100 ), &myFilter ); QVERIFY( !m3.isValid() ); }
void testNearestEdge() { QgsPointLocator loc( mVL ); QgsPointXY pt( 1.1, 0.5 ); QgsPointLocator::Match m = loc.nearestEdge( pt, 999 ); QVERIFY( m.isValid() ); QVERIFY( m.hasEdge() ); QCOMPARE( m.layer(), mVL ); QCOMPARE( m.featureId(), ( QgsFeatureId )1 ); QCOMPARE( m.point(), QgsPointXY( 1, 0.5 ) ); QCOMPARE( m.distance(), 0.1 ); QCOMPARE( m.vertexIndex(), 1 ); QgsPointXY pt1, pt2; m.edgePoints( pt1, pt2 ); QCOMPARE( pt1, QgsPointXY( 1, 0 ) ); QCOMPARE( pt2, QgsPointXY( 1, 1 ) ); }
int QgsMapToolCapture::fetchLayerPoint( const QgsPointLocator::Match &match, QgsPoint &layerPoint ) { QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mCanvas->currentLayer() ); QgsVectorLayer *sourceLayer = match.layer(); if ( match.isValid() && match.hasVertex() && sourceLayer && ( sourceLayer->crs() == vlayer->crs() ) ) { QgsFeature f; QgsFeatureRequest request; request.setFilterFid( match.featureId() ); bool fetched = match.layer()->getFeatures( request ).nextFeature( f ); if ( fetched ) { QgsVertexId vId; if ( !f.geometry().vertexIdFromVertexNr( match.vertexIndex(), vId ) ) return 2; layerPoint = f.geometry().constGet()->vertexAt( vId ); // ZM support depends on the target layer if ( !QgsWkbTypes::hasZ( vlayer->wkbType() ) ) { layerPoint.dropZValue(); } if ( !QgsWkbTypes::hasM( vlayer->wkbType() ) ) { layerPoint.dropMValue(); } return 0; } else { return 2; } } else { return 1; } }
void testSnapModeAdvanced() { QgsMapSettings mapSettings; mapSettings.setOutputSize( QSize( 100, 100 ) ); mapSettings.setExtent( QgsRectangle( 0, 0, 1, 1 ) ); QVERIFY( mapSettings.hasValidSettings() ); QgsSnappingUtils u; QgsSnappingConfig snappingConfig = u.config(); u.setMapSettings( mapSettings ); snappingConfig.setMode( QgsSnappingConfig::AdvancedConfiguration ); snappingConfig.setIndividualLayerSettings( mVL, QgsSnappingConfig::IndividualLayerSettings( true, QgsSnappingConfig::Vertex, 10, QgsTolerance::Pixels ) ); u.setConfig( snappingConfig ); QgsPointLocator::Match m = u.snapToMap( QPoint( 100, 100 ) ); QVERIFY( m.isValid() ); QVERIFY( m.hasVertex() ); QCOMPARE( m.point(), QgsPoint( 1, 0 ) ); // test with filtering FilterExcludePoint myFilter( QgsPoint( 1, 0 ) ); QgsPointLocator::Match m2 = u.snapToMap( QPoint( 100, 100 ), &myFilter ); QVERIFY( !m2.isValid() ); }
void testSnapModeAdvanced() { QgsMapSettings mapSettings; mapSettings.setOutputSize( QSize( 100, 100 ) ); mapSettings.setExtent( QgsRectangle( 0, 0, 1, 1 ) ); QVERIFY( mapSettings.hasValidSettings() ); QgsSnappingUtils u; u.setMapSettings( mapSettings ); u.setSnapToMapMode( QgsSnappingUtils::SnapAdvanced ); QList<QgsSnappingUtils::LayerConfig> layers; layers << QgsSnappingUtils::LayerConfig( mVL, QgsPointLocator::Vertex, 10, QgsTolerance::Pixels ); u.setLayers( layers ); QgsPointLocator::Match m = u.snapToMap( QPoint( 100, 100 ) ); QVERIFY( m.isValid() ); QVERIFY( m.hasVertex() ); QCOMPARE( m.point(), QgsPoint( 1, 0 ) ); // test with filtering FilterExcludePoint myFilter( QgsPoint( 1, 0 ) ); QgsPointLocator::Match m2 = u.snapToMap( QPoint( 100, 100 ), &myFilter ); QVERIFY( !m2.isValid() ); }
QgsGeometry* QgsMapToolDeletePart::partUnderPoint( QPoint point, QgsFeatureId& fid, int& partNum ) { QgsFeature f; QgsGeometry* geomPart = new QgsGeometry(); switch ( vlayer->geometryType() ) { case QGis::Point: case QGis::Line: { QgsPointLocator::Match match = mCanvas->snappingUtils()->snapToCurrentLayer( point, QgsPointLocator::Vertex | QgsPointLocator::Edge ); if ( !match.isValid() ) return geomPart; int snapVertex = match.vertexIndex(); vlayer->getFeatures( QgsFeatureRequest().setFilterFid( match.featureId() ) ).nextFeature( f ); const QgsGeometry* g = f.constGeometry(); if ( !g->isMultipart() ) { fid = match.featureId(); delete geomPart; return QgsGeometry::fromPoint( match.point() ); } if ( g->wkbType() == QGis::WKBMultiPoint || g->wkbType() == QGis::WKBMultiPoint25D ) { fid = match.featureId(); partNum = snapVertex; delete geomPart; return QgsGeometry::fromPoint( match.point() ); } if ( g->wkbType() == QGis::WKBMultiLineString || g->wkbType() == QGis::WKBMultiLineString25D ) { QgsMultiPolyline mline = g->asMultiPolyline(); for ( int part = 0; part < mline.count(); part++ ) { if ( snapVertex < mline[part].count() ) { fid = match.featureId(); partNum = part; delete geomPart; return QgsGeometry::fromPolyline( mline[part] ); } snapVertex -= mline[part].count(); } } break; } case QGis::Polygon: { QgsPoint layerCoords = toLayerCoordinates( vlayer, point ); double searchRadius = QgsTolerance::vertexSearchRadius( mCanvas->currentLayer(), mCanvas->mapSettings() ); QgsRectangle selectRect( layerCoords.x() - searchRadius, layerCoords.y() - searchRadius, layerCoords.x() + searchRadius, layerCoords.y() + searchRadius ); QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setFilterRect( selectRect ) ); fit.nextFeature( f ); const QgsGeometry* g = f.constGeometry(); if ( !g ) return geomPart; if ( !g->isMultipart() ) { fid = f.id(); return geomPart; } QgsMultiPolygon mpolygon = g->asMultiPolygon(); for ( int part = 0; part < mpolygon.count(); part++ ) // go through the polygons { const QgsPolygon& polygon = mpolygon[part]; QgsGeometry* partGeo = QgsGeometry::fromPolygon( polygon ); if ( partGeo->contains( &layerCoords ) ) { fid = f.id(); partNum = part; delete geomPart; return partGeo; } delete partGeo; } break; } default: { break; } } return geomPart; }
QgsCadUtils::AlignMapPointOutput QgsCadUtils::alignMapPoint( const QgsPointXY &originalMapPoint, const QgsCadUtils::AlignMapPointContext &ctx ) { QgsCadUtils::AlignMapPointOutput res; res.valid = true; res.softLockCommonAngle = -1; // try to snap to anything QgsPointLocator::Match snapMatch = ctx.snappingUtils->snapToMap( originalMapPoint ); QgsPointXY point = snapMatch.isValid() ? snapMatch.point() : originalMapPoint; // try to snap explicitly to a segment - useful for some constraints QgsPointXY edgePt0, edgePt1; EdgesOnlyFilter edgesOnlyFilter; QgsPointLocator::Match edgeMatch = ctx.snappingUtils->snapToMap( originalMapPoint, &edgesOnlyFilter ); if ( edgeMatch.hasEdge() ) edgeMatch.edgePoints( edgePt0, edgePt1 ); res.edgeMatch = edgeMatch; QgsPointXY previousPt, penultimatePt; if ( ctx.cadPointList.count() >= 2 ) previousPt = ctx.cadPointList.at( 1 ); if ( ctx.cadPointList.count() >= 3 ) penultimatePt = ctx.cadPointList.at( 2 ); // ***************************** // ---- X constraint if ( ctx.xConstraint.locked ) { if ( !ctx.xConstraint.relative ) { point.setX( ctx.xConstraint.value ); } else if ( ctx.cadPointList.count() >= 2 ) { point.setX( previousPt.x() + ctx.xConstraint.value ); } if ( edgeMatch.hasEdge() && !ctx.yConstraint.locked ) { // intersect with snapped segment line at X ccordinate const double dx = edgePt1.x() - edgePt0.x(); if ( dx == 0 ) { point.setY( edgePt0.y() ); } else { const double dy = edgePt1.y() - edgePt0.y(); point.setY( edgePt0.y() + ( dy * ( point.x() - edgePt0.x() ) ) / dx ); } } } // ***************************** // ---- Y constraint if ( ctx.yConstraint.locked ) { if ( !ctx.yConstraint.relative ) { point.setY( ctx.yConstraint.value ); } else if ( ctx.cadPointList.count() >= 2 ) { point.setY( previousPt.y() + ctx.yConstraint.value ); } if ( edgeMatch.hasEdge() && !ctx.xConstraint.locked ) { // intersect with snapped segment line at Y ccordinate const double dy = edgePt1.y() - edgePt0.y(); if ( dy == 0 ) { point.setX( edgePt0.x() ); } else { const double dx = edgePt1.x() - edgePt0.x(); point.setX( edgePt0.x() + ( dx * ( point.y() - edgePt0.y() ) ) / dy ); } } } // ***************************** // ---- Common Angle constraint if ( !ctx.angleConstraint.locked && ctx.cadPointList.count() >= 2 && ctx.commonAngleConstraint.locked && ctx.commonAngleConstraint.value != 0 ) { double commonAngle = ctx.commonAngleConstraint.value * M_PI / 180; // see if soft common angle constraint should be performed // only if not in HardLock mode double softAngle = std::atan2( point.y() - previousPt.y(), point.x() - previousPt.x() ); double deltaAngle = 0; if ( ctx.commonAngleConstraint.relative && ctx.cadPointList.count() >= 3 ) { // compute the angle relative to the last segment (0° is aligned with last segment) deltaAngle = std::atan2( previousPt.y() - penultimatePt.y(), previousPt.x() - penultimatePt.x() ); softAngle -= deltaAngle; } int quo = std::round( softAngle / commonAngle ); if ( std::fabs( softAngle - quo * commonAngle ) * 180.0 * M_1_PI <= SOFT_CONSTRAINT_TOLERANCE_DEGREES ) { // also check the distance in pixel to the line, otherwise it's too sticky at long ranges softAngle = quo * commonAngle; // http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html // use the direction vector (cos(a),sin(a)) from previous point. |x2-x1|=1 since sin2+cos2=1 const double dist = std::fabs( std::cos( softAngle + deltaAngle ) * ( previousPt.y() - point.y() ) - std::sin( softAngle + deltaAngle ) * ( previousPt.x() - point.x() ) ); if ( dist / ctx.mapUnitsPerPixel < SOFT_CONSTRAINT_TOLERANCE_PIXEL ) { res.softLockCommonAngle = 180.0 / M_PI * softAngle; } } } // angle can be locked in one of the two ways: // 1. "hard" lock defined by the user // 2. "soft" lock from common angle (e.g. 45 degrees) bool angleLocked = false, angleRelative = false; int angleValueDeg = 0; if ( ctx.angleConstraint.locked ) { angleLocked = true; angleRelative = ctx.angleConstraint.relative; angleValueDeg = ctx.angleConstraint.value; } else if ( res.softLockCommonAngle != -1 ) { angleLocked = true; angleRelative = ctx.commonAngleConstraint.relative; angleValueDeg = res.softLockCommonAngle; } // ***************************** // ---- Angle constraint // input angles are in degrees if ( angleLocked ) { double angleValue = angleValueDeg * M_PI / 180; if ( angleRelative && ctx.cadPointList.count() >= 3 ) { // compute the angle relative to the last segment (0° is aligned with last segment) angleValue += std::atan2( previousPt.y() - penultimatePt.y(), previousPt.x() - penultimatePt.x() ); } double cosa = std::cos( angleValue ); double sina = std::sin( angleValue ); double v = ( point.x() - previousPt.x() ) * cosa + ( point.y() - previousPt.y() ) * sina; if ( ctx.xConstraint.locked && ctx.yConstraint.locked ) { // do nothing if both X,Y are already locked } else if ( ctx.xConstraint.locked ) { if ( qgsDoubleNear( cosa, 0.0 ) ) { res.valid = false; } else { double x = ctx.xConstraint.value; if ( !ctx.xConstraint.relative ) { x -= previousPt.x(); } point.setY( previousPt.y() + x * sina / cosa ); } } else if ( ctx.yConstraint.locked ) { if ( qgsDoubleNear( sina, 0.0 ) ) { res.valid = false; } else { double y = ctx.yConstraint.value; if ( !ctx.yConstraint.relative ) { y -= previousPt.y(); } point.setX( previousPt.x() + y * cosa / sina ); } } else { point.setX( previousPt.x() + cosa * v ); point.setY( previousPt.y() + sina * v ); } if ( edgeMatch.hasEdge() && !ctx.distanceConstraint.locked ) { // magnetize to the intersection of the snapped segment and the lockedAngle // line of previous point + locked angle const double x1 = previousPt.x(); const double y1 = previousPt.y(); const double x2 = previousPt.x() + cosa; const double y2 = previousPt.y() + sina; // line of snapped segment const double x3 = edgePt0.x(); const double y3 = edgePt0.y(); const double x4 = edgePt1.x(); const double y4 = edgePt1.y(); const double d = ( x1 - x2 ) * ( y3 - y4 ) - ( y1 - y2 ) * ( x3 - x4 ); // do not compute intersection if lines are almost parallel // this threshold might be adapted if ( std::fabs( d ) > 0.01 ) { point.setX( ( ( x3 - x4 ) * ( x1 * y2 - y1 * x2 ) - ( x1 - x2 ) * ( x3 * y4 - y3 * x4 ) ) / d ); point.setY( ( ( y3 - y4 ) * ( x1 * y2 - y1 * x2 ) - ( y1 - y2 ) * ( x3 * y4 - y3 * x4 ) ) / d ); } } } // ***************************** // ---- Distance constraint if ( ctx.distanceConstraint.locked && ctx.cadPointList.count() >= 2 ) { if ( ctx.xConstraint.locked || ctx.yConstraint.locked ) { // perform both to detect errors in constraints if ( ctx.xConstraint.locked ) { QgsPointXY verticalPt0( ctx.xConstraint.value, point.y() ); QgsPointXY verticalPt1( ctx.xConstraint.value, point.y() + 1 ); res.valid &= lineCircleIntersection( previousPt, ctx.distanceConstraint.value, verticalPt0, verticalPt1, point ); } if ( ctx.yConstraint.locked ) { QgsPointXY horizontalPt0( point.x(), ctx.yConstraint.value ); QgsPointXY horizontalPt1( point.x() + 1, ctx.yConstraint.value ); res.valid &= lineCircleIntersection( previousPt, ctx.distanceConstraint.value, horizontalPt0, horizontalPt1, point ); } } else { const double dist = std::sqrt( point.sqrDist( previousPt ) ); if ( dist == 0 ) { // handle case where mouse is over origin and distance constraint is enabled // take arbitrary horizontal line point.set( previousPt.x() + ctx.distanceConstraint.value, previousPt.y() ); } else { const double vP = ctx.distanceConstraint.value / dist; point.set( previousPt.x() + ( point.x() - previousPt.x() ) * vP, previousPt.y() + ( point.y() - previousPt.y() ) * vP ); } if ( edgeMatch.hasEdge() && !ctx.angleConstraint.locked ) { // we will magnietize to the intersection of that segment and the lockedDistance ! res.valid &= lineCircleIntersection( previousPt, ctx.distanceConstraint.value, edgePt0, edgePt1, point ); } } } // ***************************** // ---- calculate CAD values QgsDebugMsgLevel( QString( "point: %1 %2" ).arg( point.x() ).arg( point.y() ), 4 ); QgsDebugMsgLevel( QString( "previous point: %1 %2" ).arg( previousPt.x() ).arg( previousPt.y() ), 4 ); QgsDebugMsgLevel( QString( "penultimate point: %1 %2" ).arg( penultimatePt.x() ).arg( penultimatePt.y() ), 4 ); //QgsDebugMsg( QString( "dx: %1 dy: %2" ).arg( point.x() - previousPt.x() ).arg( point.y() - previousPt.y() ) ); //QgsDebugMsg( QString( "ddx: %1 ddy: %2" ).arg( previousPt.x() - penultimatePt.x() ).arg( previousPt.y() - penultimatePt.y() ) ); res.finalMapPoint = point; return res; }
QgsPoint QgsMapToolMeasureAngle::snapPoint( const QPoint& p ) { QgsPointLocator::Match m = mCanvas->snappingUtils()->snapToMap( p ); return m.isValid() ? m.point() : mCanvas->getCoordinateTransform()->toMapCoordinates( p ); }
void QgsMapToolSplitFeatures::cadCanvasReleaseEvent( QgsMapMouseEvent * e ) { //check if we operate on a vector layer QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mCanvas->currentLayer() ); if ( !vlayer ) { notifyNotVectorLayer(); return; } if ( !vlayer->isEditable() ) { notifyNotEditableLayer(); return; } bool split = false; //add point to list and to rubber band if ( e->button() == Qt::LeftButton ) { //If we snap the first point on a vertex of a line layer, we directly split the feature at this point if ( vlayer->geometryType() == QgsWkbTypes::LineGeometry && points().isEmpty() ) { QgsPointLocator::Match m = mCanvas->snappingUtils()->snapToCurrentLayer( e->pos(), QgsPointLocator::Vertex ); if ( m.isValid() ) { split = true; } } int error = addVertex( e->mapPoint(), e->mapPointMatch() ); if ( error == 1 ) { //current layer is not a vector layer return; } else if ( error == 2 ) { //problem with coordinate transformation QgisApp::instance()->messageBar()->pushMessage( tr( "Coordinate transform error" ), tr( "Cannot transform the point to the layers coordinate system" ), QgsMessageBar::INFO, QgisApp::instance()->messageTimeout() ); return; } startCapturing(); } else if ( e->button() == Qt::RightButton ) { split = true; } if ( split ) { deleteTempRubberBand(); //bring up dialog if a split was not possible (polygon) or only done once (line) int topologicalEditing = QgsProject::instance()->topologicalEditing(); vlayer->beginEditCommand( tr( "Features split" ) ); int returnCode = vlayer->splitFeatures( points(), topologicalEditing ); vlayer->endEditCommand(); if ( returnCode == 4 ) { QgisApp::instance()->messageBar()->pushMessage( tr( "No features were split" ), tr( "If there are selected features, the split tool only applies to those. If you would like to split all features under the split line, clear the selection." ), QgsMessageBar::WARNING, QgisApp::instance()->messageTimeout() ); } else if ( returnCode == 3 ) { QgisApp::instance()->messageBar()->pushMessage( tr( "No feature split done" ), tr( "Cut edges detected. Make sure the line splits features into multiple parts." ), QgsMessageBar::WARNING, QgisApp::instance()->messageTimeout() ); } else if ( returnCode == 7 ) { QgisApp::instance()->messageBar()->pushMessage( tr( "No feature split done" ), tr( "The geometry is invalid. Please repair before trying to split it." ), QgsMessageBar::WARNING, QgisApp::instance()->messageTimeout() ); } else if ( returnCode != 0 ) { //several intersections but only one split (most likely line) QgisApp::instance()->messageBar()->pushMessage( tr( "No feature split done" ), tr( "An error occurred during splitting." ), QgsMessageBar::WARNING, QgisApp::instance()->messageTimeout() ); } stopCapturing(); } }
void QgsMapToolNodeTool::canvasMoveEvent( QMouseEvent * e ) { if ( !mSelectedFeature || !mClicked ) return; QgsVectorLayer* vlayer = mSelectedFeature->vlayer(); Q_ASSERT( vlayer ); mSelectAnother = false; if ( mMoving ) { // create rubberband, if none exists if ( mRubberBands.empty() ) { if ( mIsPoint ) { QList<QgsVertexEntry*> &vertexMap = mSelectedFeature->vertexMap(); for ( int i = 0; i < vertexMap.size(); i++ ) { if ( vertexMap[i]->isSelected() ) { QgsRubberBand* rb = createRubberBandMarker( vertexMap[i]->point(), vlayer ); mRubberBands.append( rb ); } } } createMovingRubberBands(); mPosMapCoordBackup = toMapCoordinates( e->pos() ); } else { // move rubberband QgsPoint posMapCoord, pressMapCoords; QgsExcludePointFilter excludePointFilter( mClosestMapVertex ); QgsPointLocator::Match match = mCanvas->snappingUtils()->snapToMap( e->pos(), &excludePointFilter ); if ( match.isValid() ) { posMapCoord = match.point(); pressMapCoords = mClosestMapVertex; } else { posMapCoord = toMapCoordinates( e->pos() ); pressMapCoords = toMapCoordinates( mPressCoordinates ); } QgsVector offset = posMapCoord - pressMapCoords; // handle points if ( mIsPoint ) { for ( int i = 0; i < mRubberBands.size(); i++ ) { mRubberBands[i]->setTranslationOffset( offset.x(), offset.y() ); } return; } // move points QList<QgsVertexEntry*> &vertexMap = mSelectedFeature->vertexMap(); for ( int i = 0; i < vertexMap.size(); i++ ) { if ( !vertexMap[i]->isSelected() ) continue; QgsPoint p = toMapCoordinates( vlayer, vertexMap[i]->point() ) + offset; mRubberBands[vertexMap[i]->rubberBandNr()]->movePoint( vertexMap[i]->rubberBandIndex(), p ); if ( vertexMap[i]->rubberBandIndex() == 0 ) { mRubberBands[vertexMap[i]->rubberBandNr()]->movePoint( 0, p ); } } // topological editing offset = posMapCoord - mPosMapCoordBackup; for ( int i = 0; i < mTopologyRubberBand.size(); i++ ) { for ( int pointIndex = 0; pointIndex < mTopologyRubberBand[i]->numberOfVertices(); pointIndex++ ) { if ( mTopologyRubberBandVertexes[i]->contains( pointIndex ) ) { const QgsPoint* point = mTopologyRubberBand[i]->getPoint( 0, pointIndex ); if ( point == 0 ) { break; } mTopologyRubberBand[i]->movePoint( pointIndex, *point + offset ); } } } mPosMapCoordBackup = posMapCoord; } } else { if ( !mSelectionRectangle ) { mSelectionRectangle = true; mSelectionRubberBand = new QRubberBand( QRubberBand::Rectangle, mCanvas ); mRect = new QRect(); mRect->setTopLeft( mPressCoordinates ); } mRect->setBottomRight( e->pos() ); QRect normalizedRect = mRect->normalized(); mSelectionRubberBand->setGeometry( normalizedRect ); mSelectionRubberBand->show(); } }
void QgsAdvancedDigitizingCanvasItem::paint( QPainter *painter ) { if ( !mAdvancedDigitizingDockWidget->cadEnabled() ) return; QgsRectangle mapRect = mMapCanvas->extent(); if ( rect() != mapRect ) setRect( mapRect ); int nPoints = mAdvancedDigitizingDockWidget->pointsCount(); if ( !nPoints ) return; bool previousPointExist, penulPointExist; const QgsPointXY curPoint = mAdvancedDigitizingDockWidget->currentPoint(); const QgsPointXY prevPoint = mAdvancedDigitizingDockWidget->previousPoint( &previousPointExist ); const QgsPointXY penulPoint = mAdvancedDigitizingDockWidget->penultimatePoint( &penulPointExist ); const bool snappedToVertex = mAdvancedDigitizingDockWidget->snappedToVertex(); const QList<QgsPointXY> snappedSegment = mAdvancedDigitizingDockWidget->snappedSegment(); const bool hasSnappedSegment = snappedSegment.count() == 2; const bool curPointExist = mapRect.contains( curPoint ); const double mupp = mMapCanvas->getCoordinateTransform()->mapUnitsPerPixel(); if ( mupp == 0 ) return; QPointF curPointPix, prevPointPix, penulPointPix, snapSegmentPix1, snapSegmentPix2; if ( curPointExist ) { curPointPix = toCanvasCoordinates( curPoint ); } if ( previousPointExist ) { prevPointPix = toCanvasCoordinates( prevPoint ); } if ( penulPointExist ) { penulPointPix = toCanvasCoordinates( penulPoint ); } if ( hasSnappedSegment ) { snapSegmentPix1 = toCanvasCoordinates( snappedSegment[0] ); snapSegmentPix2 = toCanvasCoordinates( snappedSegment[1] ); } painter->setRenderHint( QPainter::Antialiasing ); painter->setCompositionMode( QPainter::CompositionMode_Difference ); // Draw point snap if ( curPointExist && snappedToVertex ) { painter->setPen( mSnapPen ); painter->drawEllipse( curPointPix, 10, 10 ); } // Draw segment snap if ( hasSnappedSegment && !snappedToVertex ) { painter->setPen( mSnapPen ); painter->drawLine( snapSegmentPix1, snapSegmentPix2 ); if ( curPointExist ) { painter->setPen( mSnapLinePen ); painter->drawLine( snapSegmentPix1, curPointPix ); } } // Draw segment par/per input if ( mAdvancedDigitizingDockWidget->additionalConstraint() != QgsAdvancedDigitizingDockWidget::NoConstraint && hasSnappedSegment ) { painter->setPen( mConstruction2Pen ); painter->drawLine( snapSegmentPix1, snapSegmentPix2 ); } // Draw angle if ( nPoints > 1 ) { double a0, a; if ( mAdvancedDigitizingDockWidget->constraintAngle()->relative() && nPoints > 2 ) { a0 = std::atan2( -( prevPoint.y() - penulPoint.y() ), prevPoint.x() - penulPoint.x() ); } else { a0 = 0; } if ( mAdvancedDigitizingDockWidget->constraintAngle()->isLocked() ) { a = a0 - mAdvancedDigitizingDockWidget->constraintAngle()->value() * M_PI / 180; } else { a = std::atan2( -( curPoint.y() - prevPoint.y() ), curPoint.x() - prevPoint.x() ); } painter->setPen( mConstruction2Pen ); painter->drawArc( QRectF( prevPointPix.x() - 20, prevPointPix.y() - 20, 40, 40 ), static_cast<int>( 16 * -a0 * 180 / M_PI ), static_cast<int>( 16 * ( a0 - a ) * 180 / M_PI ) ); painter->drawLine( prevPointPix, prevPointPix + 60 * QPointF( std::cos( a0 ), std::sin( a0 ) ) ); if ( mAdvancedDigitizingDockWidget->constraintAngle()->isLocked() ) { painter->setPen( mLockedPen ); double d = std::max( boundingRect().width(), boundingRect().height() ); painter->drawLine( prevPointPix - d * QPointF( std::cos( a ), std::sin( a ) ), prevPointPix + d * QPointF( std::cos( a ), std::sin( a ) ) ); } } // Draw distance if ( nPoints > 1 && mAdvancedDigitizingDockWidget->constraintDistance()->isLocked() ) { painter->setPen( mLockedPen ); double r = mAdvancedDigitizingDockWidget->constraintDistance()->value() / mupp; painter->drawEllipse( prevPointPix, r, r ); } // Draw x if ( mAdvancedDigitizingDockWidget->constraintX()->isLocked() ) { double x = 0.0; bool draw = true; painter->setPen( mLockedPen ); if ( mAdvancedDigitizingDockWidget->constraintX()->relative() ) { if ( nPoints > 1 ) { x = mAdvancedDigitizingDockWidget->constraintX()->value() / mupp + prevPointPix.x(); } else { draw = false; } } else { x = toCanvasCoordinates( QgsPointXY( mAdvancedDigitizingDockWidget->constraintX()->value(), 0 ) ).x(); } if ( draw ) { painter->drawLine( QPointF( x, 0 ), QPointF( x, boundingRect().height() ) ); } } // Draw y if ( mAdvancedDigitizingDockWidget->constraintY()->isLocked() ) { double y = 0.0; bool draw = true; painter->setPen( mLockedPen ); if ( mAdvancedDigitizingDockWidget->constraintY()->relative() ) { if ( nPoints > 1 ) { // y is reversed! y = -mAdvancedDigitizingDockWidget->constraintY()->value() / mupp + prevPointPix.y(); } else { draw = false; } } else { y = toCanvasCoordinates( QgsPointXY( 0, mAdvancedDigitizingDockWidget->constraintY()->value() ) ).y(); } if ( draw ) { painter->drawLine( QPointF( 0, y ), QPointF( boundingRect().width(), y ) ); } } // Draw constr if ( mAdvancedDigitizingDockWidget->additionalConstraint() == QgsAdvancedDigitizingDockWidget::NoConstraint ) { if ( curPointExist && previousPointExist ) { painter->setPen( mConstruction2Pen ); painter->drawLine( prevPointPix, curPointPix ); } if ( previousPointExist && penulPointExist ) { painter->setPen( mConstruction1Pen ); painter->drawLine( penulPointPix, prevPointPix ); } } if ( curPointExist ) { painter->setPen( mCursorPen ); painter->drawLine( curPointPix + QPointF( -5, -5 ), curPointPix + QPointF( +5, +5 ) ); painter->drawLine( curPointPix + QPointF( -5, +5 ), curPointPix + QPointF( +5, -5 ) ); } QgsPointLocator::Match match = mAdvancedDigitizingDockWidget->mapPointMatch(); if ( match.isValid() ) { mSnapIndicator->setMatch( match ); mSnapIndicator->setVisible( true ); } else mSnapIndicator->setVisible( false ); }
QgsGeometry QgsMapToolDeletePart::partUnderPoint( QPoint point, QgsFeatureId &fid, int &partNum ) { QgsFeature f; QgsGeometry geomPart; switch ( vlayer->geometryType() ) { case QgsWkbTypes::PointGeometry: case QgsWkbTypes::LineGeometry: { QgsPointLocator::Match match = mCanvas->snappingUtils()->snapToCurrentLayer( point, QgsPointLocator::Types( QgsPointLocator::Vertex | QgsPointLocator::Edge ) ); if ( !match.isValid() ) return geomPart; int snapVertex = match.vertexIndex(); vlayer->getFeatures( QgsFeatureRequest().setFilterFid( match.featureId() ) ).nextFeature( f ); QgsGeometry g = f.geometry(); if ( !g.isMultipart() ) { fid = match.featureId(); return QgsGeometry::fromPointXY( match.point() ); } else if ( QgsWkbTypes::geometryType( g.wkbType() ) == QgsWkbTypes::PointGeometry ) { fid = match.featureId(); partNum = snapVertex; return QgsGeometry::fromPointXY( match.point() ); } else if ( QgsWkbTypes::geometryType( g.wkbType() ) == QgsWkbTypes::LineGeometry ) { QgsMultiPolylineXY mline = g.asMultiPolyline(); for ( int part = 0; part < mline.count(); part++ ) { if ( snapVertex < mline[part].count() ) { fid = match.featureId(); partNum = part; return QgsGeometry::fromPolylineXY( mline[part] ); } snapVertex -= mline[part].count(); } } break; } case QgsWkbTypes::PolygonGeometry: { QgsPointLocator::Match match = mCanvas->snappingUtils()->snapToCurrentLayer( point, QgsPointLocator::Area ); if ( !match.isValid() ) return geomPart; vlayer->getFeatures( QgsFeatureRequest().setFilterFid( match.featureId() ) ).nextFeature( f ); QgsGeometry g = f.geometry(); if ( g.isNull() ) return geomPart; QgsPointXY layerCoords = toLayerCoordinates( vlayer, point ); if ( !g.isMultipart() ) { fid = f.id(); return geomPart; } QgsMultiPolygonXY mpolygon = g.asMultiPolygon(); for ( int part = 0; part < mpolygon.count(); part++ ) // go through the polygons { const QgsPolygonXY &polygon = mpolygon[part]; QgsGeometry partGeo = QgsGeometry::fromPolygonXY( polygon ); if ( partGeo.contains( &layerCoords ) ) { fid = f.id(); partNum = part; return partGeo; } } break; } default: { break; } } return geomPart; }