mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-26 15:10:22 -05:00
Modernize well pick reader and RigWellPathFormations, add tests
Return values instead of out-parameters, and move the error dialog from the reader to the importer so the reader can be unit tested. Fix crash in level detection when erasing while iterating.
This commit is contained in:
1 parent
91e2e4d575
commit
cf36012cd2
9 files changed
+589
-622
No files matched your search
@@ -18,239 +18,118 @@
|
||||
|
||||
#include "RifWellPathFormationReader.h"
|
||||
|
||||
#include "RiaGuiApplication.h"
|
||||
#include "RiaLogging.h"
|
||||
#include "RiaRegressionTestRunner.h"
|
||||
#include "Riu3DMainWindowTools.h"
|
||||
#include "RiuMessageDialog.h"
|
||||
|
||||
#include "cafAssert.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QStringList>
|
||||
#include <QTextStream>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QStringList parseHeader( const QString& line )
|
||||
{
|
||||
QString header = line.toLower();
|
||||
header.removeIf( []( QChar c ) { return c.isSpace(); } );
|
||||
|
||||
return header.split( ';' );
|
||||
}
|
||||
} // namespace
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::map<QString, RigWellPathFormations> RifWellPathFormationReader::readWellFormationsToGeometry( const QString& filePath )
|
||||
std::expected<RifWellPathFormationReader::WellFormations, QString> RifWellPathFormationReader::readWellFormations( const QString& filePath )
|
||||
{
|
||||
std::map<QString, RigWellPathFormations> result;
|
||||
|
||||
std::vector<QString> wellNames;
|
||||
std::vector<QString> formationNames;
|
||||
|
||||
std::vector<double> mdTop;
|
||||
std::vector<double> mdBase;
|
||||
|
||||
std::vector<double> tvdTop;
|
||||
std::vector<double> tvdBase;
|
||||
|
||||
readFile( filePath, &wellNames, &formationNames, &mdTop, &mdBase, &tvdTop, &tvdBase );
|
||||
|
||||
bool mdIsPresent = true;
|
||||
bool tvdIsPresent = true;
|
||||
|
||||
if ( mdTop.empty() || mdBase.empty() )
|
||||
QFile file( filePath );
|
||||
if ( !file.open( QFile::ReadOnly | QFile::Text ) )
|
||||
{
|
||||
mdIsPresent = false;
|
||||
return std::unexpected( QString( "Failed to open %1" ).arg( filePath ) );
|
||||
}
|
||||
|
||||
if ( tvdTop.empty() || tvdBase.empty() )
|
||||
return parseWellFormations( QString::fromUtf8( file.readAll() ), filePath );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::expected<RifWellPathFormationReader::WellFormations, QString> RifWellPathFormationReader::parseWellFormations( const QString& content,
|
||||
const QString& filePath )
|
||||
{
|
||||
const auto parseFailure = QString( "Failed to parse %1 as a well pick file" ).arg( filePath );
|
||||
|
||||
QString text = content;
|
||||
QTextStream stream( &text );
|
||||
|
||||
QStringList header;
|
||||
while ( header.size() < 3 )
|
||||
{
|
||||
tvdIsPresent = false;
|
||||
if ( stream.atEnd() ) return std::unexpected( parseFailure );
|
||||
|
||||
header = parseHeader( stream.readLine() );
|
||||
}
|
||||
|
||||
if ( wellNames.empty() || formationNames.empty() )
|
||||
{
|
||||
RiuMessageDialog::showError( Riu3DMainWindowTools::mainWindowWidget(),
|
||||
"Import failure",
|
||||
QString( "Failed to parse %1 as a well pick file" ).arg( filePath ) );
|
||||
const int wellNameIndex = header.indexOf( "wellname" );
|
||||
const int unitNameIndex = header.indexOf( "unitname" );
|
||||
const int mdTopIndex = header.indexOf( "topmd" );
|
||||
const int mdBaseIndex = header.indexOf( "basemd" );
|
||||
const int tvdTopIndex = header.indexOf( "toptvdss" );
|
||||
const int tvdBaseIndex = header.indexOf( "basetvdss" );
|
||||
|
||||
return result;
|
||||
}
|
||||
else if ( !( mdIsPresent || tvdIsPresent ) )
|
||||
{
|
||||
RiuMessageDialog::showError( Riu3DMainWindowTools::mainWindowWidget(),
|
||||
"Import failure",
|
||||
QString( "Failed to parse %1 as a well pick file. Neither MD or TVD is present." ).arg( filePath ) );
|
||||
const bool hasMd = mdTopIndex != -1 && mdBaseIndex != -1;
|
||||
const bool hasTvd = tvdTopIndex != -1 && tvdBaseIndex != -1;
|
||||
|
||||
return result;
|
||||
if ( wellNameIndex == -1 || unitNameIndex == -1 )
|
||||
{
|
||||
return std::unexpected( parseFailure );
|
||||
}
|
||||
|
||||
CAF_ASSERT( wellNames.size() == formationNames.size() );
|
||||
|
||||
std::map<QString, std::vector<RigWellPathFormation>> formations;
|
||||
|
||||
for ( size_t i = 0; i < wellNames.size(); i++ )
|
||||
if ( !hasMd && !hasTvd )
|
||||
{
|
||||
return std::unexpected( parseFailure + ". Neither MD or TVD is present." );
|
||||
}
|
||||
|
||||
std::map<QString, std::vector<RigWellPathFormation>> formationsPerWell;
|
||||
|
||||
while ( !stream.atEnd() )
|
||||
{
|
||||
const QStringList columns = stream.readLine().split( ';' );
|
||||
if ( columns.size() != header.size() ) continue;
|
||||
|
||||
const QString wellName = columns[wellNameIndex];
|
||||
const QString unitName = columns[unitNameIndex].trimmed();
|
||||
if ( wellName.trimmed().isEmpty() && unitName.isEmpty() ) continue;
|
||||
|
||||
RigWellPathFormation formation;
|
||||
formation.formationName = formationNames[i];
|
||||
formation.formationName = unitName;
|
||||
|
||||
if ( mdIsPresent )
|
||||
if ( hasMd )
|
||||
{
|
||||
formation.mdTop = mdTop[i];
|
||||
formation.mdBase = mdBase[i];
|
||||
formation.mdTop = columns[mdTopIndex].toDouble();
|
||||
formation.mdBase = columns[mdBaseIndex].toDouble();
|
||||
}
|
||||
|
||||
if ( tvdIsPresent )
|
||||
if ( hasTvd )
|
||||
{
|
||||
formation.tvdTop = tvdTop[i];
|
||||
formation.tvdBase = tvdBase[i];
|
||||
formation.tvdTop = -columns[tvdTopIndex].toDouble();
|
||||
formation.tvdBase = -columns[tvdBaseIndex].toDouble();
|
||||
}
|
||||
|
||||
if ( !formations.count( wellNames[i] ) )
|
||||
{
|
||||
formations[wellNames[i]] = std::vector<RigWellPathFormation>();
|
||||
}
|
||||
|
||||
formations[wellNames[i]].push_back( formation );
|
||||
formationsPerWell[wellName].push_back( formation );
|
||||
}
|
||||
|
||||
for ( const std::pair<const QString, std::vector<RigWellPathFormation>>& formation : formations )
|
||||
if ( formationsPerWell.empty() )
|
||||
{
|
||||
result.emplace( formation.first, RigWellPathFormations( formation.second, filePath, formation.first ) );
|
||||
return std::unexpected( parseFailure );
|
||||
}
|
||||
|
||||
WellFormations result;
|
||||
for ( const auto& [wellName, formations] : formationsPerWell )
|
||||
{
|
||||
result.emplace( wellName, RigWellPathFormations( formations, filePath, wellName ) );
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void removeWhiteSpaces( QString* word )
|
||||
{
|
||||
std::string wordStd = word->toStdString();
|
||||
wordStd.erase( std::remove_if( wordStd.begin(), wordStd.end(), []( unsigned char x ) { return std::isspace( x ); } ), wordStd.end() );
|
||||
|
||||
( *word ) = QString( wordStd.c_str() );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RifWellPathFormationReader::readFile( const QString& filePath,
|
||||
std::vector<QString>* wellNames,
|
||||
std::vector<QString>* formationNames,
|
||||
std::vector<double>* mdTop,
|
||||
std::vector<double>* mdBase,
|
||||
std::vector<double>* tvdTop,
|
||||
std::vector<double>* tvdBase )
|
||||
{
|
||||
QFile data( filePath );
|
||||
|
||||
if ( !data.open( QFile::ReadOnly ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
QStringList header;
|
||||
|
||||
while ( header.size() < 3 )
|
||||
{
|
||||
if ( data.atEnd() ) return;
|
||||
|
||||
QString line = data.readLine().toLower();
|
||||
removeWhiteSpaces( &line );
|
||||
|
||||
header = line.split( ';' );
|
||||
}
|
||||
|
||||
static const QString wellNameText = "wellname";
|
||||
static const QString surfaceNameText = "surfacename";
|
||||
static const QString measuredDepthText = "md";
|
||||
|
||||
int wellNameIndex = header.indexOf( wellNameText );
|
||||
int surfaceNameIndex = header.indexOf( surfaceNameText );
|
||||
int measuredDepthIndex = header.indexOf( measuredDepthText );
|
||||
|
||||
if ( wellNameIndex != -1 && surfaceNameIndex != -1 && measuredDepthIndex != -1 )
|
||||
{
|
||||
do
|
||||
{
|
||||
QString line = data.readLine();
|
||||
|
||||
QStringList dataLine = line.split( ';' );
|
||||
if ( dataLine.size() != header.size() ) continue;
|
||||
|
||||
bool conversionOk;
|
||||
double measuredDepth = dataLine[measuredDepthIndex].toDouble( &conversionOk );
|
||||
if ( !conversionOk ) continue;
|
||||
|
||||
QString wellName = dataLine[wellNameIndex];
|
||||
QString surfaceName = dataLine[surfaceNameIndex];
|
||||
|
||||
wellNames->push_back( wellName );
|
||||
formationNames->push_back( surfaceName );
|
||||
mdTop->push_back( measuredDepth );
|
||||
|
||||
} while ( !data.atEnd() );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static const QString unitNameText = "unitname";
|
||||
static const QString measuredDepthToptext = "topmd";
|
||||
static const QString measuredDepthBasetext = "basemd";
|
||||
static const QString trueVerticalDepthToptext = "toptvdss";
|
||||
static const QString trueVerticalDepthBasetext = "basetvdss";
|
||||
|
||||
int unitNameIndex = header.indexOf( unitNameText );
|
||||
|
||||
int measuredDepthTopIndex = header.indexOf( measuredDepthToptext );
|
||||
int measuredDepthBaseIndex = header.indexOf( measuredDepthBasetext );
|
||||
|
||||
int trueVerticalDepthTopIndex = header.indexOf( trueVerticalDepthToptext );
|
||||
int trueVerticalDepthBaseIndex = header.indexOf( trueVerticalDepthBasetext );
|
||||
|
||||
bool mdIsPresent = true;
|
||||
bool tvdIsPresent = true;
|
||||
|
||||
if ( measuredDepthTopIndex == -1 || measuredDepthBaseIndex == -1 )
|
||||
{
|
||||
mdIsPresent = false;
|
||||
}
|
||||
|
||||
if ( trueVerticalDepthTopIndex == -1 || trueVerticalDepthBaseIndex == -1 )
|
||||
{
|
||||
tvdIsPresent = false;
|
||||
}
|
||||
|
||||
if ( unitNameIndex != -1 && ( mdIsPresent || tvdIsPresent ) )
|
||||
{
|
||||
do
|
||||
{
|
||||
QString line = data.readLine();
|
||||
|
||||
QStringList dataLine = line.split( ';' );
|
||||
if ( dataLine.size() != header.size() ) continue;
|
||||
|
||||
QString wellName = dataLine[wellNameIndex];
|
||||
QString unitName = dataLine[unitNameIndex];
|
||||
unitName = unitName.trimmed();
|
||||
|
||||
if ( wellName.trimmed().isEmpty() && unitName.isEmpty() ) continue;
|
||||
|
||||
if ( mdIsPresent )
|
||||
{
|
||||
double mdTopValue = dataLine[measuredDepthTopIndex].toDouble();
|
||||
double mdBaseValue = dataLine[measuredDepthBaseIndex].toDouble();
|
||||
|
||||
mdTop->push_back( mdTopValue );
|
||||
mdBase->push_back( mdBaseValue );
|
||||
}
|
||||
|
||||
if ( tvdIsPresent )
|
||||
{
|
||||
double tvdTopValue = dataLine[trueVerticalDepthTopIndex].toDouble();
|
||||
double tvdBaseValue = dataLine[trueVerticalDepthBaseIndex].toDouble();
|
||||
|
||||
tvdTop->push_back( -tvdTopValue );
|
||||
tvdBase->push_back( -tvdBaseValue );
|
||||
}
|
||||
|
||||
wellNames->push_back( wellName );
|
||||
formationNames->push_back( unitName );
|
||||
|
||||
} while ( !data.atEnd() );
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,8 @@
|
||||
|
||||
#include "Well/RigWellPathFormations.h"
|
||||
|
||||
#include <expected>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <QString>
|
||||
|
||||
@@ -32,14 +31,8 @@
|
||||
class RifWellPathFormationReader
|
||||
{
|
||||
public:
|
||||
static std::map<QString, RigWellPathFormations> readWellFormationsToGeometry( const QString& filePath );
|
||||
using WellFormations = std::map<QString /*wellName*/, RigWellPathFormations>;
|
||||
|
||||
private:
|
||||
static void readFile( const QString& filePath,
|
||||
std::vector<QString>* wellNames,
|
||||
std::vector<QString>* formationNames,
|
||||
std::vector<double>* mdTop,
|
||||
std::vector<double>* mdBase,
|
||||
std::vector<double>* tvdTop,
|
||||
std::vector<double>* tvdBase );
|
||||
static std::expected<WellFormations, QString> readWellFormations( const QString& filePath );
|
||||
static std::expected<WellFormations, QString> parseWellFormations( const QString& content, const QString& filePath );
|
||||
};
|
||||
@@ -19,6 +19,9 @@
|
||||
#include "RifWellPathFormationsImporter.h"
|
||||
#include "RifWellPathFormationReader.h"
|
||||
|
||||
#include "Riu3DMainWindowTools.h"
|
||||
#include "RiuMessageDialog.h"
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -52,17 +55,9 @@ std::optional<RigWellPathFormations> RifWellPathFormationsImporter::reloadWellPa
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::map<QString, RigWellPathFormations> RifWellPathFormationsImporter::readWellPathFormationsFromPath( const QString& filePath )
|
||||
{
|
||||
// If we have the file in the map, assume it is already read.
|
||||
if ( m_fileNameToWellPathFormationMap.find( filePath ) != m_fileNameToWellPathFormationMap.end() )
|
||||
{
|
||||
return m_fileNameToWellPathFormationMap[filePath];
|
||||
}
|
||||
readAllWellPathFormations( filePath );
|
||||
|
||||
std::map<QString, RigWellPathFormations> wellPathToFormationMap = RifWellPathFormationReader::readWellFormationsToGeometry( filePath );
|
||||
|
||||
m_fileNameToWellPathFormationMap[filePath] = wellPathToFormationMap;
|
||||
|
||||
return wellPathToFormationMap;
|
||||
return m_fileNameToWellPathFormationMap[filePath];
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -95,7 +90,11 @@ void RifWellPathFormationsImporter::readAllWellPathFormations( const QString& fi
|
||||
return;
|
||||
}
|
||||
|
||||
std::map<QString, RigWellPathFormations> wellPathToFormationMap = RifWellPathFormationReader::readWellFormationsToGeometry( filePath );
|
||||
auto wellFormations = RifWellPathFormationReader::readWellFormations( filePath );
|
||||
if ( !wellFormations )
|
||||
{
|
||||
RiuMessageDialog::showError( Riu3DMainWindowTools::mainWindowWidget(), "Import failure", wellFormations.error() );
|
||||
}
|
||||
|
||||
m_fileNameToWellPathFormationMap[filePath] = wellPathToFormationMap;
|
||||
m_fileNameToWellPathFormationMap[filePath] = wellFormations.value_or( RifWellPathFormationReader::WellFormations() );
|
||||
}
|
||||
@@ -2577,18 +2577,12 @@ void RimWellLogTrack::updateFormationNamesOnPlot()
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<double> yValues;
|
||||
|
||||
const RigWellPathFormations* formations = m_formationSettings->wellPathForSourceWellPath()->formationsGeometry();
|
||||
if ( !formations ) return;
|
||||
|
||||
std::vector<QString> formationNamesToPlot;
|
||||
auto formationLevel = static_cast<RigWellPathFormations::FormationLevel>( m_formationSettings->formationLevel() );
|
||||
formations->depthAndFormationNamesUpToLevel( formationLevel,
|
||||
&formationNamesToPlot,
|
||||
&yValues,
|
||||
m_formationSettings->showFormationFluids(),
|
||||
plot->depthType() );
|
||||
auto formationLevel = static_cast<RigWellPathFormations::FormationLevel>( m_formationSettings->formationLevel() );
|
||||
auto [formationNamesToPlot, yValues] =
|
||||
formations->depthAndFormationNamesUpToLevel( formationLevel, m_formationSettings->showFormationFluids(), plot->depthType() );
|
||||
|
||||
if ( plot->depthType() == RiaDefines::DepthType::TRUE_VERTICAL_DEPTH_RKB )
|
||||
{
|
||||
|
||||
@@ -18,17 +18,221 @@
|
||||
|
||||
#include "RigWellPathFormations.h"
|
||||
|
||||
#include "QStringList"
|
||||
#include <QStringList>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
|
||||
namespace
|
||||
{
|
||||
using FormationLevel = RiaDefines::WellLogTrackFormationLevel;
|
||||
using NamesAndDepths = std::pair<std::vector<QString>, std::vector<double>>;
|
||||
|
||||
// Picks closer than this are considered to be at the same depth
|
||||
struct DepthComp
|
||||
{
|
||||
bool operator()( double depth1, double depth2 ) const
|
||||
{
|
||||
if ( std::abs( depth1 - depth2 ) < 0.1 ) return false;
|
||||
return depth1 < depth2;
|
||||
}
|
||||
};
|
||||
|
||||
enum class PickPosition
|
||||
{
|
||||
TOP,
|
||||
BASE
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::optional<double> pickDepth( const RigWellPathFormation& formation, PickPosition position, RiaDefines::DepthType depthType )
|
||||
{
|
||||
const bool isTop = position == PickPosition::TOP;
|
||||
|
||||
switch ( depthType )
|
||||
{
|
||||
case RiaDefines::DepthType::MEASURED_DEPTH:
|
||||
return isTop ? formation.mdTop : formation.mdBase;
|
||||
case RiaDefines::DepthType::TRUE_VERTICAL_DEPTH:
|
||||
return isTop ? formation.tvdTop : formation.tvdBase;
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QString pickName( const RigWellPathFormation& formation, PickPosition position )
|
||||
{
|
||||
return formation.formationName + ( position == PickPosition::TOP ? " Top" : " Base" );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
NamesAndDepths fluidPicks( const std::vector<RigWellPathFormation>& fluids, RiaDefines::DepthType depthType )
|
||||
{
|
||||
std::map<double, QString, DepthComp> picks;
|
||||
|
||||
// Bases first, so a top at the same depth replaces the base
|
||||
for ( auto position : { PickPosition::BASE, PickPosition::TOP } )
|
||||
{
|
||||
for ( const auto& fluid : fluids )
|
||||
{
|
||||
auto depth = pickDepth( fluid, position, depthType );
|
||||
if ( !depth ) return {};
|
||||
|
||||
picks[*depth] = pickName( fluid, position );
|
||||
}
|
||||
}
|
||||
|
||||
NamesAndDepths result;
|
||||
for ( const auto& [depth, name] : picks )
|
||||
{
|
||||
result.first.push_back( name );
|
||||
result.second.push_back( depth );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
NamesAndDepths allPicksWithoutDuplicateDepths( const std::vector<std::pair<RigWellPathFormation, FormationLevel>>& formations,
|
||||
RiaDefines::DepthType depthType )
|
||||
{
|
||||
NamesAndDepths result;
|
||||
std::set<double, DepthComp> usedDepths;
|
||||
|
||||
for ( auto position : { PickPosition::TOP, PickPosition::BASE } )
|
||||
{
|
||||
for ( const auto& [formation, level] : formations )
|
||||
{
|
||||
auto depth = pickDepth( formation, position, depthType );
|
||||
if ( !depth ) return {};
|
||||
|
||||
if ( usedDepths.insert( *depth ).second )
|
||||
{
|
||||
result.first.push_back( pickName( formation, position ) );
|
||||
result.second.push_back( *depth );
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// At each depth, keep the pick from the most detailed level not exceeding maxLevel
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
NamesAndDepths picksUpToLevel( const std::vector<std::pair<RigWellPathFormation, FormationLevel>>& formations,
|
||||
FormationLevel maxLevel,
|
||||
RiaDefines::DepthType depthType )
|
||||
{
|
||||
std::map<double, std::pair<FormationLevel, QString>, DepthComp> picks;
|
||||
|
||||
for ( auto position : { PickPosition::TOP, PickPosition::BASE } )
|
||||
{
|
||||
for ( const auto& [formation, level] : formations )
|
||||
{
|
||||
auto depth = pickDepth( formation, position, depthType );
|
||||
if ( !depth ) return {};
|
||||
|
||||
if ( level > maxLevel ) continue;
|
||||
|
||||
auto it = picks.find( *depth );
|
||||
if ( it == picks.end() )
|
||||
{
|
||||
picks.emplace( *depth, std::make_pair( level, pickName( formation, position ) ) );
|
||||
}
|
||||
else if ( it->second.first < level )
|
||||
{
|
||||
it->second = { level, pickName( formation, position ) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NamesAndDepths result;
|
||||
for ( const auto& [depth, levelAndName] : picks )
|
||||
{
|
||||
result.first.push_back( levelAndName.second );
|
||||
result.second.push_back( depth );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
bool isFluid( const QString& formationName )
|
||||
{
|
||||
const QString name = formationName.trimmed();
|
||||
return name == "OIL" || name == "GAS" || name == "WATER";
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Group names are all upper case. Otherwise the number of dots in the numeric part gives the level,
|
||||
/// e.g. "Ile 2" is level 1 and "Ile 2.1" is level 2.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
FormationLevel detectLevel( const QString& formationName )
|
||||
{
|
||||
const QString name = formationName.trimmed();
|
||||
|
||||
if ( std::none_of( name.begin(), name.end(), []( QChar c ) { return c.isLower(); } ) )
|
||||
{
|
||||
return FormationLevel::GROUP;
|
||||
}
|
||||
|
||||
auto containsDigit = []( const QString& word ) { return std::any_of( word.begin(), word.end(), []( QChar c ) { return c.isDigit(); } ); };
|
||||
auto containsLetter = []( const QString& word )
|
||||
{ return std::any_of( word.begin(), word.end(), []( QChar c ) { return c.isLetter(); } ); };
|
||||
|
||||
std::vector<QString> levelDescriptorCandidates;
|
||||
for ( const QString& word : name.split( ' ' ) )
|
||||
{
|
||||
if ( containsDigit( word ) ) levelDescriptorCandidates.push_back( word );
|
||||
}
|
||||
|
||||
if ( levelDescriptorCandidates.empty() ) return FormationLevel::LEVEL0;
|
||||
|
||||
if ( levelDescriptorCandidates.size() > 1 )
|
||||
{
|
||||
std::erase_if( levelDescriptorCandidates, containsLetter );
|
||||
}
|
||||
|
||||
if ( levelDescriptorCandidates.size() != 1 ) return FormationLevel::UNKNOWN;
|
||||
|
||||
const QString levelDescriptor = levelDescriptorCandidates.front().split( '+' ).front();
|
||||
|
||||
static const std::array levels = { FormationLevel::LEVEL1,
|
||||
FormationLevel::LEVEL2,
|
||||
FormationLevel::LEVEL3,
|
||||
FormationLevel::LEVEL4,
|
||||
FormationLevel::LEVEL5,
|
||||
FormationLevel::LEVEL6,
|
||||
FormationLevel::LEVEL7,
|
||||
FormationLevel::LEVEL8,
|
||||
FormationLevel::LEVEL9,
|
||||
FormationLevel::LEVEL10 };
|
||||
|
||||
const auto dotCount = static_cast<size_t>( levelDescriptor.count( '.' ) );
|
||||
return dotCount < levels.size() ? levels[dotCount] : FormationLevel::UNKNOWN;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
RigWellPathFormations::RigWellPathFormations( const std::vector<RigWellPathFormation>& formations, const QString& filePath, const QString& key )
|
||||
: m_filePath( filePath )
|
||||
, m_keyInFile( key )
|
||||
{
|
||||
m_filePath = filePath;
|
||||
m_keyInFile = key;
|
||||
|
||||
for ( const RigWellPathFormation& formation : formations )
|
||||
for ( const auto& formation : formations )
|
||||
{
|
||||
if ( isFluid( formation.formationName ) )
|
||||
{
|
||||
@@ -36,10 +240,9 @@ RigWellPathFormations::RigWellPathFormations( const std::vector<RigWellPathForma
|
||||
}
|
||||
else
|
||||
{
|
||||
FormationLevel level = detectLevel( formation.formationName );
|
||||
m_formationsLevelsPresent[level] = true;
|
||||
|
||||
m_formations.push_back( std::pair<RigWellPathFormation, FormationLevel>( formation, level ) );
|
||||
auto level = detectLevel( formation.formationName );
|
||||
m_formationsLevelsPresent.insert( level );
|
||||
m_formations.emplace_back( formation, level );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,217 +250,29 @@ RigWellPathFormations::RigWellPathFormations( const std::vector<RigWellPathForma
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RigWellPathFormations::depthAndFormationNamesWithoutDuplicatesOnDepth( std::vector<QString>* names,
|
||||
std::vector<double>* measuredDepths,
|
||||
RiaDefines::DepthType depthType ) const
|
||||
std::pair<std::vector<QString>, std::vector<double>>
|
||||
RigWellPathFormations::depthAndFormationNamesUpToLevel( FormationLevel level, bool includeFluids, RiaDefines::DepthType depthType ) const
|
||||
{
|
||||
std::map<double, bool, DepthComp> tempMakeVectorUniqueOnMeasuredDepth;
|
||||
|
||||
if ( depthType == RiaDefines::DepthType::MEASURED_DEPTH )
|
||||
{
|
||||
for ( const std::pair<RigWellPathFormation, FormationLevel>& formation : m_formations )
|
||||
{
|
||||
if ( !tempMakeVectorUniqueOnMeasuredDepth.count( formation.first.mdTop ) )
|
||||
{
|
||||
measuredDepths->push_back( formation.first.mdTop );
|
||||
names->push_back( formation.first.formationName + " Top" );
|
||||
tempMakeVectorUniqueOnMeasuredDepth[formation.first.mdTop] = true;
|
||||
}
|
||||
}
|
||||
|
||||
for ( const std::pair<RigWellPathFormation, FormationLevel>& formation : m_formations )
|
||||
{
|
||||
if ( !tempMakeVectorUniqueOnMeasuredDepth.count( formation.first.mdBase ) )
|
||||
{
|
||||
measuredDepths->push_back( formation.first.mdBase );
|
||||
names->push_back( formation.first.formationName + " Base" );
|
||||
tempMakeVectorUniqueOnMeasuredDepth[formation.first.mdBase] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ( depthType == RiaDefines::DepthType::TRUE_VERTICAL_DEPTH )
|
||||
{
|
||||
for ( const std::pair<RigWellPathFormation, FormationLevel>& formation : m_formations )
|
||||
{
|
||||
if ( !tempMakeVectorUniqueOnMeasuredDepth.count( formation.first.tvdTop ) )
|
||||
{
|
||||
measuredDepths->push_back( formation.first.tvdTop );
|
||||
names->push_back( formation.first.formationName + " Top" );
|
||||
tempMakeVectorUniqueOnMeasuredDepth[formation.first.tvdTop] = true;
|
||||
}
|
||||
}
|
||||
|
||||
for ( const std::pair<RigWellPathFormation, FormationLevel>& formation : m_formations )
|
||||
{
|
||||
if ( !tempMakeVectorUniqueOnMeasuredDepth.count( formation.first.tvdBase ) )
|
||||
{
|
||||
measuredDepths->push_back( formation.first.tvdBase );
|
||||
names->push_back( formation.first.formationName + " Base" );
|
||||
tempMakeVectorUniqueOnMeasuredDepth[formation.first.tvdBase] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RigWellPathFormations::evaluateFormationsForOnePosition( const std::vector<std::pair<RigWellPathFormation, FormationLevel>>& formations,
|
||||
const FormationLevel& maxLevel,
|
||||
const PickPosition& position,
|
||||
std::map<double, LevelAndName, DepthComp>* uniqueListMaker,
|
||||
RiaDefines::DepthType depthType ) const
|
||||
{
|
||||
QString postFix;
|
||||
|
||||
if ( position == TOP )
|
||||
{
|
||||
postFix = " Top";
|
||||
}
|
||||
else
|
||||
{
|
||||
postFix = " Base";
|
||||
}
|
||||
|
||||
for ( const std::pair<RigWellPathFormation, FormationLevel>& formation : formations )
|
||||
{
|
||||
double depth;
|
||||
|
||||
if ( depthType == RiaDefines::DepthType::MEASURED_DEPTH )
|
||||
{
|
||||
if ( position == TOP )
|
||||
{
|
||||
depth = formation.first.mdTop;
|
||||
}
|
||||
else
|
||||
{
|
||||
depth = formation.first.mdBase;
|
||||
}
|
||||
}
|
||||
else if ( depthType == RiaDefines::DepthType::TRUE_VERTICAL_DEPTH )
|
||||
{
|
||||
if ( position == TOP )
|
||||
{
|
||||
depth = formation.first.tvdTop;
|
||||
}
|
||||
else
|
||||
{
|
||||
depth = formation.first.tvdBase;
|
||||
}
|
||||
}
|
||||
else
|
||||
return;
|
||||
|
||||
if ( formation.second > maxLevel ) continue;
|
||||
|
||||
if ( !uniqueListMaker->count( depth ) || uniqueListMaker->at( depth ).level < formation.second )
|
||||
{
|
||||
( *uniqueListMaker )[depth] = LevelAndName( formation.second, formation.first.formationName + postFix );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RigWellPathFormations::evaluateFormations( const std::vector<std::pair<RigWellPathFormation, FormationLevel>>& formations,
|
||||
const FormationLevel& maxLevel,
|
||||
std::vector<QString>* names,
|
||||
std::vector<double>* depths,
|
||||
RiaDefines::DepthType depthType ) const
|
||||
{
|
||||
std::map<double, LevelAndName, DepthComp> tempMakeVectorUniqueOnDepth;
|
||||
|
||||
evaluateFormationsForOnePosition( formations, maxLevel, PickPosition::TOP, &tempMakeVectorUniqueOnDepth, depthType );
|
||||
evaluateFormationsForOnePosition( formations, maxLevel, PickPosition::BASE, &tempMakeVectorUniqueOnDepth, depthType );
|
||||
|
||||
for ( const std::pair<const double, LevelAndName>& uniqueDepth : tempMakeVectorUniqueOnDepth )
|
||||
{
|
||||
depths->push_back( uniqueDepth.first );
|
||||
names->push_back( uniqueDepth.second.name );
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RigWellPathFormations::evaluateFluids( const std::vector<RigWellPathFormation>& fluidFormations,
|
||||
std::vector<QString>* names,
|
||||
std::vector<double>* depths,
|
||||
RiaDefines::DepthType depthType ) const
|
||||
{
|
||||
std::map<double, QString, DepthComp> uniqueListMaker;
|
||||
|
||||
for ( const RigWellPathFormation& formation : fluidFormations )
|
||||
{
|
||||
double depthBase;
|
||||
if ( depthType == RiaDefines::DepthType::MEASURED_DEPTH )
|
||||
{
|
||||
depthBase = formation.mdBase;
|
||||
}
|
||||
else if ( depthType == RiaDefines::DepthType::TRUE_VERTICAL_DEPTH )
|
||||
{
|
||||
depthBase = formation.tvdBase;
|
||||
}
|
||||
else
|
||||
return;
|
||||
|
||||
uniqueListMaker[depthBase] = formation.formationName + " Base";
|
||||
}
|
||||
|
||||
for ( const RigWellPathFormation& formation : fluidFormations )
|
||||
{
|
||||
double depthTop;
|
||||
if ( depthType == RiaDefines::DepthType::MEASURED_DEPTH )
|
||||
{
|
||||
depthTop = formation.mdTop;
|
||||
}
|
||||
else if ( depthType == RiaDefines::DepthType::TRUE_VERTICAL_DEPTH )
|
||||
{
|
||||
depthTop = formation.tvdTop;
|
||||
}
|
||||
else
|
||||
return;
|
||||
|
||||
uniqueListMaker[depthTop] = formation.formationName + " Top";
|
||||
}
|
||||
|
||||
for ( const std::pair<const double, QString>& depthAndFormation : uniqueListMaker )
|
||||
{
|
||||
depths->push_back( depthAndFormation.first );
|
||||
names->push_back( depthAndFormation.second );
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RigWellPathFormations::depthAndFormationNamesUpToLevel( FormationLevel level,
|
||||
std::vector<QString>* names,
|
||||
std::vector<double>* depths,
|
||||
bool includeFluids,
|
||||
RiaDefines::DepthType depthType ) const
|
||||
{
|
||||
names->clear();
|
||||
depths->clear();
|
||||
|
||||
NamesAndDepths result;
|
||||
if ( includeFluids )
|
||||
{
|
||||
evaluateFluids( m_fluids, names, depths, depthType );
|
||||
result = fluidPicks( m_fluids, depthType );
|
||||
}
|
||||
|
||||
if ( level == FormationLevel::NONE )
|
||||
NamesAndDepths formationResult;
|
||||
if ( level == FormationLevel::ALL )
|
||||
{
|
||||
return;
|
||||
formationResult = allPicksWithoutDuplicateDepths( m_formations, depthType );
|
||||
}
|
||||
else if ( level == FormationLevel::ALL )
|
||||
else if ( level != FormationLevel::NONE )
|
||||
{
|
||||
depthAndFormationNamesWithoutDuplicatesOnDepth( names, depths, depthType );
|
||||
}
|
||||
else
|
||||
{
|
||||
evaluateFormations( m_formations, level, names, depths, depthType );
|
||||
formationResult = picksUpToLevel( m_formations, level, depthType );
|
||||
}
|
||||
|
||||
result.first.insert( result.first.end(), formationResult.first.begin(), formationResult.first.end() );
|
||||
result.second.insert( result.second.end(), formationResult.second.begin(), formationResult.second.end() );
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -265,13 +280,7 @@ void RigWellPathFormations::depthAndFormationNamesUpToLevel( FormationLevel
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::vector<RigWellPathFormations::FormationLevel> RigWellPathFormations::formationsLevelsPresent() const
|
||||
{
|
||||
std::vector<RigWellPathFormations::FormationLevel> formationLevels;
|
||||
|
||||
for ( const std::pair<const RigWellPathFormations::FormationLevel, bool>& formationLevel : m_formationsLevelsPresent )
|
||||
{
|
||||
formationLevels.push_back( formationLevel.first );
|
||||
}
|
||||
return formationLevels;
|
||||
return { m_formationsLevelsPresent.begin(), m_formationsLevelsPresent.end() };
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -297,107 +306,3 @@ size_t RigWellPathFormations::formationNamesCount() const
|
||||
{
|
||||
return m_formations.size() + m_fluids.size();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
bool RigWellPathFormations::isFluid( QString formationName )
|
||||
{
|
||||
formationName = formationName.trimmed();
|
||||
|
||||
return formationName == "OIL" || formationName == "GAS" || formationName == "WATER";
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
RigWellPathFormations::FormationLevel RigWellPathFormations::detectLevel( QString formationName )
|
||||
{
|
||||
formationName = formationName.trimmed();
|
||||
|
||||
bool isGroupName = true;
|
||||
for ( QChar c : formationName )
|
||||
{
|
||||
if ( c.isLower() )
|
||||
{
|
||||
isGroupName = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( isGroupName )
|
||||
{
|
||||
return FormationLevel::GROUP;
|
||||
}
|
||||
|
||||
QStringList formationNameSplitted = formationName.split( " " );
|
||||
|
||||
std::vector<QString> levelDesctiptorCandidates;
|
||||
|
||||
for ( QString word : formationNameSplitted )
|
||||
{
|
||||
for ( const QChar& c : word )
|
||||
{
|
||||
if ( c.isDigit() )
|
||||
{
|
||||
levelDesctiptorCandidates.push_back( word );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( levelDesctiptorCandidates.empty() )
|
||||
{
|
||||
return FormationLevel::LEVEL0;
|
||||
}
|
||||
|
||||
if ( levelDesctiptorCandidates.size() > 1 )
|
||||
{
|
||||
for ( auto it = levelDesctiptorCandidates.begin(); it != levelDesctiptorCandidates.end(); it++ )
|
||||
{
|
||||
for ( const QChar& c : *it )
|
||||
{
|
||||
if ( c.isLetter() )
|
||||
{
|
||||
levelDesctiptorCandidates.erase( it );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( levelDesctiptorCandidates.size() != 1 ) return FormationLevel::UNKNOWN;
|
||||
|
||||
QString levelDescriptor = levelDesctiptorCandidates[0];
|
||||
|
||||
QStringList joinedLevel = levelDescriptor.split( '+' );
|
||||
if ( joinedLevel.size() > 1 )
|
||||
{
|
||||
levelDescriptor = joinedLevel[0];
|
||||
}
|
||||
|
||||
int dotCount = levelDescriptor.count( '.' );
|
||||
|
||||
switch ( dotCount )
|
||||
{
|
||||
case 0:
|
||||
return FormationLevel::LEVEL1;
|
||||
case 1:
|
||||
return FormationLevel::LEVEL2;
|
||||
case 2:
|
||||
return FormationLevel::LEVEL3;
|
||||
case 3:
|
||||
return FormationLevel::LEVEL4;
|
||||
case 4:
|
||||
return FormationLevel::LEVEL5;
|
||||
case 5:
|
||||
return FormationLevel::LEVEL6;
|
||||
case 6:
|
||||
return FormationLevel::LEVEL7;
|
||||
case 7:
|
||||
return FormationLevel::LEVEL8;
|
||||
case 8:
|
||||
return FormationLevel::LEVEL9;
|
||||
case 9:
|
||||
return FormationLevel::LEVEL10;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return FormationLevel::UNKNOWN;
|
||||
}
|
||||
@@ -21,9 +21,7 @@
|
||||
#include "RiaDefines.h"
|
||||
#include "RiaWellLogTrackDefines.h"
|
||||
|
||||
#include "cvfMath.h"
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -49,11 +47,9 @@ public:
|
||||
public:
|
||||
RigWellPathFormations( const std::vector<RigWellPathFormation>& formations, const QString& filePath, const QString& key );
|
||||
|
||||
void depthAndFormationNamesUpToLevel( FormationLevel level,
|
||||
std::vector<QString>* names,
|
||||
std::vector<double>* depths,
|
||||
bool includeFluids,
|
||||
RiaDefines::DepthType depthType ) const;
|
||||
// Returns formation names and depths as parallel vectors
|
||||
std::pair<std::vector<QString>, std::vector<double>>
|
||||
depthAndFormationNamesUpToLevel( FormationLevel level, bool includeFluids, RiaDefines::DepthType depthType ) const;
|
||||
|
||||
std::vector<FormationLevel> formationsLevelsPresent() const;
|
||||
|
||||
@@ -62,68 +58,11 @@ public:
|
||||
|
||||
size_t formationNamesCount() const;
|
||||
|
||||
private:
|
||||
struct DepthComp
|
||||
{
|
||||
bool operator()( const double& depth1, const double& depth2 ) const
|
||||
{
|
||||
if ( cvf::Math::abs( depth1 - depth2 ) < 0.1 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return depth1 < depth2;
|
||||
}
|
||||
};
|
||||
|
||||
struct LevelAndName
|
||||
{
|
||||
LevelAndName() = default;
|
||||
LevelAndName( FormationLevel level, QString name )
|
||||
: level( level )
|
||||
, name( name )
|
||||
{
|
||||
}
|
||||
|
||||
FormationLevel level;
|
||||
QString name;
|
||||
};
|
||||
|
||||
enum PickPosition
|
||||
{
|
||||
TOP,
|
||||
BASE
|
||||
};
|
||||
|
||||
private:
|
||||
void evaluateFormations( const std::vector<std::pair<RigWellPathFormation, FormationLevel>>& formations,
|
||||
const FormationLevel& maxLevel,
|
||||
std::vector<QString>* names,
|
||||
std::vector<double>* depths,
|
||||
RiaDefines::DepthType depthType ) const;
|
||||
|
||||
void evaluateFluids( const std::vector<RigWellPathFormation>& fluidFormations,
|
||||
std::vector<QString>* names,
|
||||
std::vector<double>* depths,
|
||||
RiaDefines::DepthType depthType ) const;
|
||||
|
||||
void evaluateFormationsForOnePosition( const std::vector<std::pair<RigWellPathFormation, FormationLevel>>& formations,
|
||||
const FormationLevel& maxLevel,
|
||||
const PickPosition& position,
|
||||
std::map<double, LevelAndName, DepthComp>* uniqueListMaker,
|
||||
RiaDefines::DepthType depthType ) const;
|
||||
|
||||
void depthAndFormationNamesWithoutDuplicatesOnDepth( std::vector<QString>* names,
|
||||
std::vector<double>* measuredDepths,
|
||||
RiaDefines::DepthType depthType ) const;
|
||||
|
||||
bool isFluid( QString formationName );
|
||||
FormationLevel detectLevel( QString formationName );
|
||||
|
||||
private:
|
||||
QString m_filePath;
|
||||
QString m_keyInFile;
|
||||
|
||||
std::map<FormationLevel, bool> m_formationsLevelsPresent;
|
||||
std::set<FormationLevel> m_formationsLevelsPresent;
|
||||
|
||||
std::vector<std::pair<RigWellPathFormation, FormationLevel>> m_formations;
|
||||
std::vector<RigWellPathFormation> m_fluids;
|
||||
|
||||
@@ -122,6 +122,8 @@ set(SOURCE_UNITTEST_FILES
|
||||
${CMAKE_CURRENT_LIST_DIR}/RigHexGradientTools-Test.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RifSurfaceImporter-Test.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RifFormationNamesReader-Test.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RifWellPathFormationReader-Test.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RigWellPathFormations-Test.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RifRoffReader-Test.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RifRoffWriter-Test.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RifRoffFileTools-Test.cpp
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "RifWellPathFormationReader.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::pair<std::vector<QString>, std::vector<double>> allFormations( const RigWellPathFormations& formations, RiaDefines::DepthType depthType )
|
||||
{
|
||||
return formations.depthAndFormationNamesUpToLevel( RiaDefines::WellLogTrackFormationLevel::ALL, false, depthType );
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST( RifWellPathFormationReader, ParseMdAndTvd )
|
||||
{
|
||||
const QString content = "WELLNAME;UNITNAME;TOPMD;BASEMD;TOPTVDSS;BASETVDSS\n"
|
||||
"A-1;Upper zone;100.0;110.0;90.0;95.0\n"
|
||||
"A-1;Lower zone;120.0;130.0;98.0;104.0\n"
|
||||
"B-2;Upper zone;200.0;210.0;180.0;185.0\n";
|
||||
|
||||
auto result = RifWellPathFormationReader::parseWellFormations( content, "picks.txt" );
|
||||
ASSERT_TRUE( result.has_value() );
|
||||
ASSERT_EQ( 2u, result->size() );
|
||||
|
||||
const auto& wellA = result->at( "A-1" );
|
||||
EXPECT_EQ( 2u, wellA.formationNamesCount() );
|
||||
EXPECT_EQ( "picks.txt", wellA.filePath() );
|
||||
EXPECT_EQ( "A-1", wellA.keyInFile() );
|
||||
|
||||
auto [mdNames, mdDepths] = allFormations( wellA, RiaDefines::DepthType::MEASURED_DEPTH );
|
||||
EXPECT_EQ( ( std::vector<QString>{ "Upper zone Top", "Lower zone Top", "Upper zone Base", "Lower zone Base" } ), mdNames );
|
||||
EXPECT_EQ( ( std::vector<double>{ 100.0, 120.0, 110.0, 130.0 } ), mdDepths );
|
||||
|
||||
// TVDSS is stored with negated sign
|
||||
auto [tvdNames, tvdDepths] = allFormations( wellA, RiaDefines::DepthType::TRUE_VERTICAL_DEPTH );
|
||||
EXPECT_EQ( ( std::vector<double>{ -90.0, -98.0, -95.0, -104.0 } ), tvdDepths );
|
||||
|
||||
EXPECT_EQ( 1u, result->at( "B-2" ).formationNamesCount() );
|
||||
}
|
||||
|
||||
TEST( RifWellPathFormationReader, ParseMdOnly )
|
||||
{
|
||||
const QString content = "wellname;unitname;topmd;basemd\n"
|
||||
"A-1;Zone;100.0;110.0\n";
|
||||
|
||||
auto result = RifWellPathFormationReader::parseWellFormations( content, "picks.txt" );
|
||||
ASSERT_TRUE( result.has_value() );
|
||||
|
||||
auto [names, depths] = allFormations( result->at( "A-1" ), RiaDefines::DepthType::MEASURED_DEPTH );
|
||||
EXPECT_EQ( ( std::vector<double>{ 100.0, 110.0 } ), depths );
|
||||
}
|
||||
|
||||
TEST( RifWellPathFormationReader, ParseTvdOnly )
|
||||
{
|
||||
const QString content = "wellname;unitname;toptvdss;basetvdss\n"
|
||||
"A-1;Zone;90.0;95.0\n";
|
||||
|
||||
auto result = RifWellPathFormationReader::parseWellFormations( content, "picks.txt" );
|
||||
ASSERT_TRUE( result.has_value() );
|
||||
|
||||
auto [names, depths] = allFormations( result->at( "A-1" ), RiaDefines::DepthType::TRUE_VERTICAL_DEPTH );
|
||||
EXPECT_EQ( ( std::vector<double>{ -90.0, -95.0 } ), depths );
|
||||
}
|
||||
|
||||
TEST( RifWellPathFormationReader, HeaderAfterShortLinesWithWhitespaceAndCrLf )
|
||||
{
|
||||
const QString content = "Exported picks\r\n"
|
||||
"\r\n"
|
||||
" Well Name ; Unit Name ; Top MD ; Base MD \r\n"
|
||||
"A-1; Zone ;100.0;110.0\r\n";
|
||||
|
||||
auto result = RifWellPathFormationReader::parseWellFormations( content, "picks.txt" );
|
||||
ASSERT_TRUE( result.has_value() );
|
||||
|
||||
auto [names, depths] = allFormations( result->at( "A-1" ), RiaDefines::DepthType::MEASURED_DEPTH );
|
||||
EXPECT_EQ( ( std::vector<QString>{ "Zone Top", "Zone Base" } ), names );
|
||||
EXPECT_EQ( ( std::vector<double>{ 100.0, 110.0 } ), depths );
|
||||
}
|
||||
|
||||
TEST( RifWellPathFormationReader, SkipsMalformedAndEmptyRows )
|
||||
{
|
||||
const QString content = "wellname;unitname;topmd;basemd\n"
|
||||
"A-1;Zone;100.0\n"
|
||||
";;;\n"
|
||||
"A-1;Zone;100.0;110.0\n";
|
||||
|
||||
auto result = RifWellPathFormationReader::parseWellFormations( content, "picks.txt" );
|
||||
ASSERT_TRUE( result.has_value() );
|
||||
EXPECT_EQ( 1u, result->size() );
|
||||
EXPECT_EQ( 1u, result->at( "A-1" ).formationNamesCount() );
|
||||
}
|
||||
|
||||
TEST( RifWellPathFormationReader, ErrorWhenNoDepthColumns )
|
||||
{
|
||||
const QString content = "wellname;unitname;topmd;something\n"
|
||||
"A-1;Zone;100.0;110.0\n";
|
||||
|
||||
auto result = RifWellPathFormationReader::parseWellFormations( content, "picks.txt" );
|
||||
ASSERT_FALSE( result.has_value() );
|
||||
EXPECT_TRUE( result.error().contains( "Neither MD or TVD" ) );
|
||||
}
|
||||
|
||||
TEST( RifWellPathFormationReader, ErrorWhenRequiredColumnsMissing )
|
||||
{
|
||||
const QString content = "wellname;zone;topmd;basemd\n"
|
||||
"A-1;Zone;100.0;110.0\n";
|
||||
|
||||
EXPECT_FALSE( RifWellPathFormationReader::parseWellFormations( content, "picks.txt" ).has_value() );
|
||||
}
|
||||
|
||||
TEST( RifWellPathFormationReader, ErrorWhenNoRows )
|
||||
{
|
||||
EXPECT_FALSE( RifWellPathFormationReader::parseWellFormations( "", "picks.txt" ).has_value() );
|
||||
EXPECT_FALSE( RifWellPathFormationReader::parseWellFormations( "wellname;unitname;topmd;basemd\n", "picks.txt" ).has_value() );
|
||||
}
|
||||
|
||||
TEST( RifWellPathFormationReader, ErrorWhenFileMissing )
|
||||
{
|
||||
EXPECT_FALSE( RifWellPathFormationReader::readWellFormations( "this/file/does/not/exist.txt" ).has_value() );
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "Well/RigWellPathFormations.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include <vector>
|
||||
|
||||
using FormationLevel = RiaDefines::WellLogTrackFormationLevel;
|
||||
using DepthType = RiaDefines::DepthType;
|
||||
|
||||
namespace
|
||||
{
|
||||
RigWellPathFormation formation( const QString& name, double mdTop, double mdBase, double tvdTop = 0.0, double tvdBase = 0.0 )
|
||||
{
|
||||
return RigWellPathFormation{ mdTop, mdBase, tvdTop, tvdBase, name };
|
||||
}
|
||||
|
||||
RigWellPathFormations createHierarchy()
|
||||
{
|
||||
return RigWellPathFormations( { formation( "GARN", 100.0, 200.0 ),
|
||||
formation( "Garn 1", 100.0, 150.0 ),
|
||||
formation( "Garn 2", 150.0, 200.0 ),
|
||||
formation( "Garn 2.1", 150.0, 175.0 ) },
|
||||
"file",
|
||||
"well" );
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST( RigWellPathFormations, DetectsLevels )
|
||||
{
|
||||
RigWellPathFormations formations( { formation( "GARN", 0, 1 ),
|
||||
formation( "Garn", 0, 1 ),
|
||||
formation( "Ile 2", 0, 1 ),
|
||||
formation( "Ile 2.1", 0, 1 ),
|
||||
formation( "Ile 2.1.1", 0, 1 ),
|
||||
formation( "Tofte 1+2", 0, 1 ),
|
||||
formation( "Ror 1.2 A1", 0, 1 ),
|
||||
formation( "Not 1 2", 0, 1 ) },
|
||||
"file",
|
||||
"well" );
|
||||
|
||||
std::vector<FormationLevel> expected = { FormationLevel::GROUP,
|
||||
FormationLevel::LEVEL0,
|
||||
FormationLevel::LEVEL1,
|
||||
FormationLevel::LEVEL2,
|
||||
FormationLevel::LEVEL3,
|
||||
FormationLevel::UNKNOWN };
|
||||
EXPECT_EQ( expected, formations.formationsLevelsPresent() );
|
||||
}
|
||||
|
||||
TEST( RigWellPathFormations, FluidsAreSeparatedFromFormations )
|
||||
{
|
||||
RigWellPathFormations formations( { formation( "Garn", 100.0, 200.0 ), formation( "OIL", 120.0, 140.0 ), formation( "WATER", 140.0, 160.0 ) },
|
||||
"file",
|
||||
"well" );
|
||||
|
||||
EXPECT_EQ( 3u, formations.formationNamesCount() );
|
||||
EXPECT_EQ( std::vector<FormationLevel>{ FormationLevel::LEVEL0 }, formations.formationsLevelsPresent() );
|
||||
|
||||
// A fluid top replaces a fluid base at the same depth
|
||||
auto [names, depths] = formations.depthAndFormationNamesUpToLevel( FormationLevel::NONE, true, DepthType::MEASURED_DEPTH );
|
||||
EXPECT_EQ( ( std::vector<QString>{ "OIL Top", "WATER Top", "WATER Base" } ), names );
|
||||
EXPECT_EQ( ( std::vector<double>{ 120.0, 140.0, 160.0 } ), depths );
|
||||
|
||||
auto [namesWithFormations, depthsWithFormations] =
|
||||
formations.depthAndFormationNamesUpToLevel( FormationLevel::ALL, true, DepthType::MEASURED_DEPTH );
|
||||
EXPECT_EQ( ( std::vector<QString>{ "OIL Top", "WATER Top", "WATER Base", "Garn Top", "Garn Base" } ), namesWithFormations );
|
||||
EXPECT_EQ( ( std::vector<double>{ 120.0, 140.0, 160.0, 100.0, 200.0 } ), depthsWithFormations );
|
||||
}
|
||||
|
||||
TEST( RigWellPathFormations, AllLevelsSkipsDuplicateDepths )
|
||||
{
|
||||
auto [names, depths] = createHierarchy().depthAndFormationNamesUpToLevel( FormationLevel::ALL, false, DepthType::MEASURED_DEPTH );
|
||||
|
||||
EXPECT_EQ( ( std::vector<QString>{ "GARN Top", "Garn 2 Top", "GARN Base", "Garn 2.1 Base" } ), names );
|
||||
EXPECT_EQ( ( std::vector<double>{ 100.0, 150.0, 200.0, 175.0 } ), depths );
|
||||
}
|
||||
|
||||
TEST( RigWellPathFormations, AllLevelsTreatsDepthsWithinToleranceAsEqual )
|
||||
{
|
||||
RigWellPathFormations formations( { formation( "A", 100.0, 110.0 ), formation( "B", 110.05, 120.0 ) }, "file", "well" );
|
||||
|
||||
auto [names, depths] = formations.depthAndFormationNamesUpToLevel( FormationLevel::ALL, false, DepthType::MEASURED_DEPTH );
|
||||
EXPECT_EQ( ( std::vector<QString>{ "A Top", "B Top", "B Base" } ), names );
|
||||
}
|
||||
|
||||
TEST( RigWellPathFormations, UpToLevelPrefersDeepestLevelAtSameDepth )
|
||||
{
|
||||
auto formations = createHierarchy();
|
||||
|
||||
{
|
||||
auto [names, depths] = formations.depthAndFormationNamesUpToLevel( FormationLevel::GROUP, false, DepthType::MEASURED_DEPTH );
|
||||
EXPECT_EQ( ( std::vector<QString>{ "GARN Top", "GARN Base" } ), names );
|
||||
EXPECT_EQ( ( std::vector<double>{ 100.0, 200.0 } ), depths );
|
||||
}
|
||||
{
|
||||
auto [names, depths] = formations.depthAndFormationNamesUpToLevel( FormationLevel::LEVEL1, false, DepthType::MEASURED_DEPTH );
|
||||
EXPECT_EQ( ( std::vector<QString>{ "Garn 1 Top", "Garn 2 Top", "Garn 2 Base" } ), names );
|
||||
EXPECT_EQ( ( std::vector<double>{ 100.0, 150.0, 200.0 } ), depths );
|
||||
}
|
||||
{
|
||||
auto [names, depths] = formations.depthAndFormationNamesUpToLevel( FormationLevel::LEVEL2, false, DepthType::MEASURED_DEPTH );
|
||||
EXPECT_EQ( ( std::vector<QString>{ "Garn 1 Top", "Garn 2.1 Top", "Garn 2.1 Base", "Garn 2 Base" } ), names );
|
||||
EXPECT_EQ( ( std::vector<double>{ 100.0, 150.0, 175.0, 200.0 } ), depths );
|
||||
}
|
||||
{
|
||||
auto [names, depths] = formations.depthAndFormationNamesUpToLevel( FormationLevel::NONE, false, DepthType::MEASURED_DEPTH );
|
||||
EXPECT_TRUE( names.empty() );
|
||||
EXPECT_TRUE( depths.empty() );
|
||||
}
|
||||
}
|
||||
|
||||
TEST( RigWellPathFormations, UsesTvdWhenRequested )
|
||||
{
|
||||
RigWellPathFormations formations( { formation( "Garn", 100.0, 200.0, -90.0, -180.0 ) }, "file", "well" );
|
||||
|
||||
auto [names, depths] = formations.depthAndFormationNamesUpToLevel( FormationLevel::LEVEL0, false, DepthType::TRUE_VERTICAL_DEPTH );
|
||||
EXPECT_EQ( ( std::vector<QString>{ "Garn Base", "Garn Top" } ), names );
|
||||
EXPECT_EQ( ( std::vector<double>{ -180.0, -90.0 } ), depths );
|
||||
}
|
||||
|
||||
TEST( RigWellPathFormations, UnsupportedDepthTypeGivesNoPicks )
|
||||
{
|
||||
RigWellPathFormations formations( { formation( "Garn", 100.0, 200.0 ), formation( "OIL", 120.0, 140.0 ) }, "file", "well" );
|
||||
|
||||
for ( auto level : { FormationLevel::ALL, FormationLevel::LEVEL0 } )
|
||||
{
|
||||
auto [names, depths] = formations.depthAndFormationNamesUpToLevel( level, true, DepthType::TRUE_VERTICAL_DEPTH_RKB );
|
||||
EXPECT_TRUE( names.empty() );
|
||||
EXPECT_TRUE( depths.empty() );
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user