#14878: Fix stale well picks, import errors and overlapping zones

This commit is contained in:
Magne Sjaastad committed 2026-10-08 12:44:14 +02:00
1 parent 029759f548
commit bc62af3bb5
20 files changed
+217 -132

No files matched your search

@@ -18,6 +18,8 @@
#include "RifWellPathFormationReader.h"
#include "RifCsvHtmlTableTools.h"
#include <QFile>
#include <QStringList>
#include <QTextStream>
@@ -27,14 +29,6 @@
namespace
{
//--------------------------------------------------------------------------------------------------
/// The FMU formations.csv format is comma-separated, the "well pick" formats are semicolon-separated
//--------------------------------------------------------------------------------------------------
QChar detectDelimiter( const QString& line )
{
return line.contains( ',' ) ? ',' : ';';
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -104,7 +98,7 @@ std::expected<RifWellPathFormationReader::WellFormations, QString> RifWellPathFo
if ( stream.atEnd() ) return std::unexpected( parseFailure );
const QString rawHeaderLine = stream.readLine();
delimiter = detectDelimiter( rawHeaderLine );
delimiter = RifCsvHtmlTableTools::detectDelimiter( rawHeaderLine );
header = parseHeader( rawHeaderLine, delimiter );
}
@@ -223,14 +223,6 @@ RimEclipseResultCase* RimParameterRftCrossPlot::eclipseCase() const
return m_eclipseCase();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RimParameterRftCrossPlot::useDepthRange() const
{
return m_filterMode() != RimRftCrossPlotTools::DepthFilterMode::NONE;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -292,8 +284,7 @@ RiuQwtPlotWidget* RimParameterRftCrossPlot::viewer()
//--------------------------------------------------------------------------------------------------
std::vector<RimRftCrossPlotTools::DepthInterval> RimParameterRftCrossPlot::depthIntervals() const
{
return RimRftCrossPlotTools::buildDepthIntervals( useDepthRange(),
m_filterMode(),
return RimRftCrossPlotTools::buildDepthIntervals( m_filterMode(),
m_depthRangeMin(),
m_depthRangeMax(),
m_wellFormations(),
@@ -387,15 +378,11 @@ void RimParameterRftCrossPlot::updateAxes()
const int axisTitleSize = caf::FontTools::absolutePointSize( RiaPreferences::current()->defaultPlotFontSize(), m_axisTitleFontSize() );
const int axisValueSize = caf::FontTools::absolutePointSize( RiaPreferences::current()->defaultPlotFontSize(), m_axisValueFontSize() );
const QString pressureLabel = m_samplingMode() == SamplingMode::ALL_SAMPLES ? "Pressure" : "Mean Pressure";
const QString filterDescription = RimRftCrossPlotTools::depthFilterDescription( useDepthRange(),
m_filterMode(),
m_depthType(),
m_depthRangeMin(),
m_depthRangeMax(),
m_selectedZones() );
const QString depthLabel = filterDescription.isEmpty() ? pressureLabel
: QString( "%1 [%2]" ).arg( pressureLabel ).arg( filterDescription );
const QString pressureLabel = m_samplingMode() == SamplingMode::ALL_SAMPLES ? "Pressure" : "Mean Pressure";
const QString filterDescription =
RimRftCrossPlotTools::depthFilterDescription( m_filterMode(), m_depthType(), m_depthRangeMin(), m_depthRangeMax(), m_selectedZones() );
const QString depthLabel = filterDescription.isEmpty() ? pressureLabel
: QString( "%1 [%2]" ).arg( pressureLabel ).arg( filterDescription );
m_plotWidget->setAxisTitleText( RiuPlotAxis::defaultLeft(), depthLabel );
m_plotWidget->setAxisTitleEnabled( RiuPlotAxis::defaultLeft(), true );
@@ -844,12 +831,8 @@ void RimParameterRftCrossPlot::updatePlotTitle()
if ( m_useAutoPlotTitle && m_ensemble() )
{
const QString filterDescription = RimRftCrossPlotTools::depthFilterDescription( useDepthRange(),
m_filterMode(),
m_depthType(),
m_depthRangeMin(),
m_depthRangeMax(),
m_selectedZones() );
const QString filterDescription =
RimRftCrossPlotTools::depthFilterDescription( m_filterMode(), m_depthType(), m_depthRangeMin(), m_depthRangeMax(), m_selectedZones() );
if ( !filterDescription.isEmpty() )
{
m_description =
@@ -83,7 +83,6 @@ public:
QDateTime selectedTimeStep() const;
RimSummaryEnsemble* ensemble() const;
RimEclipseResultCase* eclipseCase() const;
bool useDepthRange() const;
double depthRangeMin() const;
double depthRangeMax() const;
RiaDefines::DepthType depthType() const;
@@ -537,7 +537,6 @@ void RimRftCorrelationReportPlot::syncTornadoInputsFromCrossPlot()
m_tornadoPlot->setTimeStep( m_parameterRftCrossPlot->selectedTimeStep() );
m_tornadoPlot->setSelectedParameter( m_parameterRftCrossPlot->ensembleParameter() );
m_tornadoPlot->setEclipseCase( m_parameterRftCrossPlot->eclipseCase() );
m_tornadoPlot->setUseDepthRange( m_parameterRftCrossPlot->useDepthRange() );
m_tornadoPlot->setDepthRange( m_parameterRftCrossPlot->depthRangeMin(), m_parameterRftCrossPlot->depthRangeMax() );
m_tornadoPlot->setDepthType( m_parameterRftCrossPlot->depthType() );
m_tornadoPlot->setFilterMode( m_parameterRftCrossPlot->filterMode() );
@@ -554,7 +553,6 @@ void RimRftCorrelationReportPlot::applyWellFormationsToSubPlots()
RimWellFormationsFile* wellFormationsFile = m_parameterRftCrossPlot->wellFormationsFile();
m_parameterRftCrossPlot->setWellFormations( wellFormationsFile );
if ( m_tornadoPlot() ) m_tornadoPlot->setWellFormations( wellFormationsFile );
}
@@ -101,8 +101,7 @@ std::vector<double> RimRftCrossPlotTools::filterPressuresByDepthRange( const std
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RimRftCrossPlotTools::DepthInterval> RimRftCrossPlotTools::buildDepthIntervals( bool useFilter,
DepthFilterMode mode,
std::vector<RimRftCrossPlotTools::DepthInterval> RimRftCrossPlotTools::buildDepthIntervals( DepthFilterMode mode,
double depthRangeMin,
double depthRangeMax,
RimWellFormationsFile* wellFormationsFile,
@@ -110,15 +109,15 @@ std::vector<RimRftCrossPlotTools::DepthInterval> RimRftCrossPlotTools::buildDept
const std::vector<QString>& selectedZones,
RiaDefines::DepthType depthType )
{
if ( !useFilter || mode == DepthFilterMode::NONE ) return {};
if ( mode == DepthFilterMode::NONE ) return {};
if ( mode == DepthFilterMode::DEPTH_RANGE ) return { DepthInterval{ depthRangeMin, depthRangeMax } };
// ZONES mode
if ( !wellFormationsFile || selectedZones.empty() ) return {};
std::optional<RigWellPathFormations> formations = wellFormationsFile->formationsForWell( wellName );
if ( !formations.has_value() ) return {};
const RigWellPathFormations* formations = wellFormationsFile->formationsForWell( wellName );
if ( !formations ) return {};
std::vector<DepthInterval> intervals;
for ( size_t i = 0; i < formations->formationCount(); ++i )
@@ -169,14 +168,13 @@ std::vector<double> RimRftCrossPlotTools::filterPressuresByDepthIntervals( const
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimRftCrossPlotTools::depthFilterDescription( bool useFilter,
DepthFilterMode mode,
QString RimRftCrossPlotTools::depthFilterDescription( DepthFilterMode mode,
RiaDefines::DepthType depthType,
double depthRangeMin,
double depthRangeMax,
const std::vector<QString>& selectedZones )
{
if ( !useFilter || mode == DepthFilterMode::NONE ) return {};
if ( mode == DepthFilterMode::NONE ) return {};
if ( mode == DepthFilterMode::ZONES )
{
@@ -83,10 +83,9 @@ std::vector<double> filterPressuresByDepthRange( const std::vector<double>& dept
double depthRangeMax );
// Builds the active depth filter intervals from either a depth range or a set of selected zones.
// Returns an empty vector (meaning "no filter") when useFilter is false, or when ZONES mode is
// Returns an empty vector (meaning "no filter") when the mode is NONE, or when ZONES mode is
// selected but no well formations file, well name, or matching zones are available.
std::vector<DepthInterval> buildDepthIntervals( bool useFilter,
DepthFilterMode mode,
std::vector<DepthInterval> buildDepthIntervals( DepthFilterMode mode,
double depthRangeMin,
double depthRangeMax,
RimWellFormationsFile* wellFormationsFile,
@@ -103,8 +102,7 @@ std::vector<double> filterPressuresByDepthIntervals( const std::vector<double>&
// Short description of the active depth filter for titles/axis labels/group text, e.g.
// "MD 1000 - 2000 m" or "Zones: Valysar, Therys". Empty when filtering is disabled.
QString depthFilterDescription( bool useFilter,
DepthFilterMode mode,
QString depthFilterDescription( DepthFilterMode mode,
RiaDefines::DepthType depthType,
double depthRangeMin,
double depthRangeMax,
@@ -61,10 +61,9 @@ RimRftTornadoPlot::RimRftTornadoPlot()
CAF_PDM_InitField( &m_wellName, "WellName", QString(), "Well Name" );
CAF_PDM_InitFieldNoDefault( &m_selectedTimeStep, "TimeStep", "Time Step" );
CAF_PDM_InitFieldNoDefault( &m_eclipseCase, "EclipseCase", "Eclipse Case (MD fallback)" );
CAF_PDM_InitField( &m_useDepthRange, "UseDepthRange", false, "Filter by Depth Range" );
CAF_PDM_InitField( &m_filterMode,
"FilterMode",
RimRftCrossPlotTools::DepthFilterModeEnum( RimRftCrossPlotTools::DepthFilterMode::DEPTH_RANGE ),
RimRftCrossPlotTools::DepthFilterModeEnum( RimRftCrossPlotTools::DepthFilterMode::NONE ),
"Filter By" );
CAF_PDM_InitField( &m_depthRangeMin, "DepthRangeMin", 0.0, "Min Depth" );
CAF_PDM_InitField( &m_depthRangeMax, "DepthRangeMax", 5000.0, "Max Depth" );
@@ -138,14 +137,6 @@ void RimRftTornadoPlot::setEclipseCase( RimEclipseResultCase* eclipseCase )
m_eclipseCase = eclipseCase;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimRftTornadoPlot::setUseDepthRange( bool useDepthRange )
{
m_useDepthRange = useDepthRange;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -397,8 +388,7 @@ void RimRftTornadoPlot::onPlotItemSelected( std::shared_ptr<RiuPlotItem> plotIte
//--------------------------------------------------------------------------------------------------
std::vector<RimRftCrossPlotTools::DepthInterval> RimRftTornadoPlot::depthIntervals() const
{
return RimRftCrossPlotTools::buildDepthIntervals( m_useDepthRange(),
m_filterMode(),
return RimRftCrossPlotTools::buildDepthIntervals( m_filterMode(),
m_depthRangeMin(),
m_depthRangeMax(),
m_wellFormations(),
@@ -466,14 +456,10 @@ void RimRftTornadoPlot::updatePlotTitle()
if ( m_useAutoPlotTitle() && m_ensemble() )
{
const QString filterDescription = RimRftCrossPlotTools::depthFilterDescription( m_useDepthRange(),
m_filterMode(),
m_depthType(),
m_depthRangeMin(),
m_depthRangeMax(),
m_selectedZones() );
const QString rangeStr = filterDescription.isEmpty() ? QString() : QString( " [%1]" ).arg( filterDescription );
m_description = QString( "Parameter Correlation vs RFT Pressure%1, %2" ).arg( rangeStr ).arg( m_ensemble->name() );
const QString filterDescription =
RimRftCrossPlotTools::depthFilterDescription( m_filterMode(), m_depthType(), m_depthRangeMin(), m_depthRangeMax(), m_selectedZones() );
const QString rangeStr = filterDescription.isEmpty() ? QString() : QString( " [%1]" ).arg( filterDescription );
m_description = QString( "Parameter Correlation vs RFT Pressure%1, %2" ).arg( rangeStr ).arg( m_ensemble->name() );
}
m_plotWidget->setPlotTitle( m_description() );
@@ -62,7 +62,6 @@ public:
void setWellName( const QString& wellName );
void setTimeStep( const QDateTime& timeStep );
void setEclipseCase( RimEclipseResultCase* eclipseCase );
void setUseDepthRange( bool useDepthRange );
void setDepthRange( double minMd, double maxMd );
void setDepthType( RiaDefines::DepthType depthType );
void setFilterMode( RimRftCrossPlotTools::DepthFilterMode filterMode );
@@ -107,7 +106,6 @@ private:
caf::PdmField<QString> m_wellName;
caf::PdmField<QDateTime> m_selectedTimeStep;
caf::PdmPtrField<RimEclipseResultCase*> m_eclipseCase;
caf::PdmField<bool> m_useDepthRange;
caf::PdmField<RimRftCrossPlotTools::DepthFilterModeEnum> m_filterMode;
caf::PdmField<double> m_depthRangeMin;
caf::PdmField<double> m_depthRangeMax;
@@ -18,7 +18,6 @@
#include "RimWellFormationsCollection.h"
#include "RiaLogging.h"
#include "RimWellFormationsFile.h"
#include "RiuMessageDialog.h"
@@ -27,6 +26,8 @@
#include <QFileInfo>
#include <memory>
CAF_PDM_SOURCE_INIT( RimWellFormationsCollection, "WellFormationsCollection" );
//--------------------------------------------------------------------------------------------------
@@ -42,9 +43,10 @@ RimWellFormationsCollection::RimWellFormationsCollection()
}
//--------------------------------------------------------------------------------------------------
/// Returns the existing entry for filePath if present, otherwise imports and returns a new one
/// Returns the existing entry for filePath if present, otherwise imports and returns a new one. A
/// file that cannot be read is not added.
//--------------------------------------------------------------------------------------------------
RimWellFormationsFile* RimWellFormationsCollection::findOrCreate( const QString& filePath )
std::expected<RimWellFormationsFile*, QString> RimWellFormationsCollection::findOrCreate( const QString& filePath )
{
const QString absoluteFilePath = QFileInfo( filePath ).absoluteFilePath();
@@ -52,20 +54,25 @@ RimWellFormationsFile* RimWellFormationsCollection::findOrCreate( const QString&
{
if ( QFileInfo( file->filePath() ).absoluteFilePath() == absoluteFilePath )
{
// An earlier read may have failed, e.g. if the file was missing or malformed at the time
if ( file->wellNames().isEmpty() )
{
if ( auto result = file->reload(); !result ) return std::unexpected( result.error() );
}
return file;
}
}
auto newFile = new RimWellFormationsFile();
auto newFile = std::make_unique<RimWellFormationsFile>();
newFile->setFilePath( filePath );
if ( auto result = newFile->reload(); !result )
{
RiaLogging::error( result.error().toStdString() );
return std::unexpected( result.error() );
}
m_wellFormationsFiles.push_back( newFile );
m_wellFormationsFiles.push_back( newFile.get() );
return newFile;
return newFile.release();
}
//--------------------------------------------------------------------------------------------------
@@ -76,7 +83,7 @@ RimWellFormationsFile* RimWellFormationsCollection::findFileForWell( const QStri
{
for ( RimWellFormationsFile* file : m_wellFormationsFiles )
{
if ( file->formationsForWell( wellName ).has_value() ) return file;
if ( file->formationsForWell( wellName ) ) return file;
}
return nullptr;
@@ -92,15 +99,14 @@ std::vector<RimWellFormationsFile*> RimWellFormationsCollection::importFiles( co
for ( const QString& filePath : filePaths )
{
auto newFile = new RimWellFormationsFile();
newFile->setFilePath( filePath );
if ( auto result = newFile->reload(); !result )
auto file = findOrCreate( filePath );
if ( !file )
{
totalErrorMessage += "\nError in: " + filePath + "\n\t" + result.error();
totalErrorMessage += "\nError in: " + filePath + "\n\t" + file.error();
continue;
}
m_wellFormationsFiles.push_back( newFile );
importedFiles.push_back( newFile );
importedFiles.push_back( *file );
}
if ( !totalErrorMessage.isEmpty() )
@@ -111,6 +117,15 @@ std::vector<RimWellFormationsFile*> RimWellFormationsCollection::importFiles( co
return importedFiles;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellFormationsCollection::onChildDeleted( caf::PdmChildArrayFieldHandle* childArray,
std::vector<caf::PdmObjectHandle*>& referringObjects )
{
RimWellFormationsFile::updateReferringObjects( referringObjects );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -21,6 +21,10 @@
#include "cafPdmChildArrayField.h"
#include "cafPdmObject.h"
#include <QString>
#include <expected>
namespace caf
{
class CmdFeatureMenuBuilder;
@@ -42,13 +46,14 @@ public:
const caf::PdmChildArrayField<RimWellFormationsFile*>& wellFormationsFiles() const { return m_wellFormationsFiles; }
RimWellFormationsFile* findOrCreate( const QString& filePath );
std::expected<RimWellFormationsFile*, QString> findOrCreate( const QString& filePath );
RimWellFormationsFile* findFileForWell( const QString& wellName ) const;
std::vector<RimWellFormationsFile*> importFiles( const QStringList& filePaths );
protected:
void onChildDeleted( caf::PdmChildArrayFieldHandle* childArray, std::vector<caf::PdmObjectHandle*>& referringObjects ) override;
void appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const override;
private:
@@ -21,6 +21,10 @@
#include "RiaLogging.h"
#include "RifCsvHtmlTableTools.h"
#include "RimMainPlotCollection.h"
#include "RimProject.h"
#include "RimWellPath.h"
#include "cafPdmUiFilePathEditor.h"
#include "cafPdmUiTextEditor.h"
#include "cafPdmUiTreeOrdering.h"
@@ -82,12 +86,12 @@ std::expected<void, QString> RimWellFormationsFile::reload()
if ( !result )
{
m_wellFormations.clear();
updateContentTable();
m_contentTable = "";
return std::unexpected( result.error() );
}
m_wellFormations = std::move( *result );
updateContentTable();
m_contentTable = "";
return {};
}
@@ -125,11 +129,11 @@ QStringList RimWellFormationsFile::zoneNames( const QString& wellName ) const
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::optional<RigWellPathFormations> RimWellFormationsFile::formationsForWell( const QString& wellName ) const
const RigWellPathFormations* RimWellFormationsFile::formationsForWell( const QString& wellName ) const
{
if ( auto it = m_wellFormations.find( wellName ); it != m_wellFormations.end() )
{
return it->second;
return &it->second;
}
// Fall back to a normalized name match, to handle differences between e.g. RFT and FMU well names
@@ -138,11 +142,11 @@ std::optional<RigWellPathFormations> RimWellFormationsFile::formationsForWell( c
{
if ( normalizedWellName( candidateName ) == normalizedTarget )
{
return formations;
return &formations;
}
}
return std::nullopt;
return nullptr;
}
//--------------------------------------------------------------------------------------------------
@@ -167,9 +171,30 @@ void RimWellFormationsFile::fieldChangedByUi( const caf::PdmFieldHandle* changed
{
RiaLogging::error( result.error().toStdString() );
}
updateReferringObjects( objectsWithReferringPtrFields() );
}
}
//--------------------------------------------------------------------------------------------------
/// Well paths cache the formations resolved from their file, so they must be refreshed when the
/// file is reloaded or deleted
//--------------------------------------------------------------------------------------------------
void RimWellFormationsFile::updateReferringObjects( const std::vector<caf::PdmObjectHandle*>& referringObjects )
{
for ( caf::PdmObjectHandle* object : referringObjects )
{
if ( auto wellPath = dynamic_cast<RimWellPath*>( object ) )
{
wellPath->refreshFormationsFromFile();
wellPath->updateConnectedEditors();
}
}
RimProject::current()->scheduleCreateDisplayModelAndRedrawAllViews();
RimMainPlotCollection::current()->updatePlotsWithFormations();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -183,6 +208,9 @@ void RimWellFormationsFile::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTree
//--------------------------------------------------------------------------------------------------
void RimWellFormationsFile::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
{
// Built on demand, as the table is only needed when the file is shown in the property editor
if ( m_contentTable().isEmpty() ) updateContentTable();
uiOrdering.add( &m_filePath );
uiOrdering.add( &m_contentTable );
uiOrdering.skipRemainingFields();
@@ -231,7 +259,9 @@ void RimWellFormationsFile::updateUiTreeName()
//--------------------------------------------------------------------------------------------------
void RimWellFormationsFile::updateContentTable()
{
auto result = RifCsvHtmlTableTools::generateHtmlTableFromFile( filePath() );
const int maxRowCount = 1000;
auto result = RifCsvHtmlTableTools::generateHtmlTableFromFile( filePath(), maxRowCount );
if ( result )
{
m_contentTable = *result;
@@ -25,6 +25,7 @@
#include <expected>
#include <optional>
#include <vector>
//==================================================================================================
/// A single well formations/well picks file (e.g. an FMU formations.csv or a well pick export),
@@ -44,11 +45,12 @@ public:
std::expected<void, QString> reload();
QStringList wellNames() const;
QStringList zoneNames( const QString& wellName ) const;
std::optional<RigWellPathFormations> formationsForWell( const QString& wellName ) const;
QStringList wellNames() const;
QStringList zoneNames( const QString& wellName ) const;
const RigWellPathFormations* formationsForWell( const QString& wellName ) const;
static QString normalizedWellName( const QString& wellName );
static void updateReferringObjects( const std::vector<caf::PdmObjectHandle*>& referringObjects );
protected:
void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override;
@@ -195,13 +195,11 @@ void RimWellLogFormationSettings::setWellNameInFormationsFile( const QString& we
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::optional<RigWellPathFormations> RimWellLogFormationSettings::resolveWellPickFormations() const
const RigWellPathFormations* RimWellLogFormationSettings::resolveWellPickFormations() const
{
if ( m_formationSource() == RiaDefines::WellLogTrackFormationSource::WELL_PICK_FILTER && m_formationWellPathForSourceWellPath() )
{
const RigWellPathFormations* formations = m_formationWellPathForSourceWellPath->formationsGeometry();
if ( formations ) return *formations;
return std::nullopt;
return m_formationWellPathForSourceWellPath->formationsGeometry();
}
if ( m_formationSource() == RiaDefines::WellLogTrackFormationSource::WELL_PICKS_NO_TRAJECTORY && m_wellFormationsFile() &&
@@ -210,7 +208,7 @@ std::optional<RigWellPathFormations> RimWellLogFormationSettings::resolveWellPic
return m_wellFormationsFile->formationsForWell( m_wellNameInFormationsFile() );
}
return std::nullopt;
return nullptr;
}
//--------------------------------------------------------------------------------------------------
@@ -333,7 +331,7 @@ void RimWellLogFormationSettings::uiOrdering( const QString& uiConfigName, caf::
// WELL_PICKS_NO_TRAJECTORY source, so this source only ever shows the well path picker.
uiOrdering.add( &m_formationWellPathForSourceWellPath );
if ( resolveWellPickFormations().has_value() )
if ( resolveWellPickFormations() )
{
uiOrdering.add( &m_formationLevel );
uiOrdering.add( &m_showFormationFluids );
@@ -346,7 +344,7 @@ void RimWellLogFormationSettings::uiOrdering( const QString& uiConfigName, caf::
uiOrdering.add( &m_wellFormationsFile );
if ( m_wellFormationsFile() ) uiOrdering.add( &m_wellNameInFormationsFile );
if ( resolveWellPickFormations().has_value() )
if ( resolveWellPickFormations() )
{
uiOrdering.add( &m_formationLevel );
uiOrdering.add( &m_showFormationFluids );
@@ -78,7 +78,7 @@ public:
// Resolves the formations to show for the WELL_PICK_FILTER source: the selected well path's
// own formations if present, otherwise the well looked up directly in wellFormationsFile().
std::optional<RigWellPathFormations> resolveWellPickFormations() const;
const RigWellPathFormations* resolveWellPickFormations() const;
// Simulation well
QString simWellName() const;
@@ -2607,7 +2607,7 @@ void RimWellLogTrack::updateFormationNamesOnPlot()
m_formationSettings->formationSource() == RiaDefines::WellLogTrackFormationSource::WELL_PICKS_NO_TRAJECTORY )
{
auto formations = m_formationSettings->resolveWellPickFormations();
if ( !formations.has_value() ) return;
if ( !formations ) return;
if ( plot->depthType() != RiaDefines::DepthType::MEASURED_DEPTH && plot->depthType() != RiaDefines::DepthType::TRUE_VERTICAL_DEPTH &&
plot->depthType() != RiaDefines::DepthType::TRUE_VERTICAL_DEPTH_RKB )
@@ -22,6 +22,7 @@
#include "RiaColorTables.h"
#include "RiaFieldHandleTools.h"
#include "RiaLogging.h"
#include "RiaSimWellBranchTools.h"
#include "RiaWellNameComparer.h"
@@ -489,6 +490,7 @@ void RimWellPath::fieldChangedByUi( const caf::PdmFieldHandle* changedField, con
{
refreshFormationsFromFile();
proj->scheduleCreateDisplayModelAndRedrawAllViews();
RimMainPlotCollection::current()->updatePlotsWithFormations();
}
else
{
@@ -584,8 +586,14 @@ void RimWellPath::initAfterRead()
{
if ( !oilField->wellFormationsCollection() ) oilField->wellFormationsCollection = new RimWellFormationsCollection();
auto* file = oilField->wellFormationsCollection->findOrCreate( m_wellPathFormationFilePath_OBSOLETE().path() );
setWellFormationsFile( file, m_formationKeyInFile() );
if ( auto file = oilField->wellFormationsCollection->findOrCreate( m_wellPathFormationFilePath_OBSOLETE().path() ) )
{
setWellFormationsFile( *file, m_formationKeyInFile() );
}
else
{
RiaLogging::error( file.error().toStdString() );
}
}
}
}
@@ -1058,10 +1066,18 @@ bool RimWellPath::refreshFormationsFromFile()
{
if ( !m_wellFormationsFile() )
{
m_wellPathFormations.reset();
return false;
}
m_wellPathFormations = m_wellFormationsFile()->formationsForWell( m_formationKeyInFile() );
if ( auto formations = m_wellFormationsFile()->formationsForWell( m_formationKeyInFile() ) )
{
m_wellPathFormations = *formations;
}
else
{
m_wellPathFormations.reset();
}
return m_wellPathFormations.has_value();
}
@@ -536,11 +536,19 @@ void RimWellPathCollection::addWellPathFormations( const QStringList& filePaths
// The collection is deletable from the project tree
if ( !oilField->wellFormationsCollection() ) oilField->wellFormationsCollection = new RimWellFormationsCollection();
bool fileReadSuccess = false;
bool fileReadSuccess = false;
QString errorMessage;
for ( const QString& filePath : filePaths )
{
RimWellFormationsFile* wellFormationsFile = oilField->wellFormationsCollection->findOrCreate( filePath );
auto file = oilField->wellFormationsCollection->findOrCreate( filePath );
if ( !file )
{
errorMessage += "\nError in: " + filePath + "\n\t" + file.error();
continue;
}
RimWellFormationsFile* wellFormationsFile = *file;
for ( const QString& wellName : wellFormationsFile->wellNames() )
{
@@ -575,6 +583,11 @@ void RimWellPathCollection::addWellPathFormations( const QStringList& filePaths
RiuMessageDialog::showError( Riu3DMainWindowTools::mainWindowWidget(), "Well Picks Import", outputMessage );
}
if ( !errorMessage.isEmpty() )
{
RiuMessageDialog::showError( Riu3DMainWindowTools::mainWindowWidget(), "Well Picks Import", errorMessage );
}
sortWellsByName();
updateAllRequiredEditors();
}
@@ -281,16 +281,22 @@ std::pair<std::vector<QString>, std::vector<double>>
///
//--------------------------------------------------------------------------------------------------
/// Returns one (name, top, base) range per formation whose level does not exceed the given level,
/// for use as shaded zone regions. Unlike depthAndFormationNamesUpToLevel(), this does not merge
/// hierarchical levels at coincident depths, so a parent/child formation pair can overlap in the
/// result when both satisfy the level filter (e.g. requesting LEVEL1 also includes any GROUP-level
/// parent zone covering the same depth range).
/// for use as shaded zone regions. Each zone is clipped against the more detailed zones inside it, so
/// parent and child zones are not drawn on top of each other.
//--------------------------------------------------------------------------------------------------
std::vector<std::tuple<QString, double, double>> RigWellPathFormations::depthRangesUpToLevel( FormationLevel level,
RiaDefines::DepthType depthType ) const
{
std::vector<std::tuple<QString, double, double>> result;
if ( level == FormationLevel::NONE ) return result;
struct Zone
{
QString name;
double top;
double base;
FormationLevel level;
};
std::vector<Zone> zones;
if ( level == FormationLevel::NONE ) return {};
for ( const auto& [formation, formationLevel] : m_formations )
{
@@ -300,7 +306,49 @@ std::vector<std::tuple<QString, double, double>> RigWellPathFormations::depthRan
auto base = pickDepth( formation, PickPosition::BASE, depthType );
if ( !top || !base ) continue;
result.emplace_back( formation.formationName, *top, *base );
zones.push_back( { formation.formationName, *top, *base, formationLevel } );
}
// UNKNOWN has no place in the hierarchy, so it neither hides nor is hidden by other zones
auto isMoreDetailed = []( FormationLevel candidate, FormationLevel reference )
{ return candidate <= FormationLevel::LEVEL10 && reference <= FormationLevel::LEVEL10 && candidate > reference; };
const double minThickness = 0.1;
std::vector<std::tuple<QString, double, double>> result;
for ( const auto& zone : zones )
{
const double lower = std::min( zone.top, zone.base );
const double upper = std::max( zone.top, zone.base );
std::vector<std::pair<double, double>> covered;
for ( const auto& other : zones )
{
if ( !isMoreDetailed( other.level, zone.level ) ) continue;
covered.emplace_back( std::min( other.top, other.base ), std::max( other.top, other.base ) );
}
std::sort( covered.begin(), covered.end() );
// Keep the parts of the zone not covered by a more detailed zone, in the zone's own direction
auto addPart = [&]( double from, double to )
{
if ( to - from <= minThickness ) return;
if ( zone.top <= zone.base )
result.emplace_back( zone.name, from, to );
else
result.emplace_back( zone.name, to, from );
};
double current = lower;
for ( const auto& [coveredLower, coveredUpper] : covered )
{
if ( coveredLower >= upper ) break;
if ( coveredUpper <= current ) continue;
addPart( current, std::min( coveredLower, upper ) );
current = std::max( current, coveredUpper );
}
addPart( current, upper );
}
return result;
}
@@ -147,10 +147,19 @@ TEST( RigWellPathFormations, DepthRangesUpToLevelReturnsZoneIntervals )
}
{
auto ranges = formations.depthRangesUpToLevel( FormationLevel::LEVEL1, DepthType::MEASURED_DEPTH );
ASSERT_EQ( 2u, ranges.size() );
EXPECT_EQ( QString( "Garn 1" ), std::get<0>( ranges[0] ) );
EXPECT_EQ( QString( "Garn 2" ), std::get<0>( ranges[1] ) );
}
{
// Garn 2 is only partly covered by Garn 2.1, so the remainder is kept
auto ranges = formations.depthRangesUpToLevel( FormationLevel::ALL, DepthType::MEASURED_DEPTH );
ASSERT_EQ( 3u, ranges.size() );
EXPECT_EQ( QString( "GARN" ), std::get<0>( ranges[0] ) );
EXPECT_EQ( QString( "Garn 1" ), std::get<0>( ranges[1] ) );
EXPECT_EQ( QString( "Garn 2" ), std::get<0>( ranges[2] ) );
EXPECT_EQ( QString( "Garn 1" ), std::get<0>( ranges[0] ) );
EXPECT_EQ( QString( "Garn 2" ), std::get<0>( ranges[1] ) );
EXPECT_DOUBLE_EQ( 175.0, std::get<1>( ranges[1] ) );
EXPECT_DOUBLE_EQ( 200.0, std::get<2>( ranges[1] ) );
EXPECT_EQ( QString( "Garn 2.1" ), std::get<0>( ranges[2] ) );
}
{
auto ranges = formations.depthRangesUpToLevel( FormationLevel::NONE, DepthType::MEASURED_DEPTH );
@@ -105,12 +105,11 @@ TEST( RimRftCrossPlotToolsTest, DepthTypeAbbreviation )
}
//--------------------------------------------------------------------------------------------------
/// Disabled filtering returns no intervals regardless of mode.
/// Disabled filtering returns no intervals.
//--------------------------------------------------------------------------------------------------
TEST( RimRftCrossPlotToolsTest, BuildDepthIntervals_DisabledReturnsEmpty )
{
auto intervals = RimRftCrossPlotTools::buildDepthIntervals( false,
RimRftCrossPlotTools::DepthFilterMode::DEPTH_RANGE,
auto intervals = RimRftCrossPlotTools::buildDepthIntervals( RimRftCrossPlotTools::DepthFilterMode::NONE,
1000.0,
2000.0,
nullptr,
@@ -125,8 +124,7 @@ TEST( RimRftCrossPlotToolsTest, BuildDepthIntervals_DisabledReturnsEmpty )
//--------------------------------------------------------------------------------------------------
TEST( RimRftCrossPlotToolsTest, BuildDepthIntervals_DepthRangeProducesSingleInterval )
{
auto intervals = RimRftCrossPlotTools::buildDepthIntervals( true,
RimRftCrossPlotTools::DepthFilterMode::DEPTH_RANGE,
auto intervals = RimRftCrossPlotTools::buildDepthIntervals( RimRftCrossPlotTools::DepthFilterMode::DEPTH_RANGE,
1000.0,
2000.0,
nullptr,
@@ -143,8 +141,7 @@ TEST( RimRftCrossPlotToolsTest, BuildDepthIntervals_DepthRangeProducesSingleInte
//--------------------------------------------------------------------------------------------------
TEST( RimRftCrossPlotToolsTest, BuildDepthIntervals_ZonesWithoutFileReturnsEmpty )
{
auto intervals = RimRftCrossPlotTools::buildDepthIntervals( true,
RimRftCrossPlotTools::DepthFilterMode::ZONES,
auto intervals = RimRftCrossPlotTools::buildDepthIntervals( RimRftCrossPlotTools::DepthFilterMode::ZONES,
1000.0,
2000.0,
nullptr,
@@ -187,8 +184,7 @@ TEST( RimRftCrossPlotToolsTest, FilterPressuresByDepthIntervals_FiltersByMultipl
//--------------------------------------------------------------------------------------------------
TEST( RimRftCrossPlotToolsTest, DepthFilterDescription_DisabledReturnsEmpty )
{
QString description = RimRftCrossPlotTools::depthFilterDescription( false,
RimRftCrossPlotTools::DepthFilterMode::DEPTH_RANGE,
QString description = RimRftCrossPlotTools::depthFilterDescription( RimRftCrossPlotTools::DepthFilterMode::NONE,
RiaDefines::DepthType::MEASURED_DEPTH,
1000.0,
2000.0,
@@ -201,8 +197,7 @@ TEST( RimRftCrossPlotToolsTest, DepthFilterDescription_DisabledReturnsEmpty )
//--------------------------------------------------------------------------------------------------
TEST( RimRftCrossPlotToolsTest, DepthFilterDescription_ZonesListsSelectedZoneNames )
{
QString description = RimRftCrossPlotTools::depthFilterDescription( true,
RimRftCrossPlotTools::DepthFilterMode::ZONES,
QString description = RimRftCrossPlotTools::depthFilterDescription( RimRftCrossPlotTools::DepthFilterMode::ZONES,
RiaDefines::DepthType::MEASURED_DEPTH,
1000.0,
2000.0,