Create depth adjusted LAS files

* Add support for creating depth adjustment LAS files
* Add RiaDefine for LAS file depth property names
* Remove incorrect check in K-index Calculator

Co-authored-by: jorgenherje <jorgenherje@users.noreply.github.com>
Co-authored-by: Magne Sjaastad <magne.sjaastad@ceetronsolutions.com>
This commit is contained in:
Jørgen Herje
2023-01-23 12:46:54 +01:00
committed by GitHub
parent ab165b0d72
commit 50b1820b60
15 changed files with 1158 additions and 22 deletions

View File

@@ -5,3 +5,4 @@
^ApplicationExeCode/Resources/SouthView.svg
^ApplicationExeCode/Resources/WestView.svg
^ApplicationLibCode/ProjectDataModel/RimEclipseStatisticsCaseEvaluator.cpp
^ApplicationLibCode/Commands/ExportCommands/RicCreateDepthAdjustedLasFilesImpl.cpp

View File

@@ -31,6 +31,7 @@ set(SOURCE_GROUP_HEADER_FILES
${CMAKE_CURRENT_LIST_DIR}/RiaPlotCollectionScheduler.h
${CMAKE_CURRENT_LIST_DIR}/RiaScheduler.h
${CMAKE_CURRENT_LIST_DIR}/RiaSummaryDefines.h
${CMAKE_CURRENT_LIST_DIR}/RiaLasDefines.h
)
set(SOURCE_GROUP_SOURCE_FILES
@@ -66,6 +67,7 @@ set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RiaPlotCollectionScheduler.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaScheduler.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaSummaryDefines.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaLasDefines.cpp
)
list(APPEND CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES})

View File

