///////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2018- Equinor ASA // // ResInsight is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. // // See the GNU General Public License at // for more details. // ///////////////////////////////////////////////////////////////////////////////// #include "RigWellPathGeometryTools.h" #include "RigWellPath.h" #include "cvfMath.h" #include "cvfMatrix3.h" #include "RiaOffshoreSphericalCoords.h" #include "qwt_curve_fitter.h" #include "qwt_spline.h" #include "qwt_spline_curve_fitter.h" #include #include int RigWellPathGeometryTools::lookup( double x, const QPolygonF& values ) { #if 0 //qLowerBound/qHigherBound ??? #endif int i1; const int size = values.size(); if ( x <= values[0].x() ) i1 = 0; else if ( x >= values[size - 2].x() ) i1 = size - 2; else { i1 = 0; int i2 = size - 2; int i3 = 0; while ( i2 - i1 > 1 ) { i3 = i1 + ( ( i2 - i1 ) >> 1 ); if ( values[i3].x() > x ) i2 = i3; else i1 = i3; } } return i1; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- double RigWellPathGeometryTools::value( double x, const QPolygonF& values ) { if ( values.size() == 0 ) return 0.0; const int i = lookup( x, values ); if ( i >= values.size() - 1 ) return values.back().y(); auto low = values[i]; auto high = values[i + 1]; auto delta = ( x - low.x() ) / ( high.x() - low.x() ); return ( 1 - delta ) * low.y() + delta * high.y(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RigWellPathGeometryTools::calculateLineSegmentNormals( const std::vector& vertices, double planeAngle ) { std::vector pointNormals; if ( vertices.empty() ) return pointNormals; pointNormals.reserve( vertices.size() ); const cvf::Vec3d up( 0, 0, 1 ); const cvf::Vec3d rotatedUp = up.getTransformedVector( cvf::Mat3d::fromRotation( cvf::Vec3d( 0.0, 1.0, 0.0 ), planeAngle ) ); const cvf::Vec3d dominantDirection = estimateDominantDirectionInXYPlane( vertices ); const cvf::Vec3d projectionPlaneNormal = ( up ^ dominantDirection ).getNormalized(); CVF_ASSERT( projectionPlaneNormal * dominantDirection <= std::numeric_limits::epsilon() ); double sumDotWithRotatedUp = 0.0; for ( size_t i = 0; i < vertices.size() - 1; ++i ) { cvf::Vec3d p1 = vertices[i]; cvf::Vec3d p2 = vertices[i + 1]; cvf::Vec3d tangent = ( p2 - p1 ).getNormalized(); cvf::Vec3d normal( 0, 0, 0 ); if ( cvf::Math::abs( tangent * projectionPlaneNormal ) < 0.7071 ) { cvf::Vec3d projectedTangent = ( tangent - ( tangent * projectionPlaneNormal ) * projectionPlaneNormal ).getNormalized(); normal = ( projectedTangent ^ projectionPlaneNormal ).getNormalized(); normal = normal.getTransformedVector( cvf::Mat3d::fromRotation( tangent, planeAngle ) ); } pointNormals.push_back( normal ); sumDotWithRotatedUp += normal * rotatedUp; } pointNormals.push_back( pointNormals.back() ); if ( sumDotWithRotatedUp < 0.0 ) { for ( cvf::Vec3d& normal : pointNormals ) { normal *= -1.0; } } return interpolateUndefinedNormals( up, pointNormals, vertices ); } //-------------------------------------------------------------------------------------------------- /// Lets you estimate MD values from an existing md/tvd relationship and a new set of TVD-values /// Requires the points to be ordered from the start/top of the well path to the end/bottom. //-------------------------------------------------------------------------------------------------- std::vector RigWellPathGeometryTools::interpolateMdFromTvd( const std::vector& originalMdValues, const std::vector& originalTvdValues, const std::vector& tvdValuesToInterpolateFrom ) { CVF_ASSERT( !originalMdValues.empty() ); if ( originalMdValues.size() < 2u ) { return { originalMdValues }; } std::vector interpolatedMdValues; interpolatedMdValues.reserve( tvdValuesToInterpolateFrom.size() ); auto splinePoints = createSplinePoints( originalMdValues, originalTvdValues ); std::vector segmentStartIndices = findSplineSegmentsContainingRoots( splinePoints, tvdValuesToInterpolateFrom ); for ( size_t i = 0; i < segmentStartIndices.size(); ++i ) { double currentTVDValue = tvdValuesToInterpolateFrom[i]; double startMD = splinePoints.front().x(); double endMD = splinePoints.back().y(); if ( segmentStartIndices[i] != -1 ) { int startIndex = segmentStartIndices[i]; int endIndex = startIndex + 1; // Search interval for best MD value startMD = splinePoints[startIndex].x(); endMD = splinePoints.back().y(); if ( endIndex < splinePoints.size() ) { if ( !interpolatedMdValues.empty() ) { double mdDiff = 0.0; if ( interpolatedMdValues.size() > 1 ) { mdDiff = interpolatedMdValues[i - 1] - interpolatedMdValues[i - 2]; } startMD = std::max( startMD, interpolatedMdValues.back() + 0.1 * mdDiff ); } endMD = splinePoints[endIndex].x(); } } double mdValue = solveForX( splinePoints, startMD, endMD, currentTVDValue ); interpolatedMdValues.push_back( mdValue ); } return interpolatedMdValues; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::pair RigWellPathGeometryTools::calculateAzimuthAndInclinationAtMd( double measuredDepth, gsl::not_null wellPathGeometry ) { int mdIndex = -1; auto mdList = wellPathGeometry->measuredDepths(); for ( int i = 0; i < (int)mdList.size(); i++ ) { if ( mdList[i] > measuredDepth ) { mdIndex = i - 1; break; } } auto ptList = wellPathGeometry->wellPathPoints(); if ( mdIndex >= 0 && mdIndex < (int)ptList.size() - 1 ) { const auto& v2 = cvf::Vec3d( ptList[mdIndex] ); const auto& v3 = cvf::Vec3d( ptList[mdIndex + 1] ); auto v32 = ( v3 - v2 ).getNormalized(); auto v13mean = v32; if ( mdIndex > 0 ) { const auto& v1 = cvf::Vec3d( ptList[mdIndex - 1] ); auto v21 = ( v2 - v1 ).getNormalized(); v13mean = ( v21 + v32 ) / 2; } auto v24mean = v32; if ( mdIndex < (int)ptList.size() - 2 ) { const auto& v4 = cvf::Vec3d( ptList[mdIndex + 2] ); auto v43 = ( v4 - v3 ).getNormalized(); v24mean = ( v32 + v43 ) / 2; } double weight = ( measuredDepth - mdList[mdIndex] ) / ( mdList[mdIndex + 1] - mdList[mdIndex] ); auto vTan = v13mean * ( 1.0 - weight ) + v24mean * ( weight ); RiaOffshoreSphericalCoords coords( vTan ); return { coords.azi(), coords.inc() }; } return { 0.0, 0.0 }; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RigWellPathGeometryTools::findSplineSegmentsContainingRoots( const QPolygonF& points, const std::vector& tvdValuesToInterpolateFrom ) { std::vector segmentStartIndices; segmentStartIndices.reserve( tvdValuesToInterpolateFrom.size() ); int lastSplineStartIndex = 0; for ( double tvdValue : tvdValuesToInterpolateFrom ) { int currentSplineStartIndex = lastSplineStartIndex; bool foundMatch = false; // Increment current_it until we find an interval containing our TVD while ( currentSplineStartIndex < points.size() - 2 ) { double diffCurrent = points[currentSplineStartIndex].y() - tvdValue; if ( std::abs( diffCurrent ) < 1.0e-8 ) // Current is matching the point { foundMatch = true; break; } int nextStartIndex = currentSplineStartIndex + 1; double diffNext = points[nextStartIndex].y() - tvdValue; if ( diffCurrent * diffNext < 0.0 ) // One is above, the other is below { foundMatch = true; break; } currentSplineStartIndex = nextStartIndex; } if ( foundMatch ) { segmentStartIndices.push_back( currentSplineStartIndex ); lastSplineStartIndex = currentSplineStartIndex; } else { segmentStartIndices.push_back( -1 ); } } return segmentStartIndices; } std::vector RigWellPathGeometryTools::interpolateUndefinedNormals( const cvf::Vec3d& planeNormal, const std::vector& normals, const std::vector& vertices ) { std::vector interpolated( normals ); cvf::Vec3d lastNormalNonInterpolated( 0, 0, 0 ); cvf::Vec3d lastNormalAny( 0, 0, 0 ); double distanceFromLast = 0.0; for ( size_t i = 0; i < normals.size(); ++i ) { cvf::Vec3d currentNormal = normals[i]; bool currentInterpolated = false; if ( i > 0 ) { distanceFromLast += ( vertices[i] - vertices[i - 1] ).length(); } if ( currentNormal.length() == 0.0 ) // Undefined. Need to estimate from neighbors. { currentInterpolated = true; currentNormal = planeNormal; // By default use the plane normal cvf::Vec3d nextNormal( 0, 0, 0 ); double distanceToNext = 0.0; for ( size_t j = i + 1; j < normals.size() && nextNormal.length() == 0.0; ++j ) { nextNormal = normals[j]; distanceToNext += ( vertices[j] - vertices[j - 1] ).length(); } if ( lastNormalNonInterpolated.length() > 0.0 && nextNormal.length() > 0.0 ) { // Both last and next are acceptable, interpolate! currentNormal = ( distanceToNext * lastNormalNonInterpolated + distanceFromLast * nextNormal ).getNormalized(); } else if ( lastNormalNonInterpolated.length() > 0.0 ) { currentNormal = lastNormalNonInterpolated; } else if ( nextNormal.length() > 0.0 ) { currentNormal = nextNormal; } } if ( i > 0 && currentNormal * lastNormalAny < -std::numeric_limits::epsilon() ) { currentNormal *= -1.0; } if ( !currentInterpolated ) { lastNormalNonInterpolated = currentNormal; distanceFromLast = 0.0; // Reset distance } lastNormalAny = currentNormal; interpolated[i] = currentNormal; } return interpolated; } cvf::Vec3d RigWellPathGeometryTools::estimateDominantDirectionInXYPlane( const std::vector& vertices ) { cvf::Vec3d directionSum( 0, 0, 0 ); for ( size_t i = 1; i < vertices.size(); ++i ) { cvf::Vec3d vec = vertices[i] - vertices[i - 1]; vec.z() = 0.0; if ( directionSum.length() > 0.0 && ( directionSum * vec ) < 0.0 ) { vec *= -1; } directionSum += vec; } if ( directionSum.length() < 1.0e-8 ) { directionSum = cvf::Vec3d( 0, -1, 0 ); } return directionSum.getNormalized(); } //-------------------------------------------------------------------------------------------------- /// Golden-section minimization: https://en.wikipedia.org/wiki/Golden-section_search //-------------------------------------------------------------------------------------------------- double RigWellPathGeometryTools::solveForX( const QPolygonF& spline, double minX, double maxX, double y ) { const double phi = ( 1.0 + std::sqrt( 5.0 ) ) / 2.0; const double tol = 1.0e-8; double a = minX, b = maxX; double c = b - ( b - a ) / phi; double d = a + ( b - a ) / phi; double fc = value( c, spline ) - y; double fd = value( d, spline ) - y; for ( int n = 0; n < 100; ++n ) { if ( std::fabs( c - d ) < tol ) { break; } if ( std::fabs( fc ) < std::fabs( fd ) ) { b = d; d = c; fd = fc; c = b - ( b - a ) / phi; fc = value( c, spline ) - y; } else { a = c; c = d; fc = fd; d = a + ( b - a ) / phi; fd = value( d, spline ) - y; } } return ( a + b ) / 2.0; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QPolygonF RigWellPathGeometryTools::createSplinePoints( const std::vector& originalMdValues, const std::vector& originalTvdValues ) { QPolygonF polygon; for ( size_t i = 0; i < originalMdValues.size(); ++i ) { polygon << QPointF( originalMdValues[i], originalTvdValues[i] ); } QwtSplineCurveFitter curveFitter; double tolerance = 0.5; auto splinePoints = curveFitter.spline()->polygon( polygon, tolerance ); if ( splinePoints.empty() ) splinePoints = polygon; // Extend spline from 0.0 (if it does not already exist) to a large value for MD // This is to force a specific and known extrapolation. // Otherwise we get an undefined and unknown extrapolation. if ( !( splinePoints[0].x() == 0.0 ) ) { double x1 = splinePoints[0].x(); double x2 = splinePoints[1].x(); double y1 = splinePoints[0].y(); double y2 = splinePoints[1].y(); double M = ( y2 - y1 ) / ( x2 - x1 ); QPointF startPoint( 0.0f, M * ( 0.0f - x1 ) + y1 ); splinePoints.push_front( startPoint ); } { int N = splinePoints.size() - 1; double x1 = splinePoints[N - 1].x(); double x2 = splinePoints[N].x(); double y1 = splinePoints[N - 1].y(); double y2 = splinePoints[N].y(); double M = ( y2 - y1 ) / ( x2 - x1 ); double endX = 2.0 * splinePoints[N].x(); QPointF endPoint( endX, M * ( endX - x1 ) + y1 ); splinePoints.push_back( endPoint ); } return splinePoints; }