Files
ResInsight/ApplicationLibCode/ReservoirDataModel/RigLasFileExporter.cpp
T
Magne Sjaastad 7728973ddc #12674: Export TVDMSL in the depth unit of the curve
TVDMSL was always written with unit M and the comment 'True vertical
depth in meters', also for a FIELD case whose values are in feet.
RigLasFileExporter now writes TVDMSL with the depth unit of the
curve, as it already does for DEPTH and TVDRKB.

Compute the LAS unit string once via a depthUnitString() helper and
reuse it for DEPTH, TVDMSL, TVDRKB and setDepthUnit, instead of
repeating the unit if/else chain for each quantity.
2026-10-23 16:23:58 +02:00

568 lines
20 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2016- Statoil 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 <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RigLasFileExporter.h"
#include "RiaDefines.h"
#include "RiaGuiApplication.h"
#include "Well/RigWellLogCurveData.h"
#include "Well/RigWellPath.h"
#include "RimCase.h"
#include "RimWellLogCurve.h"
#include "RimWellLogExtractionCurve.h"
#include "RimWellPath.h"
#include "cafUtils.h"
#include "cafAssert.h"
#include "laswell.hpp"
#include <QDir>
#include <QMessageBox>
#include <cmath> // Needed for HUGE_VAL on Linux
namespace NRLib
{
class LasWell;
};
class SingleChannelData
{
public:
SingleChannelData( const std::string& channelName, const std::string& unit, const std::string& comment, const RigWellLogCurveData& curveData )
: m_channelName( channelName )
, m_unit( unit )
, m_comment( comment )
, m_curveData( curveData )
{
}
void appendDataToLasFile( NRLib::LasWell* lasFile, double absentValue ) const
{
CAF_ASSERT( lasFile );
if ( !m_curveData.propertyValues().empty() )
{
std::vector<double> wellLogValues = m_curveData.propertyValues( QString::fromStdString( m_unit ) );
for ( size_t vIdx = 0; vIdx < wellLogValues.size(); vIdx++ )
{
double value = wellLogValues[vIdx];
if ( value == HUGE_VAL || value == -HUGE_VAL || value != value )
{
wellLogValues[vIdx] = absentValue;
}
}
lasFile->AddLog( m_channelName, m_unit, m_comment, wellLogValues );
}
}
std::string channelName() const { return m_channelName; }
const RigWellLogCurveData& curveData() const { return m_curveData; }
private:
std::string m_channelName;
std::string m_unit;
std::string m_comment;
RigWellLogCurveData m_curveData;
};
class SingleLasFileMetaData
{
public:
SingleLasFileMetaData()
: m_rkbDiff( HUGE_VAL )
, m_exportTvdrkb( false )
, m_minimumCurveValue( HUGE_VAL )
{
}
void setWellName( const QString& wellName ) { m_wellName = wellName; }
QString wellName() { return m_wellName; }
void setCaseName( const QString& caseName ) { m_caseName = caseName; }
void setDate( const QString& date ) { m_date = date; }
void setRkbDiff( double rkbDiff ) { m_rkbDiff = rkbDiff; }
void enableTvdrkbExport() { m_exportTvdrkb = true; }
double rkbDiff() { return m_rkbDiff; }
void addLogData( const std::string& channelName, const std::string& unit, const std::string& comment, const RigWellLogCurveData& curveData )
{
m_logCurveData.push_back( SingleChannelData( channelName, unit, comment, curveData ) );
for ( double xValue : curveData.propertyValues() )
{
if ( xValue < m_minimumCurveValue )
{
m_minimumCurveValue = xValue;
}
}
}
std::string generateFilename() const
{
QString fileBasenameCandidate;
QString separator( "-" );
if ( !m_wellName.isEmpty() )
{
fileBasenameCandidate += m_wellName;
}
if ( !m_caseName.isEmpty() )
{
if ( !fileBasenameCandidate.isEmpty() ) fileBasenameCandidate += separator;
fileBasenameCandidate += m_caseName;
}
// Add property name if only one curve is exported
if ( m_logCurveData.size() == 1 )
{
if ( !fileBasenameCandidate.isEmpty() ) fileBasenameCandidate += separator;
fileBasenameCandidate += QString::fromStdString( m_logCurveData[0].channelName() );
}
if ( !m_date.isEmpty() )
{
if ( !fileBasenameCandidate.isEmpty() ) fileBasenameCandidate += separator;
fileBasenameCandidate += m_date;
}
QString cleanFileName = caf::Utils::makeValidFileBasename( fileBasenameCandidate );
cleanFileName += ".las";
return cleanFileName.toStdString();
}
void appendDataToLasFile( NRLib::LasWell* lasFile ) const
{
if ( m_logCurveData.empty() ) return;
lasFile->addWellInfo( "WELL", m_wellName.toStdString() );
QString wellLogDate = m_date;
wellLogDate.replace( ".", "_" );
wellLogDate.replace( " ", "_" );
lasFile->addWellInfo( "DATE", wellLogDate.toStdString() );
const RigWellLogCurveData& firstCurveData = curveDataForFirstCurve();
const std::string depthUnitStr = depthUnitString( firstCurveData.depthUnit() );
std::string depthComment = "Depth in Connection number";
if ( firstCurveData.depthUnit() == RiaDefines::DepthUnitType::UNIT_METER )
depthComment = "Depth in meters";
else if ( firstCurveData.depthUnit() == RiaDefines::DepthUnitType::UNIT_FEET )
depthComment = "Depth in feet";
lasFile->AddLog( "DEPTH", depthUnitStr, depthComment, firstCurveData.depths( RiaDefines::DepthType::MEASURED_DEPTH ) );
if ( !firstCurveData.depths( RiaDefines::DepthType::TRUE_VERTICAL_DEPTH ).empty() )
{
std::string tvdmslComment = "True vertical depth";
if ( firstCurveData.depthUnit() == RiaDefines::DepthUnitType::UNIT_METER )
tvdmslComment = "True vertical depth in meters";
else if ( firstCurveData.depthUnit() == RiaDefines::DepthUnitType::UNIT_FEET )
tvdmslComment = "True vertical depth in feet";
lasFile->AddLog( "TVDMSL", depthUnitStr, tvdmslComment, firstCurveData.depths( RiaDefines::DepthType::TRUE_VERTICAL_DEPTH ) );
if ( m_exportTvdrkb && m_rkbDiff != -1.0 )
{
// Export True Vertical Depth Rotary Kelly Bushing - TVDRKB
std::vector<double> tvdrkbValues = firstCurveData.depths( RiaDefines::DepthType::TRUE_VERTICAL_DEPTH );
for ( auto& value : tvdrkbValues )
{
value += m_rkbDiff;
}
CAF_ASSERT( firstCurveData.depthUnit() != RiaDefines::DepthUnitType::UNIT_NONE );
lasFile->AddLog( "TVDRKB", depthUnitStr, "True vertical depth (Rotary Kelly Bushing)", tvdrkbValues );
}
}
double minDepth = 0.0;
double maxDepth = 0.0;
firstCurveData.calculateDepthRange( RiaDefines::DepthType::MEASURED_DEPTH, firstCurveData.depthUnit(), &minDepth, &maxDepth );
lasFile->setStartDepth( minDepth );
lasFile->setStopDepth( maxDepth );
if ( firstCurveData.depthUnit() != RiaDefines::DepthUnitType::UNIT_NONE )
{
lasFile->setDepthUnit( depthUnitStr );
}
double absentValue = SingleLasFileMetaData::createAbsentValue( m_minimumCurveValue );
lasFile->SetMissing( absentValue );
for ( auto curveData : m_logCurveData )
{
curveData.appendDataToLasFile( lasFile, absentValue );
}
}
private:
const RigWellLogCurveData& curveDataForFirstCurve() const
{
CAF_ASSERT( !m_logCurveData.empty() );
return m_logCurveData[0].curveData();
}
static std::string depthUnitString( RiaDefines::DepthUnitType depthUnit )
{
if ( depthUnit == RiaDefines::DepthUnitType::UNIT_METER ) return "M";
if ( depthUnit == RiaDefines::DepthUnitType::UNIT_FEET ) return "FT";
return "";
}
static double createAbsentValue( double lowestDataValue )
{
double absentValue = -999.0;
while ( absentValue > lowestDataValue )
{
absentValue *= 10;
absentValue -= 9;
}
return absentValue - 0.25;
}
private:
QString m_wellName;
QString m_caseName;
QString m_date;
double m_rkbDiff;
bool m_exportTvdrkb;
std::vector<double> m_depthValues;
std::vector<SingleChannelData> m_logCurveData;
double m_minimumCurveValue;
};
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RigLasFileExporter::RigLasFileExporter( const std::vector<RimWellLogCurve*>& curves )
: m_curves( curves )
, m_isResampleActive( false )
, m_resamplingInterval( 1.0 )
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigLasFileExporter::setResamplingInterval( double interval )
{
m_isResampleActive = true;
m_resamplingInterval = interval;
m_resampledCurveDatas.clear();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigLasFileExporter::wellPathsAndRkbDiff( std::vector<QString>* wellNames, std::vector<double>* rkbDiffs )
{
std::vector<SingleLasFileMetaData> lasFileDescriptions = createLasFileDescriptions( m_curves, false );
std::set<QString> uniqueWellNames;
for ( auto metaData : lasFileDescriptions )
{
if ( metaData.rkbDiff() != std::numeric_limits<double>::infinity() )
{
QString wellName = metaData.wellName();
if ( uniqueWellNames.find( wellName ) == uniqueWellNames.end() )
{
uniqueWellNames.insert( wellName );
wellNames->push_back( wellName );
rkbDiffs->push_back( metaData.rkbDiff() );
}
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigLasFileExporter::setRkbDiffs( const std::vector<QString>& wellNames, const std::vector<double>& rkbDiffs )
{
assert( wellNames.size() == rkbDiffs.size() );
std::vector<SingleLasFileMetaData> lasFileDescriptions = createLasFileDescriptions( m_curves, false );
for ( size_t i = 0; i < wellNames.size(); i++ )
{
for ( auto& metaData : lasFileDescriptions )
{
if ( metaData.wellName() == wellNames[i] )
{
m_userDefinedRkbOffsets.push_back( rkbDiffs[i] );
}
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<QString> RigLasFileExporter::writeToFolder( const QString& exportFolder,
const QString& filePrefix /*= ""*/,
bool capitalizeFileName /*= false*/,
bool alwaysOverwrite /*= false*/,
bool convertCurveUnits /*= false*/ )
{
std::vector<QString> writtenFiles;
std::vector<SingleLasFileMetaData> lasFileDescriptions = createLasFileDescriptions( m_curves, convertCurveUnits );
applyUserDefinedRkbOffsets( &lasFileDescriptions );
for ( auto lasFileDescr : lasFileDescriptions )
{
NRLib::LasWell lasFile;
lasFile.setVersionInfo( "2.0" );
lasFileDescr.appendDataToLasFile( &lasFile );
if ( m_isResampleActive )
{
lasFile.setDepthStep( m_resamplingInterval );
}
QDir dir( exportFolder );
QString fileName = filePrefix + QString::fromStdString( lasFileDescr.generateFilename() );
if ( capitalizeFileName )
{
fileName = fileName.toUpper();
}
QString fullPathName = dir.absoluteFilePath( fileName );
if ( caf::Utils::fileExists( fullPathName ) && !alwaysOverwrite )
{
QString txt = QString( "File %1 exists.\n\nDo you want to overwrite the file?" ).arg( fullPathName );
int ret = QMessageBox::question( RiaGuiApplication::widgetToUseAsParent(),
"LAS File Export",
txt,
QMessageBox::Yes | QMessageBox::No,
QMessageBox::Yes );
if ( ret != QMessageBox::Yes ) continue;
}
std::vector<std::string> commentHeader;
lasFile.WriteToFile( fullPathName.toStdString(), commentHeader );
if ( QFileInfo::exists( fullPathName ) )
{
writtenFiles.push_back( fullPathName );
}
}
return writtenFiles;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<SingleLasFileMetaData> RigLasFileExporter::createLasFileDescriptions( const std::vector<RimWellLogCurve*>& curves,
bool convertCurveUnits )
{
std::vector<RimWellLogCurve*> eclipseCurves;
std::vector<RimWellLogCurve*> geoMechCurves;
std::vector<RimWellLogCurve*> externalLasCurves;
for ( auto curve : curves )
{
RimWellLogExtractionCurve* extractionCurve = dynamic_cast<RimWellLogExtractionCurve*>( curve );
if ( extractionCurve )
{
if ( extractionCurve->isEclipseCurve() )
{
eclipseCurves.push_back( extractionCurve );
}
else
{
geoMechCurves.push_back( extractionCurve );
}
}
else
{
externalLasCurves.push_back( curve );
}
}
// External LAS files
std::vector<SingleLasFileMetaData> lasFileDescriptions;
appendLasFileDescriptions( externalLasCurves, &lasFileDescriptions, convertCurveUnits );
appendLasFileDescriptions( eclipseCurves, &lasFileDescriptions, convertCurveUnits );
appendLasFileDescriptions( geoMechCurves, &lasFileDescriptions, convertCurveUnits );
return lasFileDescriptions;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigLasFileExporter::appendLasFileDescriptions( const std::vector<RimWellLogCurve*>& curves,
std::vector<SingleLasFileMetaData>* lasFileDescriptions,
bool convertCurveUnits )
{
CAF_ASSERT( lasFileDescriptions );
struct CurveCollectionDefinition
{
CurveCollectionDefinition( const QString& wellName, const QString& caseName, const QString& date )
: m_wellName( wellName )
, m_caseName( caseName )
, m_date( date )
{
}
bool isEqual( RimWellLogCurve* curve, const QString& caseName )
{
return m_wellName == curve->wellName() && m_caseName == caseName && m_date == curve->wellDate();
}
QString m_wellName;
QString m_caseName;
QString m_date;
};
std::vector<CurveCollectionDefinition> curveDefinitions;
for ( auto curve : curves )
{
QString caseName = caseNameFromCurve( curve );
bool found = false;
for ( auto curveDef : curveDefinitions )
{
if ( curveDef.isEqual( curve, caseName ) )
{
found = true;
}
}
if ( !found )
{
CurveCollectionDefinition curveDefCandidate( curve->wellName(), caseName, curve->wellDate() );
curveDefinitions.push_back( curveDefCandidate );
}
}
for ( auto curveDef : curveDefinitions )
{
SingleLasFileMetaData singleLasFileMeta;
singleLasFileMeta.setWellName( curveDef.m_wellName );
singleLasFileMeta.setCaseName( curveDef.m_caseName );
singleLasFileMeta.setDate( curveDef.m_date );
for ( auto curve : curves )
{
if ( curveDef.isEqual( curve, caseNameFromCurve( curve ) ) )
{
singleLasFileMeta.setRkbDiff( rkbDiff( curve ) );
const auto& curveData =
m_isResampleActive
? m_resampledCurveDatas.emplace_back( curve->curveData().calculateResampledCurveData( m_resamplingInterval ) )
: curve->curveData();
QString units = curveData.propertyValueUnit();
if ( convertCurveUnits || units == RiaWellLogUnitTools<double>::barX100UnitString() )
{
units = curve->wellLogChannelUnits();
}
singleLasFileMeta.addLogData( curve->wellLogChannelName().toStdString(), units.toStdString(), "", curveData );
}
}
lasFileDescriptions->push_back( singleLasFileMeta );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RigLasFileExporter::caseNameFromCurve( RimWellLogCurve* curve )
{
QString caseName;
RimWellLogExtractionCurve* extractionCurve = dynamic_cast<RimWellLogExtractionCurve*>( curve );
if ( extractionCurve )
{
caseName = extractionCurve->caseName();
}
else
{
caseName = "unnamed";
}
return caseName;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RigLasFileExporter::rkbDiff( RimWellLogCurve* curve )
{
RimWellLogExtractionCurve* extractionCurve = dynamic_cast<RimWellLogExtractionCurve*>( curve );
if ( extractionCurve && extractionCurve->wellPath() && extractionCurve->wellPath()->wellPathGeometry() )
{
return extractionCurve->wellPath()->wellPathGeometry()->rkbDiff();
}
return HUGE_VAL;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigLasFileExporter::applyUserDefinedRkbOffsets( std::vector<SingleLasFileMetaData>* lasFileDescriptions )
{
if ( m_userDefinedRkbOffsets.size() == lasFileDescriptions->size() )
{
for ( size_t i = 0; i < m_userDefinedRkbOffsets.size(); i++ )
{
if ( m_userDefinedRkbOffsets[i] != HUGE_VAL )
{
lasFileDescriptions->at( i ).setRkbDiff( m_userDefinedRkbOffsets[i] );
lasFileDescriptions->at( i ).enableTvdrkbExport();
}
}
}
}