@@ -0,0 +1,43 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2023- Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RiaLasDefines.h"
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RiaDefines::propertyNameMeasuredDepth()
{
return "DEPTH";
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RiaDefines::propertyNameTvdMslDepth()
{
return "TVDMSL";
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RiaDefines::propertyNameTvdRkbDepth()
{
return "TVDRKB";
}

View File

@@ -0,0 +1,28 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2023- Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include <QString>
namespace RiaDefines
{
QString propertyNameMeasuredDepth();
QString propertyNameTvdMslDepth();
QString propertyNameTvdRkbDepth();
}; // namespace RiaDefines

View File

@@ -26,6 +26,9 @@ set(SOURCE_GROUP_HEADER_FILES
${CMAKE_CURRENT_LIST_DIR}/RicExportLgrUi.h
${CMAKE_CURRENT_LIST_DIR}/RicEclipseCellResultToFileImpl.h
${CMAKE_CURRENT_LIST_DIR}/RicLgrSplitType.h
${CMAKE_CURRENT_LIST_DIR}/RicCreateDepthAdjustedLasFilesFeature.h
${CMAKE_CURRENT_LIST_DIR}/RicCreateDepthAdjustedLasFilesUi.h
${CMAKE_CURRENT_LIST_DIR}/RicCreateDepthAdjustedLasFilesImpl.h
)
set(SOURCE_GROUP_SOURCE_FILES
@@ -55,6 +58,9 @@ set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RicExportLgrFeature.cpp
${CMAKE_CURRENT_LIST_DIR}/RicExportLgrUi.cpp
${CMAKE_CURRENT_LIST_DIR}/RicEclipseCellResultToFileImpl.cpp
${CMAKE_CURRENT_LIST_DIR}/RicCreateDepthAdjustedLasFilesFeature.cpp
${CMAKE_CURRENT_LIST_DIR}/RicCreateDepthAdjustedLasFilesUi.cpp
${CMAKE_CURRENT_LIST_DIR}/RicCreateDepthAdjustedLasFilesImpl.cpp
)
list(APPEND COMMAND_CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES})

View File

@@ -0,0 +1,176 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2023- Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RicCreateDepthAdjustedLasFilesFeature.h"
#include "RiaLogging.h"
#include "RicCreateDepthAdjustedLasFilesImpl.h"
#include "RicCreateDepthAdjustedLasFilesUi.h"
#include "RicExportFeatureImpl.h"
#include "RigEclipseWellLogExtractor.h"
#include "RigGeoMechWellLogExtractor.h"
#include "RimEclipseCase.h"
#include "RimGeoMechCase.h"
#include "RimMainPlotCollection.h"
#include "RimOilField.h"
#include "RimProject.h"
#include "RimWellLogFile.h"
#include "RimWellLogPlotCollection.h"
#include "RimWellPath.h"
#include "RimWellPathCollection.h"
#include "cafPdmUiPropertyViewDialog.h"
#include <QAction>
CAF_CMD_SOURCE_INIT( RicCreateDepthAdjustedLasFilesFeature, "RicCreateDepthAdjustedLasFilesFeature" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RicCreateDepthAdjustedLasFilesFeature::isCommandEnabled()
{
return true;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicCreateDepthAdjustedLasFilesFeature::onActionTriggered( bool isChecked )
{
RicCreateDepthAdjustedLasFilesUi featureUi;
featureUi.setDefaultValues();
caf::PdmUiPropertyViewDialog propertyDialog( nullptr, &featureUi, "Create Depth Adjusted LAS file(s)", "" );
RicExportFeatureImpl::configureForExport( propertyDialog.dialogButtonBox() );
propertyDialog.setWindowFlag( Qt::WindowCloseButtonHint, true );
propertyDialog.resize( QSize( 850, 500 ) );
if ( propertyDialog.exec() == QDialog::Accepted )
{
if ( !featureUi.hasValidSelections() )
{
RiaLogging::warning( featureUi.invalidSelectionsLogString() );
}
else
{
RimCase* selectedCase = featureUi.selectedCase();
RimWellPath* sourceWell = featureUi.sourceWell();
RimWellLogFile* sourceWellLogFile = sourceWell->wellLogFiles()[0];
std::vector<RimWellPath*> destinationWells = featureUi.destinationWells().ptrReferencedObjects();
std::vector<QString> selectedResultProperties = featureUi.selectedResultProperties();
QString exportFolder = featureUi.exportFolder();
RimEclipseCase* eclipseCase = dynamic_cast<RimEclipseCase*>( selectedCase );
RimGeoMechCase* geomCase = dynamic_cast<RimGeoMechCase*>( selectedCase );
if ( eclipseCase == nullptr && geomCase == nullptr )
{
RiaLogging::warning( QString( "The selected case is invalid" ) );
return;
}
if ( eclipseCase != nullptr )
{
createDepthAdjustedWellLogFileFromEclipseCase( eclipseCase,
sourceWell,
destinationWells,
selectedResultProperties,
exportFolder );
}
else if ( geomCase != nullptr )
{
createDepthAdjustedWellLogFileFromGeoMechCase( geomCase,
sourceWell,
destinationWells,
selectedResultProperties,
exportFolder );
}
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicCreateDepthAdjustedLasFilesFeature::setupActionLook( QAction* actionToSetup )
{
actionToSetup->setText( "Create Depth Adjusted LAS Files..." );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicCreateDepthAdjustedLasFilesFeature::createDepthAdjustedWellLogFileFromEclipseCase(
RimEclipseCase* eclipseCase,
RimWellPath* sourceWell,
const std::vector<RimWellPath*> destinationWells,
const std::vector<QString>& selectedResultProperties,
const QString& exportFolder )
{
if ( sourceWell->wellLogFiles().empty() ) return;
RimWellLogPlotCollection* wellLogCollection = RimMainPlotCollection::current()->wellLogPlotCollection();
cvf::ref<RigEclipseWellLogExtractor> sourceWellExtractor =
wellLogCollection->findOrCreateExtractor( sourceWell, eclipseCase );
if ( sourceWellExtractor.isNull() )
{
RiaLogging::info( QString( "Could not create RigEclipseWellLogExtractor for %1" ).arg( sourceWell->name() ) );
return;
}
const double rkbDiff = sourceWellExtractor->wellPathGeometry()->rkbDiff();
RicCreateDepthAdjustedLasFilesImpl::createDestinationWellsLasFiles( eclipseCase,
sourceWell,
destinationWells,
selectedResultProperties,
exportFolder,
rkbDiff );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicCreateDepthAdjustedLasFilesFeature::createDepthAdjustedWellLogFileFromGeoMechCase(
RimGeoMechCase* geoMechCase,
RimWellPath* sourceWell,
const std::vector<RimWellPath*> destinationWells,
const std::vector<QString>& selectedResultProperties,
const QString& exportFolder )
{
if ( sourceWell->wellLogFiles().empty() ) return;
auto* wellLogFileData = sourceWell->wellLogFiles()[0]->wellLogFileData();
RimWellLogPlotCollection* wellLogCollection = RimMainPlotCollection::current()->wellLogPlotCollection();
cvf::ref<RigGeoMechWellLogExtractor> sourceWellExtractor =
wellLogCollection->findOrCreateExtractor( sourceWell, geoMechCase );
if ( sourceWellExtractor.isNull() )
{
RiaLogging::info( QString( "Could not create RigGeoMechWellLogExtractor for %1" ).arg( sourceWell->name() ) );
return;
}
const double rkbDiff = sourceWellExtractor->wellPathGeometry()->rkbDiff();
RicCreateDepthAdjustedLasFilesImpl::createDestinationWellsLasFiles( geoMechCase,
sourceWell,
destinationWells,
selectedResultProperties,
exportFolder,
rkbDiff );
}

View File

@@ -0,0 +1,54 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2023- Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "cafCmdFeature.h"
class RimEclipseCase;
class RimGeoMechCase;
class RimWellPath;
//==================================================================================================
///
//==================================================================================================
class RicCreateDepthAdjustedLasFilesFeature : public caf::CmdFeature
{
CAF_CMD_HEADER_INIT;
public:
RicCreateDepthAdjustedLasFilesFeature() = default;
protected:
bool isCommandEnabled() override;
void onActionTriggered( bool isChecked ) override;
void setupActionLook( QAction* actionToSetup ) override;
private:
void createDepthAdjustedWellLogFileFromEclipseCase( RimEclipseCase* eclipseCase,
RimWellPath* sourceWell,
const std::vector<RimWellPath*> destinationWells,
const std::vector<QString>& selectedResultProperties,
const QString& exportFolder );
void createDepthAdjustedWellLogFileFromGeoMechCase( RimGeoMechCase* geoMechCase,
RimWellPath* sourceWell,
const std::vector<RimWellPath*> destinationWells,
const std::vector<QString>& selectedResultProperties,
const QString& exportFolder );
};

View File

@@ -0,0 +1,411 @@
#include "RicCreateDepthAdjustedLasFilesFeature.h"
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2023- Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RicCreateDepthAdjustedLasFilesImpl.h"
#include "RiaLogging.h"
#include "RicCreateDepthAdjustedLasFilesUi.h"
#include "RigCaseCellResultsData.h"
#include "RigEclipseCaseData.h"
#include "RigEclipseResultAddress.h"
#include "RigEclipseWellLogExtractor.h"
#include "RigGeoMechWellLogExtractor.h"
#include "RigResultAccessorFactory.h"
#include "RimEclipseCase.h"
#include "RimGeoMechCase.h"
#include "RimMainPlotCollection.h"
#include "RimWellLogFile.h"
#include "RimWellLogPlotCollection.h"
#include "RimWellPath.h"
#include "NRLib/nrlib/well/laswell.hpp"
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void LasDepthValueAndIndexPerKLayer::insertIndexAndValue( int kLayer, size_t index, double value )
{
m_kLayerIndexAndValuePairsMap[kLayer][index] = value;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool LasDepthValueAndIndexPerKLayer::hasKLayer( int kLayer ) const
{
return m_kLayerIndexAndValuePairsMap.find( kLayer ) != m_kLayerIndexAndValuePairsMap.end();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::map<size_t, double> LasDepthValueAndIndexPerKLayer::indexAndValuePairs( int kLayer ) const
{
if ( !hasKLayer( kLayer ) ) return std::map<size_t, double>();
return m_kLayerIndexAndValuePairsMap.at( kLayer );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
cvf::ref<RigResultAccessor> RicCreateDepthAdjustedLasFilesImpl::createIndexKResultAccessor( RimEclipseCase* eclipseCase )
{
const int firstTimeStep = 0;
const int gridIndex = 0;
RigEclipseResultAddress indexKResAdr( RiaDefines::ResultCatType::STATIC_NATIVE, RiaResultNames::indexKResultName() );
eclipseCase->eclipseCaseData()->results( RiaDefines::PorosityModelType::MATRIX_MODEL )->ensureKnownResultLoaded( indexKResAdr );
return RigResultAccessorFactory::createFromResultAddress( eclipseCase->eclipseCaseData(),
gridIndex,
RiaDefines::PorosityModelType::MATRIX_MODEL,
firstTimeStep,
indexKResAdr );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
LasDepthValueAndIndexPerKLayer RicCreateDepthAdjustedLasFilesImpl::createLasDepthIndexAndPercValuePerKLayerFromMap(
const std::vector<double>& lasWellDepths,
const std::map<int, IndexKDepthData>& indexKDepthDataMap )
{
// Create container of depth value (in percent) and its original index in a LAS file vector
// categorized by K-layer. Depth value as percentage value between MD top and MD bottom for K-layer.
auto lasWellDepthValueAndIndexPerKLayer = LasDepthValueAndIndexPerKLayer();
for ( size_t i = 0; i < lasWellDepths.size(); ++i )
{
const double depth = lasWellDepths[i];
for ( const auto& [indexK, depthData] : indexKDepthDataMap )
{
if ( depthData.mdTop <= depth && depth <= depthData.mdBottom )
{
const double percentage = ( depth - depthData.mdTop ) / ( depthData.mdBottom - depthData.mdTop );
lasWellDepthValueAndIndexPerKLayer.insertIndexAndValue( indexK, i, percentage );
break;
}
}
}
return lasWellDepthValueAndIndexPerKLayer;
}
//--------------------------------------------------------------------------------------------------
/// NOTE: map createIndexKDepthDataMapFromCase is created using well extractor, while sourceWellLogData depth
/// values are from LAS file. Floating point rounding in LAS file can occur, thus depth values might be placed
/// outside of K-layer close to top/bottom due to inaccuracy.
//--------------------------------------------------------------------------------------------------
void RicCreateDepthAdjustedLasFilesImpl::createDestinationWellsLasFiles( RimCase* selectedCase,
RimWellPath* sourceWell,
const std::vector<RimWellPath*> destinationWells,
const std::vector<QString>& selectedResultProperties,
const QString& exportFolder,
double rkbDiff )
{
auto* sourceWellLogData = sourceWell->wellLogFiles()[0]->wellLogFileData();
const auto defaultPropertyMap = createDefaultPropertyMap( selectedResultProperties, sourceWellLogData );
// NOTE: map createIndexKDepthDataMapFromCase is created using well extractor, while sourceWellLogData depth
// values are from LAS file. Floating point rounding in LAS file can occur, thus depth values might be placed
// outside of K-layer close to top/bottom due to inaccuracy.
const auto sourceWellDepthIndexAndPercValuePerKLayer =
createLasDepthIndexAndPercValuePerKLayerFromMap( sourceWellLogData->depthValues(),
createIndexKDepthDataMapFromCase( selectedCase, sourceWell ) );
for ( RimWellPath* well : destinationWells )
{
const std::map<int, IndexKDepthData> destinationWellIndexKDepthsMap =
createIndexKDepthDataMapFromCase( selectedCase, well );
if ( destinationWellIndexKDepthsMap.empty() ) continue;
std::vector<double> mdValues;
std::vector<double> tvdMslValues;
std::vector<double> tvdRkbValues;
std::map<QString, std::vector<double>> propertyMap = defaultPropertyMap;
for ( const auto& [indexK, depthData] : destinationWellIndexKDepthsMap )
{
if ( !sourceWellDepthIndexAndPercValuePerKLayer.hasKLayer( indexK ) ) continue;
for ( const auto& [index, depthPerc] : sourceWellDepthIndexAndPercValuePerKLayer.indexAndValuePairs( indexK ) )
{
if ( sourceWellLogData->hasTvdMslChannel() )
{
const double tvdMslValue = depthPerc * ( depthData.tvdBottom - depthData.tvdTop ) + depthData.tvdTop;
tvdMslValues.push_back( tvdMslValue );
}
if ( sourceWellLogData->hasTvdRkbChannel() )
{
const double tvdRkbValue = depthPerc * ( depthData.tvdBottom - depthData.tvdTop ) +
depthData.tvdTop + rkbDiff;
tvdRkbValues.push_back( tvdRkbValue );
}
const double mdValue = depthPerc * ( depthData.mdBottom - depthData.mdTop ) + depthData.mdTop;
mdValues.push_back( mdValue );
for ( auto& [propertyName, values] : propertyMap )
{
double value = sourceWellLogData->values( propertyName )[index];
value = value == HUGE_VAL ? sourceWellLogData->getMissingValue() : value;
values.push_back( value );
}
}
}
createDestinationWellLasFile( well->name(),
selectedCase->caseUserDescription(),
mdValues,
tvdMslValues,
tvdRkbValues,
propertyMap,
sourceWellLogData,
exportFolder );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicCreateDepthAdjustedLasFilesImpl::createDestinationWellLasFile( const QString& wellName,
const QString& caseDescription,
const std::vector<double>& mdValues,
const std::vector<double>& tvdMslValues,
const std::vector<double>& tvdRkbValues,
const std::map<QString, std::vector<double>>& propertyMap,
const RigWellLogFile* sourceWellLogData,
const QString& exportFolder )
{
const auto depthUnitText = createDepthUnitText( sourceWellLogData->depthUnit() );
const auto depthUnitComment = createDepthUnitComment( sourceWellLogData->depthUnit() );
const auto deptUnit = sourceWellLogData->depthUnit();
// Build LAS file
NRLib::LasWell lasFile;
lasFile.setVersionInfo( "2.0" );
lasFile.setDepthUnit( depthUnitText );
lasFile.SetMissing( sourceWellLogData->getMissingValue() );
lasFile.setStartDepth( *std::min_element( mdValues.begin(), mdValues.end() ) );
lasFile.setStopDepth( *std::max_element( mdValues.begin(), mdValues.end() ) );
lasFile.addWellInfo( "WELL", wellName.toStdString() );
lasFile.addWellInfo( "DATE", sourceWellLogData->date().toStdString() );
// Add Measured depth
lasFile.AddLog( RiaDefines::propertyNameMeasuredDepth().toStdString(), depthUnitText, "Depth " + depthUnitComment, mdValues );
// Add tvd msl values if existing
if ( !tvdMslValues.empty() )
{
const auto unitText =
sourceWellLogData->wellLogChannelUnitString( RiaDefines::propertyNameTvdMslDepth(), deptUnit ).toStdString();
lasFile.AddLog( RiaDefines::propertyNameTvdMslDepth().toStdString(),
unitText,
"True vertical depth " + depthUnitComment,
tvdMslValues );
}
// Add tvd rkb values if existing
if ( !tvdRkbValues.empty() )
{
const auto unitText =
sourceWellLogData->wellLogChannelUnitString( RiaDefines::propertyNameTvdRkbDepth(), deptUnit ).toStdString();
lasFile.AddLog( RiaDefines::propertyNameTvdRkbDepth().toStdString(),
unitText,
"True vertical depth (Rotary Kelly Bushing)",
tvdRkbValues );
}
// Add property values
for ( auto& [name, values] : propertyMap )
{
std::string unitText = sourceWellLogData->wellLogChannelUnitString( name ).toStdString();
lasFile.AddLog( name.toUpper().toStdString(), unitText, "", values );
}
std::vector<std::string> commentHeader;
QString fullPathName = exportFolder + "/" + wellName + "_" + caseDescription + "_" + sourceWellLogData->date() + ".las";
lasFile.WriteToFile( fullPathName.toStdString(), commentHeader );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::string RicCreateDepthAdjustedLasFilesImpl::createDepthUnitText( RiaDefines::DepthUnitType depthUnitType )
{
return depthUnitType == RiaDefines::DepthUnitType::UNIT_METER
? "M"
: depthUnitType == RiaDefines::DepthUnitType::UNIT_FEET ? "FT" : "";
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::string RicCreateDepthAdjustedLasFilesImpl::createDepthUnitComment( RiaDefines::DepthUnitType depthUnitType )
{
return depthUnitType == RiaDefines::DepthUnitType::UNIT_METER
? "in meters"
: depthUnitType == RiaDefines::DepthUnitType::UNIT_FEET ? "in feet" : "in Connection number";
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::map<int, RicCreateDepthAdjustedLasFilesImpl::IndexKDepthData>
RicCreateDepthAdjustedLasFilesImpl::createIndexKDepthDataMapFromCase( RimCase* selectedCase, RimWellPath* wellPath )
{
RimEclipseCase* eclipseCase = dynamic_cast<RimEclipseCase*>( selectedCase );
RimGeoMechCase* geomCase = dynamic_cast<RimGeoMechCase*>( selectedCase );
RimWellLogPlotCollection* wellLogCollection = RimMainPlotCollection::current()->wellLogPlotCollection();
if ( eclipseCase != nullptr )
{
cvf::ref<RigEclipseWellLogExtractor> wellExtractor =
wellLogCollection->findOrCreateExtractor( wellPath, eclipseCase );
if ( wellExtractor.isNull() )
{
RiaLogging::info( QString( "Could not create RigEclipseWellLogExtractor for %1" ).arg( wellPath->name() ) );
}
const auto result = createIndexKDepthDataMap( wellExtractor, createIndexKResultAccessor( eclipseCase ) );
if ( result.empty() )
{
RiaLogging::info( QString( "Not able to create Index-K depth map for %1" ).arg( wellPath->name() ) );
}
return result;
}
else if ( geomCase != nullptr )
{
cvf::ref<RigGeoMechWellLogExtractor> wellExtractor = wellLogCollection->findOrCreateExtractor( wellPath, geomCase );
if ( wellExtractor.isNull() )
{
RiaLogging::info( QString( "Could not create RigGeoMechWellLogExtractor for %1" ).arg( wellPath->name() ) );
}
const auto result = createIndexKDepthDataMap( wellExtractor );
if ( result.empty() )
{
RiaLogging::info( QString( "Not able to create Index-K depth map for %1" ).arg( wellPath->name() ) );
}
return result;
}
RiaLogging::info( QString( "Invalid case when creating Index-K depth map for %1" ).arg( wellPath->name() ) );
return std::map<int, RicCreateDepthAdjustedLasFilesImpl::IndexKDepthData>();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::map<int, RicCreateDepthAdjustedLasFilesImpl::IndexKDepthData>
RicCreateDepthAdjustedLasFilesImpl::createIndexKDepthDataMap( cvf::ref<RigEclipseWellLogExtractor> wellExtractor,
cvf::ref<RigResultAccessor> indexKResAcc )
{
std::vector<double> wellIndexKValues;
wellExtractor->curveData( indexKResAcc.p(), &wellIndexKValues );
return createIndexKDepthDataMapFromVectors( wellExtractor->cellIntersectionMDs(),
wellExtractor->cellIntersectionTVDs(),
wellIndexKValues );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::map<int, RicCreateDepthAdjustedLasFilesImpl::IndexKDepthData>
RicCreateDepthAdjustedLasFilesImpl::createIndexKDepthDataMap( cvf::ref<RigGeoMechWellLogExtractor> wellExtractor )
{
const int frameIdx = 0;
const int timeStepIdx = 0;
RigFemResultAddress indexKResAdr( RigFemResultPosEnum::RIG_ELEMENT_NODAL, "INDEX", "INDEX_K" );
std::vector<double> wellIndexKValues;
wellExtractor->curveData( indexKResAdr, timeStepIdx, frameIdx, &wellIndexKValues );
return createIndexKDepthDataMapFromVectors( wellExtractor->cellIntersectionMDs(),
wellExtractor->cellIntersectionTVDs(),
wellIndexKValues );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::map<int, RicCreateDepthAdjustedLasFilesImpl::IndexKDepthData>
RicCreateDepthAdjustedLasFilesImpl::createIndexKDepthDataMapFromVectors( const std::vector<double>& wellMdValues,
const std::vector<double>& wellTvdValues,
const std::vector<double>& wellIndexKValues )
{
std::map<int, IndexKDepthData> indexKDepthsMap;
// Must have non-empty equal length vectors!
if ( wellIndexKValues.empty() )
{
RiaLogging::info( QString( "Empty vector of index-K values" ) );
return indexKDepthsMap;
}
if ( wellMdValues.size() != wellTvdValues.size() || wellMdValues.size() != wellIndexKValues.size() )
{
return indexKDepthsMap;
}
int prevKLayer = -1;
for ( size_t i = 0; i < wellIndexKValues.size(); ++i )
{
// Asymptotically increasing k-indexes!
const auto kLayer = static_cast<int>( wellIndexKValues[i] );
if ( kLayer < prevKLayer ) break;
if ( indexKDepthsMap.find( kLayer ) == indexKDepthsMap.end() )
{
indexKDepthsMap[kLayer] = IndexKDepthData();
indexKDepthsMap[kLayer].mdTop = wellMdValues[i];
indexKDepthsMap[kLayer].mdBottom = wellMdValues[i];
indexKDepthsMap[kLayer].tvdTop = wellTvdValues[i];
indexKDepthsMap[kLayer].tvdBottom = wellTvdValues[i];
}
else
{
indexKDepthsMap[kLayer].mdBottom = wellMdValues[i];
indexKDepthsMap[kLayer].tvdBottom = wellTvdValues[i];
}
}
return indexKDepthsMap;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::map<QString, std::vector<double>>
RicCreateDepthAdjustedLasFilesImpl::createDefaultPropertyMap( const std::vector<QString>& selectedProperties,
const RigWellLogFile* wellLogFile )
{
const QStringList lasDepthNames = QStringList( { RiaDefines::propertyNameMeasuredDepth(),
RiaDefines::propertyNameTvdMslDepth(),
RiaDefines::propertyNameTvdRkbDepth() } );
std::vector<QString> validPropertyNames;
for ( const auto& propertyName : selectedProperties )
{
if ( !lasDepthNames.contains( propertyName ) && wellLogFile->wellLogChannelNames().contains( propertyName ) )
{
validPropertyNames.push_back( propertyName );
}
}
std::map<QString, std::vector<double>> defaultPropertyMap;
for ( const auto& name : validPropertyNames )
{
defaultPropertyMap[name];
}
return defaultPropertyMap;
}

View File

@@ -0,0 +1,99 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2023- Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RiaDefines.h"
#include "cvfObject.h"
class RigEclipseWellLogExtractor;
class RigGeoMechWellLogExtractor;
class RigResultAccessor;
class RigWellLogFile;
class RimCase;
class RimEclipseCase;
class RimWellPath;
//==================================================================================================
/// Object to hold Depth value and its original index in a LAS file vector categorized by K-layer.
//==================================================================================================
class LasDepthValueAndIndexPerKLayer
{
public:
LasDepthValueAndIndexPerKLayer() = default;
void insertIndexAndValue( int kLayer, size_t index, double value );
std::map<size_t, double> indexAndValuePairs( int kLayer ) const;
bool hasKLayer( int kLayer ) const;
private:
// Map of K-layer and Index and Value pairs for LAS file depth vectors
std::map<int, std::map<size_t, double>> m_kLayerIndexAndValuePairsMap;
};
//==================================================================================================
///
//==================================================================================================
namespace RicCreateDepthAdjustedLasFilesImpl
{
struct IndexKDepthData
{
double mdTop = 0.0;
double mdBottom = 0.0;
double tvdTop = 0.0;
double tvdBottom = 0.0;
};
cvf::ref<RigResultAccessor> createIndexKResultAccessor( RimEclipseCase* selectedCase );
void createDestinationWellsLasFiles( RimCase* selectedCase,
RimWellPath* sourceWell,
const std::vector<RimWellPath*> destinationWells,
const std::vector<QString>& selectedResultProperties,
const QString& exportFolder,
double rkbDiff );
void createDestinationWellLasFile( const QString& wellName,
const QString& caseDescription,
const std::vector<double>& mdValues,
const std::vector<double>& tvdMslValues,
const std::vector<double>& tvdRkbValues,
const std::map<QString, std::vector<double>>& propertyMap,
const RigWellLogFile* sourceWellLogData,
const QString& exportFolder );
std::string createDepthUnitText( RiaDefines::DepthUnitType depthUnitType );
std::string createDepthUnitComment( RiaDefines::DepthUnitType depthUnitType );
LasDepthValueAndIndexPerKLayer
createLasDepthIndexAndPercValuePerKLayerFromMap( const std::vector<double>& lasWellDepths,
const std::map<int, IndexKDepthData>& indexKDepthDataMap );
std::map<int, IndexKDepthData> createIndexKDepthDataMapFromCase( RimCase* selectedCase, RimWellPath* wellPath );
std::map<int, IndexKDepthData> createIndexKDepthDataMap( cvf::ref<RigEclipseWellLogExtractor> wellExtractor,
cvf::ref<RigResultAccessor> indexKResAcc );
std::map<int, IndexKDepthData> createIndexKDepthDataMap( cvf::ref<RigGeoMechWellLogExtractor> wellExtractor );
std::map<int, IndexKDepthData> createIndexKDepthDataMapFromVectors( const std::vector<double>& wellMdValues,
const std::vector<double>& wellTvdValues,
const std::vector<double>& wellIndexKValues );
std::map<QString, std::vector<double>> createDefaultPropertyMap( const std::vector<QString>& selectedProperties,
const RigWellLogFile* wellLogFile );
}; // namespace RicCreateDepthAdjustedLasFilesImpl

View File

@@ -0,0 +1,239 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2023- Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RicCreateDepthAdjustedLasFilesUi.h"
#include "RiaApplication.h"
#include "RimCase.h"
#include "RimOilField.h"
#include "RimProject.h"
#include "RimTools.h"
#include "RimWellLogFile.h"
#include "RimWellLogFileChannel.h"
#include "RimWellPath.h"
#include "RimWellPathCollection.h"
#include "cafPdmUiCheckBoxEditor.h"
#include "cafPdmUiFilePathEditor.h"
#include "cafPdmUiOrdering.h"
#include "cafPdmUiTreeSelectionEditor.h"
CAF_PDM_SOURCE_INIT( RicCreateDepthAdjustedLasFilesUi, "RicCreateDepthAdjustedLasFilesUi" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RicCreateDepthAdjustedLasFilesUi::RicCreateDepthAdjustedLasFilesUi()
{
CAF_PDM_InitField( &exportFolder, "ExportFolder", QString(), "Export Folder" );
exportFolder.uiCapability()->setUiEditorTypeName( caf::PdmUiFilePathEditor::uiEditorTypeName() );
CAF_PDM_InitFieldNoDefault( &selectedCase, "SelectedCase", "Select Case" );
CAF_PDM_InitFieldNoDefault( &sourceWell, "SourceWell", "Source Well" );
CAF_PDM_InitFieldNoDefault( &selectedResultProperties, "SelectedResultProperties", "Selected Result Properties" );
selectedResultProperties.uiCapability()->setUiEditorTypeName( caf::PdmUiTreeSelectionEditor::uiEditorTypeName() );
CAF_PDM_InitFieldNoDefault( &destinationWells, "DestinationWells", "Destination Wells" );
destinationWells.uiCapability()->setUiEditorTypeName( caf::PdmUiTreeSelectionEditor::uiEditorTypeName() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RicCreateDepthAdjustedLasFilesUi::~RicCreateDepthAdjustedLasFilesUi()
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QList<caf::PdmOptionItemInfo>
RicCreateDepthAdjustedLasFilesUi::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions )
{
QList<caf::PdmOptionItemInfo> options;
if ( fieldNeedingOptions == &selectedCase )
{
RimTools::caseOptionItems( &options );
}
if ( fieldNeedingOptions == &sourceWell )
{
RimProject* proj = RimProject::current();
if ( proj )
{
std::vector<RimWellPath*> allWellPaths = proj->activeOilField()->wellPathCollection->allWellPaths();
for ( auto* wellPath : allWellPaths )
{
if ( !wellPath->wellLogFiles().empty() )
{
options.push_back( caf::PdmOptionItemInfo( wellPath->name(), wellPath ) );
}
}
}
}
if ( fieldNeedingOptions == &selectedResultProperties )
{
if ( sourceWell && !sourceWell->wellLogFiles().empty() )
{
auto* firstWellLogFile = sourceWell->wellLogFiles()[0];
for ( auto* property : firstWellLogFile->wellLogChannels() )
{
if ( !m_depthProperties.contains( property->name() ) )
{
options.push_back( caf::PdmOptionItemInfo( property->name(), property->name() ) );
}
}
}
}
if ( fieldNeedingOptions == &destinationWells )
{
RimProject* proj = RimProject::current();
if ( proj )
{
std::vector<RimWellPath*> allWellPaths = proj->activeOilField()->wellPathCollection->allWellPaths();
for ( auto* wellPath : allWellPaths )
{
if ( wellPath != sourceWell )
{
options.push_back( caf::PdmOptionItemInfo( wellPath->name(), wellPath ) );
}
}
}
}
return options;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicCreateDepthAdjustedLasFilesUi::fieldChangedByUi( const caf::PdmFieldHandle* changedField,
const QVariant& oldValue,
const QVariant& newValue )
{
if ( changedField == &sourceWell )
{
selectedResultProperties.v().clear();
destinationWells.clearWithoutDelete();
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicCreateDepthAdjustedLasFilesUi::defineEditorAttribute( const caf::PdmFieldHandle* field,
QString uiConfigName,
caf::PdmUiEditorAttribute* attribute )
{
if ( field == &exportFolder )
{
caf::PdmUiFilePathEditorAttribute* myAttr = dynamic_cast<caf::PdmUiFilePathEditorAttribute*>( attribute );
if ( myAttr )
{
myAttr->m_selectDirectory = true;
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicCreateDepthAdjustedLasFilesUi::setDefaultValues()
{
// Default folder directory
QString defaultDir =
RiaApplication::instance()->lastUsedDialogDirectoryWithFallbackToProjectFolder( "WELL_LOGS_DIR" );
exportFolder = defaultDir;
// Default selected case and source well
RimProject* proj = RimProject::current();
if ( proj )
{
std::vector<RimCase*> allCases;
proj->allCases( allCases );
if ( !allCases.empty() ) selectedCase = allCases[0];
std::vector<RimWellPath*> allWellPaths = proj->activeOilField()->wellPathCollection->allWellPaths();
for ( auto* wellPath : allWellPaths )
{
if ( !wellPath->wellLogFiles().empty() )
{
sourceWell = wellPath;
break;
}
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RicCreateDepthAdjustedLasFilesUi::hasValidSelections() const
{
return !exportFolder().isEmpty() && sourceWell() != nullptr && selectedCase() != nullptr &&
!selectedResultProperties().empty() && !destinationWells.empty();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RicCreateDepthAdjustedLasFilesUi::invalidSelectionsLogString() const
{
if ( hasValidSelections() )
{
return QString();
}
QString logStr;
if ( exportFolder().isEmpty() )
{
logStr += "Selected Export folder is empty!\n";
}
if ( selectedCase() == nullptr )
{
logStr += "Selected case is not defined!\n";
}
if ( sourceWell() == nullptr )
{
logStr += "Source well is not defined!\n";
}
if ( selectedResultProperties().empty() )
{
logStr += "No result properties are selected!\n";
}
if ( destinationWells.empty() )
{
logStr += "No destination wells are selected!\n";
}
return logStr;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicCreateDepthAdjustedLasFilesUi::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
{
uiOrdering.add( &exportFolder );
uiOrdering.add( &selectedCase );
uiOrdering.add( &sourceWell );
uiOrdering.add( &selectedResultProperties );
uiOrdering.add( &destinationWells );
uiOrdering.skipRemainingFields( true );
}

View File

@@ -0,0 +1,71 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2023- Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RiaLasDefines.h"
#include "cafPdmField.h"
#include "cafPdmObject.h"
#include "cafPdmPtrArrayField.h"
#include "cafPdmPtrField.h"
#include "cafPdmUiItem.h"
#include <QList>
#include <QString>
#include <QStringList>
class RimCase;
class RimWellPath;
//==================================================================================================
///
//==================================================================================================
class RicCreateDepthAdjustedLasFilesUi : public caf::PdmObject
{
CAF_PDM_HEADER_INIT;
public:
RicCreateDepthAdjustedLasFilesUi();
~RicCreateDepthAdjustedLasFilesUi() override;
QList<caf::PdmOptionItemInfo> calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) override;
void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override;
void defineEditorAttribute( const caf::PdmFieldHandle* field,
QString uiConfigName,
caf::PdmUiEditorAttribute* attribute ) override;
void setDefaultValues();
bool hasValidSelections() const;
QString invalidSelectionsLogString() const;
public:
caf::PdmField<QString> exportFolder;
caf::PdmPtrField<RimCase*> selectedCase;
caf::PdmPtrField<RimWellPath*> sourceWell;
caf::PdmField<std::vector<QString>> selectedResultProperties;
caf::PdmPtrArrayField<RimWellPath*> destinationWells;
protected:
void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override;
private:
const QStringList m_depthProperties = QStringList( { RiaDefines::propertyNameMeasuredDepth(),
RiaDefines::propertyNameTvdMslDepth(),
RiaDefines::propertyNameTvdRkbDepth() } );
};

View File

@@ -72,12 +72,8 @@ RigFemScalarResultFrames* RigFemPartResultCalculatorKIndices::calculate( int
const size_t valCount = femPart->elementNodeResultCount();
dstFrameData.resize( valCount, std::numeric_limits<float>::infinity() );
const RigFormationNames* activeFormNames = m_resultCollection->activeFormationNames();
stepCountProgress.incrementProgress();
if ( activeFormNames )
{
// Has to be done before the parallel loop because the first call allocates.
const RigFemPartGrid* structGrid = femPart->getOrCreateStructGrid();
@@ -103,7 +99,6 @@ RigFemScalarResultFrames* RigFemPartResultCalculatorKIndices::calculate( int
}
}
}
}
return resFrames;
}

View File

@@ -342,6 +342,7 @@ caf::CmdFeatureMenuBuilder RimContextCommandBuilder::commandsFromSelection()
menuBuilder << "RicNewEditableWellPathFeature";
menuBuilder << "RicPasteModeledWellPathFeature";
menuBuilder << "RicCreateEnsembleWellLogFeature";
menuBuilder << "RicCreateDepthAdjustedLasFilesFeature";
menuBuilder.addSeparator();
menuBuilder.subMenuStart( "Import" );
menuBuilder << "RicWellPathsImportFileFeature";

View File

@@ -291,6 +291,14 @@ bool RigWellLogFile::hasTvdRkbChannel() const
return !m_tvdRkbLogName.isEmpty();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RigWellLogFile::getMissingValue() const
{
return m_wellLogFile->GetContMissing();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------

View File

@@ -61,6 +61,8 @@ public:
bool hasTvdMslChannel() const;
bool hasTvdRkbChannel() const;
double getMissingValue() const;
private:
void close();
QString depthUnitString() const;