// TODO: move to geometry utils (if not already there) static bool lineCircleIntersection( const QgsPointXY ¢er, const double radius, const QgsPointXY &edgePt0, const QgsPointXY &edgePt1, QgsPointXY &intersection ) { // formula taken from http://mathworld.wolfram.com/Circle-LineIntersection.html const double x1 = edgePt0.x() - center.x(); const double y1 = edgePt0.y() - center.y(); const double x2 = edgePt1.x() - center.x(); const double y2 = edgePt1.y() - center.y(); const double dx = x2 - x1; const double dy = y2 - y1; const double dr = std::sqrt( std::pow( dx, 2 ) + std::pow( dy, 2 ) ); const double d = x1 * y2 - x2 * y1; const double disc = std::pow( radius, 2 ) * std::pow( dr, 2 ) - std::pow( d, 2 ); if ( disc < 0 ) { //no intersection or tangent return false; } else { // two solutions const int sgnDy = dy < 0 ? -1 : 1; const double ax = center.x() + ( d * dy + sgnDy * dx * std::sqrt( std::pow( radius, 2 ) * std::pow( dr, 2 ) - std::pow( d, 2 ) ) ) / ( std::pow( dr, 2 ) ); const double ay = center.y() + ( -d * dx + std::fabs( dy ) * std::sqrt( std::pow( radius, 2 ) * std::pow( dr, 2 ) - std::pow( d, 2 ) ) ) / ( std::pow( dr, 2 ) ); const QgsPointXY p1( ax, ay ); const double bx = center.x() + ( d * dy - sgnDy * dx * std::sqrt( std::pow( radius, 2 ) * std::pow( dr, 2 ) - std::pow( d, 2 ) ) ) / ( std::pow( dr, 2 ) ); const double by = center.y() + ( -d * dx - std::fabs( dy ) * std::sqrt( std::pow( radius, 2 ) * std::pow( dr, 2 ) - std::pow( d, 2 ) ) ) / ( std::pow( dr, 2 ) ); const QgsPointXY p2( bx, by ); // snap to nearest intersection if ( intersection.sqrDist( p1 ) < intersection.sqrDist( p2 ) ) { intersection.set( p1.x(), p1.y() ); } else { intersection.set( p2.x(), p2.y() ); } return true; } }
void QgsAnnotation::updateBalloon() { //first test if the point is in the frame. In that case we don't need a balloon. if ( !mHasFixedMapPosition || ( mOffsetFromReferencePoint.x() < 0 && ( mOffsetFromReferencePoint.x() + mFrameSize.width() ) > 0 && mOffsetFromReferencePoint.y() < 0 && ( mOffsetFromReferencePoint.y() + mFrameSize.height() ) > 0 ) ) { mBalloonSegment = -1; return; } //edge list QList<QLineF> segmentList; segmentList << segment( 0 ); segmentList << segment( 1 ); segmentList << segment( 2 ); segmentList << segment( 3 ); //find closest edge / closest edge point double minEdgeDist = std::numeric_limits<double>::max(); int minEdgeIndex = -1; QLineF minEdge; QgsPointXY minEdgePoint; QgsPointXY origin( 0, 0 ); for ( int i = 0; i < 4; ++i ) { QLineF currentSegment = segmentList.at( i ); QgsPointXY currentMinDistPoint; double currentMinDist = origin.sqrDistToSegment( currentSegment.x1(), currentSegment.y1(), currentSegment.x2(), currentSegment.y2(), currentMinDistPoint ); if ( currentMinDist < minEdgeDist ) { minEdgeIndex = i; minEdgePoint = currentMinDistPoint; minEdgeDist = currentMinDist; minEdge = currentSegment; } } if ( minEdgeIndex < 0 ) { return; } //make that configurable for the item double segmentPointWidth = 10; mBalloonSegment = minEdgeIndex; QPointF minEdgeEnd = minEdge.p2(); mBalloonSegmentPoint1 = QPointF( minEdgePoint.x(), minEdgePoint.y() ); if ( std::sqrt( minEdgePoint.sqrDist( minEdgeEnd.x(), minEdgeEnd.y() ) ) < segmentPointWidth ) { mBalloonSegmentPoint1 = pointOnLineWithDistance( minEdge.p2(), minEdge.p1(), segmentPointWidth ); } mBalloonSegmentPoint2 = pointOnLineWithDistance( mBalloonSegmentPoint1, minEdge.p2(), 10 ); }
// return ratio [mu/lu] between map units and layer units // this is of course only an approximation double _ratioMU2LU( const QgsMapSettings &mapSettings, QgsMapLayer *layer ) { double distMU = mapSettings.mapUnitsPerPixel(); QgsPointXY ptMapCenterMU = mapSettings.visibleExtent().center(); QgsPointXY ptMapCenterRightMU( ptMapCenterMU.x() + distMU, ptMapCenterMU.y() ); QgsPointXY ptMapCenterLU = mapSettings.mapToLayerCoordinates( layer, ptMapCenterMU ); QgsPointXY ptMapCenterRightLU = mapSettings.mapToLayerCoordinates( layer, ptMapCenterRightMU ); double distLU = std::sqrt( ptMapCenterLU.sqrDist( ptMapCenterRightLU ) ); double ratio = distMU / distLU; return ratio; }
void QgsAdvancedDigitizingDockWidget::updateUnlockedConstraintValues( const QgsPointXY &point ) { bool previousPointExist, penulPointExist; QgsPointXY previousPt = previousPoint( &previousPointExist ); QgsPointXY penultimatePt = penultimatePoint( &penulPointExist ); // --- angle if ( !mAngleConstraint->isLocked() && previousPointExist ) { double angle = 0.0; if ( penulPointExist && mAngleConstraint->relative() ) { // previous angle angle = std::atan2( previousPt.y() - penultimatePt.y(), previousPt.x() - penultimatePt.x() ); } angle = ( std::atan2( point.y() - previousPt.y(), point.x() - previousPt.x() ) - angle ) * 180 / M_PI; // modulus angle = std::fmod( angle, 360.0 ); mAngleConstraint->setValue( angle ); } // --- distance if ( !mDistanceConstraint->isLocked() && previousPointExist ) { mDistanceConstraint->setValue( std::sqrt( previousPt.sqrDist( point ) ) ); } // --- X if ( !mXConstraint->isLocked() ) { if ( previousPointExist && mXConstraint->relative() ) { mXConstraint->setValue( point.x() - previousPt.x() ); } else { mXConstraint->setValue( point.x() ); } } // --- Y if ( !mYConstraint->isLocked() ) { if ( previousPointExist && mYConstraint->relative() ) { mYConstraint->setValue( point.y() - previousPt.y() ); } else { mYConstraint->setValue( point.y() ); } } }
bool ProjectorData::checkRows( const QgsCoordinateTransform &ct ) { if ( !ct.isValid() ) { return false; } for ( int r = 0; r < mCPRows; r++ ) { for ( int c = 1; c < mCPCols - 1; c += 2 ) { double myDestX, myDestY; destPointOnCPMatrix( r, c, &myDestX, &myDestY ); QgsPointXY myDestPoint( myDestX, myDestY ); QgsPointXY mySrcPoint1 = mCPMatrix[r][c - 1]; QgsPointXY mySrcPoint2 = mCPMatrix[r][c]; QgsPointXY mySrcPoint3 = mCPMatrix[r][c + 1]; QgsPointXY mySrcApprox( ( mySrcPoint1.x() + mySrcPoint3.x() ) / 2, ( mySrcPoint1.y() + mySrcPoint3.y() ) / 2 ); if ( !mCPLegalMatrix[r][c - 1] || !mCPLegalMatrix[r][c] || !mCPLegalMatrix[r][c + 1] ) { // There was an error earlier in transform, just abort return false; } try { QgsPointXY myDestApprox = ct.transform( mySrcApprox, QgsCoordinateTransform::ReverseTransform ); double mySqrDist = myDestApprox.sqrDist( myDestPoint ); if ( mySqrDist > mSqrTolerance ) { return false; } } catch ( QgsCsException &e ) { Q_UNUSED( e ); // Caught an error in transform return false; } } } return true; }
void ProjectorData::calcSrcRowsCols() { // Wee need to calculate minimum cell size in the source // TODO: Think it over better, what is the right source resolution? // Taking distances between cell centers projected to source along source // axis would result in very high resolution // TODO: different resolution for rows and cols ? double myMinSize = std::numeric_limits<double>::max(); if ( mApproximate ) { // For now, we take cell sizes projected to source but not to source axes double myDestColsPerMatrixCell = static_cast< double >( mDestCols ) / mCPCols; double myDestRowsPerMatrixCell = static_cast< double >( mDestRows ) / mCPRows; QgsDebugMsgLevel( QStringLiteral( "myDestColsPerMatrixCell = %1 myDestRowsPerMatrixCell = %2" ).arg( myDestColsPerMatrixCell ).arg( myDestRowsPerMatrixCell ), 4 ); for ( int i = 0; i < mCPRows - 1; i++ ) { for ( int j = 0; j < mCPCols - 1; j++ ) { QgsPointXY myPointA = mCPMatrix[i][j]; QgsPointXY myPointB = mCPMatrix[i][j + 1]; QgsPointXY myPointC = mCPMatrix[i + 1][j]; if ( mCPLegalMatrix[i][j] && mCPLegalMatrix[i][j + 1] && mCPLegalMatrix[i + 1][j] ) { double mySize = std::sqrt( myPointA.sqrDist( myPointB ) ) / myDestColsPerMatrixCell; if ( mySize < myMinSize ) myMinSize = mySize; mySize = std::sqrt( myPointA.sqrDist( myPointC ) ) / myDestRowsPerMatrixCell; if ( mySize < myMinSize ) myMinSize = mySize; } } } } else { // take highest from corners, points in in the middle of corners and center (3 x 3 ) //double QgsRectangle srcExtent; int srcXSize, srcYSize; if ( QgsRasterProjector::extentSize( mInverseCt, mDestExtent, mDestCols, mDestRows, srcExtent, srcXSize, srcYSize ) ) { double srcXRes = srcExtent.width() / srcXSize; double srcYRes = srcExtent.height() / srcYSize; myMinSize = std::min( srcXRes, srcYRes ); } else { QgsDebugMsg( QStringLiteral( "Cannot get src extent/size" ) ); } } // Make it a bit higher resolution // TODO: find the best coefficient, attention, increasing resolution for WMS // is changing WMS content myMinSize *= 0.75; QgsDebugMsgLevel( QStringLiteral( "mMaxSrcXRes = %1 mMaxSrcYRes = %2" ).arg( mMaxSrcXRes ).arg( mMaxSrcYRes ), 4 ); // mMaxSrcXRes, mMaxSrcYRes may be 0 - no limit (WMS) double myMinXSize = mMaxSrcXRes > myMinSize ? mMaxSrcXRes : myMinSize; double myMinYSize = mMaxSrcYRes > myMinSize ? mMaxSrcYRes : myMinSize; QgsDebugMsgLevel( QStringLiteral( "myMinXSize = %1 myMinYSize = %2" ).arg( myMinXSize ).arg( myMinYSize ), 4 ); QgsDebugMsgLevel( QStringLiteral( "mSrcExtent.width = %1 mSrcExtent.height = %2" ).arg( mSrcExtent.width() ).arg( mSrcExtent.height() ), 4 ); // we have to round to keep alignment set in calcSrcExtent mSrcRows = static_cast< int >( std::round( mSrcExtent.height() / myMinYSize ) ); mSrcCols = static_cast< int >( std::round( mSrcExtent.width() / myMinXSize ) ); QgsDebugMsgLevel( QStringLiteral( "mSrcRows = %1 mSrcCols = %2" ).arg( mSrcRows ).arg( mSrcCols ), 4 ); }
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; }