diff --git a/ApplicationCode/Commands/RicWellLogTools.cpp b/ApplicationCode/Commands/RicWellLogTools.cpp index e53773e8e0..8dfa71eedc 100644 --- a/ApplicationCode/Commands/RicWellLogTools.cpp +++ b/ApplicationCode/Commands/RicWellLogTools.cpp @@ -33,6 +33,7 @@ #include "RimWellLogRftCurve.h" #include "RimWellLogTrack.h" #include "RimWellLogWbsCurve.h" +#include "RimWellMeasurementCurve.h" #include "RimWellPath.h" #include "RifReaderEclipseRft.h" @@ -410,3 +411,33 @@ RimWellLogWbsCurve* RicWellLogTools::addWellLogWbsCurve( RimWellLogTrack* plotTr useBranchDetection, showPlotWindow ); } + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimWellMeasurementCurve* RicWellLogTools::addWellMeasurementCurve( RimWellLogTrack* plotTrack, + RimWellPath* wellPath, + const QString& measurementKind, + bool showPlotWindow ) +{ + CVF_ASSERT( plotTrack ); + + RimWellMeasurementCurve* curve = new RimWellMeasurementCurve; + curve->setWellPath( wellPath ); + curve->setMeasurementKind( measurementKind ); + + plotTrack->addCurve( curve ); + plotTrack->updateConnectedEditors(); + + RiaApplication::instance()->project()->updateConnectedEditors(); + RiaGuiApplication::instance()->getOrCreateMainPlotWindow(); + RiuPlotMainWindowTools::selectAsCurrentItem( curve ); + + if ( showPlotWindow ) + { + // Make sure the summary plot window is visible + RiuPlotMainWindowTools::showPlotMainWindow(); + } + + return curve; +} diff --git a/ApplicationCode/Commands/RicWellLogTools.h b/ApplicationCode/Commands/RicWellLogTools.h index a848bdfcb5..791d423be5 100644 --- a/ApplicationCode/Commands/RicWellLogTools.h +++ b/ApplicationCode/Commands/RicWellLogTools.h @@ -33,6 +33,7 @@ class RimWellLogRftCurve; class RimWellLogTrack; class RimWellLogWbsCurve; class RimWellPath; +class RimWellMeasurementCurve; //-------------------------------------------------------------------------------------------------- /// @@ -66,6 +67,10 @@ public: int branchIndex, bool useBranchDetection, bool showPlotWindow = true ); + static RimWellMeasurementCurve* addWellMeasurementCurve( RimWellLogTrack* plotTrack, + RimWellPath* wellPath, + const QString& measurementName, + bool showPlotWindow = true ); private: template diff --git a/ApplicationCode/Commands/WellLogCommands/RicNewWellBoreStabilityPlotFeature.cpp b/ApplicationCode/Commands/WellLogCommands/RicNewWellBoreStabilityPlotFeature.cpp index 85eb22ba16..ba6d0125e6 100644 --- a/ApplicationCode/Commands/WellLogCommands/RicNewWellBoreStabilityPlotFeature.cpp +++ b/ApplicationCode/Commands/WellLogCommands/RicNewWellBoreStabilityPlotFeature.cpp @@ -321,6 +321,14 @@ void RicNewWellBoreStabilityPlotFeature::createStabilityCurvesTrack( RimWellBore curve->setSmoothCurve( true ); curve->setSmoothingThreshold( 0.002 ); } + + std::vector measurementNames; + measurementNames.push_back( "XLOT" ); + for ( size_t i = 0; i < measurementNames.size(); i++ ) + { + RicWellLogTools::addWellMeasurementCurve( stabilityCurvesTrack, wellPath, measurementNames[i] ); + } + stabilityCurvesTrack->setAutoScaleXEnabled( true ); } diff --git a/ApplicationCode/Commands/WellPathCommands/CMakeLists_files.cmake b/ApplicationCode/Commands/WellPathCommands/CMakeLists_files.cmake index d1f45fe324..2ed251eb7d 100644 --- a/ApplicationCode/Commands/WellPathCommands/CMakeLists_files.cmake +++ b/ApplicationCode/Commands/WellPathCommands/CMakeLists_files.cmake @@ -17,6 +17,7 @@ ${CMAKE_CURRENT_LIST_DIR}/RicWellPathFormationsImportFileFeature.h ${CMAKE_CURRENT_LIST_DIR}/RicPolylineTargetsPickEventHandler.h ${CMAKE_CURRENT_LIST_DIR}/RicNewPolylineTargetFeature.h ${CMAKE_CURRENT_LIST_DIR}/RicDeletePolylineTargetFeature.h +${CMAKE_CURRENT_LIST_DIR}/RicImportWellMeasurementsFeature.h ${CMAKE_CURRENT_LIST_DIR}/PointTangentManipulator/Ric3dObjectEditorHandle.h ${CMAKE_CURRENT_LIST_DIR}/PointTangentManipulator/RicPointTangentManipulator.h ${CMAKE_CURRENT_LIST_DIR}/PointTangentManipulator/RicWellTarget3dEditor.h @@ -44,6 +45,7 @@ ${CMAKE_CURRENT_LIST_DIR}/RicWellPathFormationsImportFileFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicPolylineTargetsPickEventHandler.cpp ${CMAKE_CURRENT_LIST_DIR}/RicNewPolylineTargetFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicDeletePolylineTargetFeature.cpp +${CMAKE_CURRENT_LIST_DIR}/RicImportWellMeasurementsFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/PointTangentManipulator/Ric3dObjectEditorHandle.cpp ${CMAKE_CURRENT_LIST_DIR}/PointTangentManipulator/RicPointTangentManipulator.cpp ${CMAKE_CURRENT_LIST_DIR}/PointTangentManipulator/RicWellTarget3dEditor.cpp diff --git a/ApplicationCode/Commands/WellPathCommands/RicImportWellMeasurementsFeature.cpp b/ApplicationCode/Commands/WellPathCommands/RicImportWellMeasurementsFeature.cpp new file mode 100644 index 0000000000..b3b84d5c4a --- /dev/null +++ b/ApplicationCode/Commands/WellPathCommands/RicImportWellMeasurementsFeature.cpp @@ -0,0 +1,133 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2019- Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RicImportWellMeasurementsFeature.h" + +#include "RiaApplication.h" +#include "RiaLogging.h" + +#include "RimPerforationCollection.h" +#include "RimPerforationInterval.h" +#include "RimProject.h" +#include "RimWellMeasurement.h" +#include "RimWellMeasurementCollection.h" +#include "RimWellPath.h" +#include "RimWellPathCollection.h" + +#include "RifFileParseTools.h" +#include "RifWellMeasurementReader.h" + +#include "Riu3DMainWindowTools.h" + +#include "cafSelectionManager.h" + +#include +#include + +CAF_CMD_SOURCE_INIT( RicImportWellMeasurementsFeature, "RicImportWellMeasurementsFeature" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RicImportWellMeasurementsFeature::isCommandEnabled() +{ + return ( RicImportWellMeasurementsFeature::selectedWellPathCollection() != nullptr ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RicImportWellMeasurementsFeature::onActionTriggered( bool isChecked ) +{ + RimWellPathCollection* wellPathCollection = RicImportWellMeasurementsFeature::selectedWellPathCollection(); + CVF_ASSERT( wellPathCollection ); + + // Open dialog box to select well path files + RiaApplication* app = RiaApplication::instance(); + QString defaultDir = app->lastUsedDialogDirectory( "WELLPATH_DIR" ); + QStringList wellPathFilePaths = QFileDialog::getOpenFileNames( Riu3DMainWindowTools::mainWindowWidget(), + "Import Well Measurements", + defaultDir, + "Well Measurements (*.csv);;All Files (*.*)" ); + + if ( wellPathFilePaths.size() < 1 ) return; + + // Remember the path to next time + app->setLastUsedDialogDirectory( "WELLPATH_DIR", QFileInfo( wellPathFilePaths.last() ).absolutePath() ); + + std::vector wellMeasurements; + try + { + RifWellMeasurementReader::readWellMeasurements( wellMeasurements, wellPathFilePaths ); + } + catch ( FileParseException& exception ) + { + RiaLogging::warning( QString( "Well measurement import failed: '%1'." ).arg( exception.message ) ); + return; + } + + RimWellMeasurement* lastWellMeasurement = nullptr; + for ( auto& measurement : wellMeasurements ) + { + RimWellMeasurement* wellMeasurement = new RimWellMeasurement; + wellMeasurement->setWellName( measurement.wellName ); + wellMeasurement->setMD( measurement.MD ); + wellMeasurement->setValue( measurement.value ); + wellMeasurement->setDate( measurement.date ); + wellMeasurement->setQuality( measurement.quality ); + wellMeasurement->setKind( measurement.kind ); + wellMeasurement->setRemark( measurement.remark ); + wellPathCollection->measurementCollection()->appendMeasurement( wellMeasurement ); + lastWellMeasurement = wellMeasurement; + } + wellPathCollection->uiCapability()->updateConnectedEditors(); + + if ( app->project() ) + { + app->project()->scheduleCreateDisplayModelAndRedrawAllViews(); + } + + if ( lastWellMeasurement ) + { + Riu3DMainWindowTools::selectAsCurrentItem( lastWellMeasurement ); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RicImportWellMeasurementsFeature::setupActionLook( QAction* actionToSetup ) +{ + actionToSetup->setText( "Import Measurements" ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimWellPathCollection* RicImportWellMeasurementsFeature::selectedWellPathCollection() +{ + std::vector objects; + caf::SelectionManager::instance()->objectsByType( &objects ); + + if ( objects.size() == 1 ) + { + return objects[0]; + } + + return nullptr; +} diff --git a/ApplicationCode/Commands/WellPathCommands/RicImportWellMeasurementsFeature.h b/ApplicationCode/Commands/WellPathCommands/RicImportWellMeasurementsFeature.h new file mode 100644 index 0000000000..d4776c1ac4 --- /dev/null +++ b/ApplicationCode/Commands/WellPathCommands/RicImportWellMeasurementsFeature.h @@ -0,0 +1,40 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2019- Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "cafCmdFeature.h" + +class RimWellPathCollection; + +//================================================================================================== +/// +//================================================================================================== +class RicImportWellMeasurementsFeature : public caf::CmdFeature +{ + CAF_CMD_HEADER_INIT; + +protected: + // Overrides + bool isCommandEnabled() override; + void onActionTriggered( bool isChecked ) override; + void setupActionLook( QAction* actionToSetup ) override; + +private: + static RimWellPathCollection* selectedWellPathCollection(); +}; diff --git a/ApplicationCode/Commands/WellPathCommands/RicWellPathPickEventHandler.cpp b/ApplicationCode/Commands/WellPathCommands/RicWellPathPickEventHandler.cpp index b2b5cda46c..64929f8126 100644 --- a/ApplicationCode/Commands/WellPathCommands/RicWellPathPickEventHandler.cpp +++ b/ApplicationCode/Commands/WellPathCommands/RicWellPathPickEventHandler.cpp @@ -24,6 +24,8 @@ #include "Rim2dIntersectionView.h" #include "Rim3dView.h" #include "RimPerforationInterval.h" +#include "RimWellMeasurement.h" +#include "RimWellMeasurementCollection.h" #include "RimWellPath.h" #include "RimWellPathAttribute.h" #include "RimWellPathAttributeCollection.h" @@ -130,6 +132,35 @@ bool RicWellPathPickEventHandler::handle3dPickEvent( const Ric3dPickEvent& event RiuMainWindow::instance()->setResultInfo( attrText ); RiuMainWindow::instance()->selectAsCurrentItem( collection ); } + else if ( dynamic_cast( sourceInfo->object() ) ) + { + RimWellMeasurement* measurement = dynamic_cast( sourceInfo->object() ); + RimWellMeasurementCollection* collection = nullptr; + measurement->firstAncestorOrThisOfTypeAsserted( collection ); + + QString measurementText = QString( "Well path name: %1\n" ).arg( measurement->wellName() ); + measurementText += QString( "Measured Depth: %1\n" ).arg( measurement->MD() ); + measurementText += QString( "Value: %1\n" ).arg( measurement->value() ); + measurementText += QString( "Date: %1\n" ).arg( measurement->date().toString() ); + + if ( !measurement->kind().isEmpty() ) + { + measurementText += QString( "Kind: %1\n" ).arg( measurement->kind() ); + } + + if ( measurement->quality() > 0 ) + { + measurementText += QString( "Quality: %1\n" ).arg( measurement->quality() ); + } + + if ( !measurement->remark().isEmpty() ) + { + measurementText += QString( "Remark: %1\n" ).arg( measurement->remark() ); + } + + RiuMainWindow::instance()->setResultInfo( measurementText ); + RiuMainWindow::instance()->selectAsCurrentItem( collection ); + } } if ( dynamic_cast( firstPickedPart->sourceInfo() ) ) diff --git a/ApplicationCode/FileInterface/CMakeLists_files.cmake b/ApplicationCode/FileInterface/CMakeLists_files.cmake index f06338536a..e0865f25e0 100644 --- a/ApplicationCode/FileInterface/CMakeLists_files.cmake +++ b/ApplicationCode/FileInterface/CMakeLists_files.cmake @@ -35,6 +35,7 @@ ${CMAKE_CURRENT_LIST_DIR}/RifDataSourceForRftPltQMetaType.h ${CMAKE_CURRENT_LIST_DIR}/RifEclipseUserDataKeywordTools.h ${CMAKE_CURRENT_LIST_DIR}/RifCsvUserData.h ${CMAKE_CURRENT_LIST_DIR}/RifCsvUserDataParser.h +${CMAKE_CURRENT_LIST_DIR}/RifWellMeasurementReader.h ${CMAKE_CURRENT_LIST_DIR}/RifWellPathFormationReader.h ${CMAKE_CURRENT_LIST_DIR}/RifWellPathFormationsImporter.h ${CMAKE_CURRENT_LIST_DIR}/RifElementPropertyTableReader.h @@ -88,6 +89,7 @@ ${CMAKE_CURRENT_LIST_DIR}/RifDataSourceForRftPlt.cpp ${CMAKE_CURRENT_LIST_DIR}/RifEclipseUserDataKeywordTools.cpp ${CMAKE_CURRENT_LIST_DIR}/RifCsvUserData.cpp ${CMAKE_CURRENT_LIST_DIR}/RifCsvUserDataParser.cpp +${CMAKE_CURRENT_LIST_DIR}/RifWellMeasurementReader.cpp ${CMAKE_CURRENT_LIST_DIR}/RifWellPathFormationReader.cpp ${CMAKE_CURRENT_LIST_DIR}/RifWellPathFormationsImporter.cpp ${CMAKE_CURRENT_LIST_DIR}/RifElementPropertyTableReader.cpp diff --git a/ApplicationCode/FileInterface/RifWellMeasurementReader.cpp b/ApplicationCode/FileInterface/RifWellMeasurementReader.cpp new file mode 100644 index 0000000000..6786a4b5d9 --- /dev/null +++ b/ApplicationCode/FileInterface/RifWellMeasurementReader.cpp @@ -0,0 +1,209 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2019- Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RifWellMeasurementReader.h" + +#include "RifFileParseTools.h" + +#include +#include +#include + +//================================================================================================== +/// +//================================================================================================== +void RifWellMeasurementReader::readWellMeasurements( std::vector& wellMeasurements, + const QStringList& filePaths ) +{ + for ( const QString& filePath : filePaths ) + { + try + { + readWellMeasurements( wellMeasurements, filePath ); + } + catch ( FileParseException& ) + { + // Delete all well measurements and rethrow exception + wellMeasurements.clear(); + throw; + } + } +} + +//================================================================================================== +/// +//================================================================================================== +void RifWellMeasurementReader::readWellMeasurements( std::vector& wellMeasurements, + const QString& filePath ) +{ + QFile file( filePath ); + if ( !file.open( QIODevice::ReadOnly | QIODevice::Text ) ) + { + throw FileParseException( QString( "Unable to open file: %1" ).arg( filePath ) ); + } + + QTextStream in( &file ); + int lineNumber = 1; + while ( !in.atEnd() ) + { + QString line = in.readLine(); + if ( !isEmptyLine( line ) && !isCommentLine( line ) ) + { + RifWellMeasurement wellMeasurement = parseWellMeasurement( line, lineNumber, filePath ); + wellMeasurements.push_back( wellMeasurement ); + } + + lineNumber++; + } +} + +//================================================================================================== +/// +//================================================================================================== +RifWellMeasurement + RifWellMeasurementReader::parseWellMeasurement( const QString& line, int lineNumber, const QString& filePath ) +{ + QStringList tokens = tokenize( line, "," ); + + if ( tokens.size() != 7 ) + { + throw FileParseException( QString( "Incomplete data on line %1: %2" ).arg( lineNumber ).arg( filePath ) ); + } + + // Check for unexpected empty tokens + QStringList nameOfNonEmptyTokens; + nameOfNonEmptyTokens << "Well Name" + << "Measured Depth" + << "Date" + << "Value" + << "Kind"; + verifyNonEmptyTokens( tokens, nameOfNonEmptyTokens, lineNumber, filePath ); + + RifWellMeasurement wellMeasurement; + wellMeasurement.wellName = tokens[0]; + wellMeasurement.MD = parseDouble( tokens[1], "Measured Depth", lineNumber, filePath ); + wellMeasurement.date = parseDate( tokens[2], "Date", lineNumber, filePath ); + wellMeasurement.value = parseDouble( tokens[3], "Value", lineNumber, filePath ); + wellMeasurement.kind = tokens[4]; + wellMeasurement.quality = parseInt( tokens[5], "Quality", lineNumber, filePath ); + wellMeasurement.remark = tokens[6]; + wellMeasurement.filePath = filePath; + return wellMeasurement; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QStringList RifWellMeasurementReader::tokenize( const QString& line, const QString& separator ) +{ + return RifFileParseTools::splitLineAndTrim( line, separator ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QDate RifWellMeasurementReader::parseDate( const QString& token, + const QString& propertyName, + int lineNumber, + const QString& filePath ) +{ + QDate date = QDate::fromString( token, Qt::ISODate ); + if ( !date.isValid() ) + { + throw FileParseException( QString( "Invalid date format (must be ISO 8601) for '%1' on line %2: %3" ) + .arg( propertyName ) + .arg( lineNumber ) + .arg( filePath ) ); + } + + return date; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +double RifWellMeasurementReader::parseDouble( const QString& token, + const QString& propertyName, + int lineNumber, + const QString& filePath ) +{ + bool isOk = false; + double value = token.toDouble( &isOk ); + if ( !isOk ) + { + throw FileParseException( + QString( "Invalid number for '%1' on line %2: %3" ).arg( propertyName ).arg( lineNumber ).arg( filePath ) ); + } + + return value; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +int RifWellMeasurementReader::parseInt( const QString& token, + const QString& propertyName, + int lineNumber, + const QString& filePath ) +{ + bool isOk = false; + int value = token.toInt( &isOk ); + if ( !isOk ) + { + throw FileParseException( + QString( "Invalid number for '%1' on line %2: %3" ).arg( propertyName ).arg( lineNumber ).arg( filePath ) ); + } + + return value; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RifWellMeasurementReader::isEmptyLine( const QString& line ) +{ + return line.trimmed().isEmpty(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RifWellMeasurementReader::isCommentLine( const QString& line ) +{ + return line.startsWith( "#" ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RifWellMeasurementReader::verifyNonEmptyTokens( const QStringList& tokens, + const QStringList& nameOfNonEmptyTokens, + int lineNumber, + const QString& filePath ) +{ + for ( int i = 0; i < nameOfNonEmptyTokens.size(); ++i ) + { + if ( tokens[i].isEmpty() ) + { + throw FileParseException( QString( "Unexpected empty '%1' on line %2: %3" ) + .arg( nameOfNonEmptyTokens[i] ) + .arg( lineNumber ) + .arg( filePath ) ); + } + } +} diff --git a/ApplicationCode/FileInterface/RifWellMeasurementReader.h b/ApplicationCode/FileInterface/RifWellMeasurementReader.h new file mode 100644 index 0000000000..f93241524d --- /dev/null +++ b/ApplicationCode/FileInterface/RifWellMeasurementReader.h @@ -0,0 +1,61 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2019- Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include + +#include +#include + +struct RifWellMeasurement +{ + QString wellName; + double MD; + double value; + QDate date; + int quality; + QString kind; + QString remark; + QString filePath; +}; + +//================================================================================================== +/// +//================================================================================================== +class RifWellMeasurementReader +{ +public: + static void readWellMeasurements( std::vector& wellMeasurements, const QStringList& filePaths ); + +private: + static void readWellMeasurements( std::vector& wellMeasurements, const QString& filePath ); + static RifWellMeasurement parseWellMeasurement( const QString& line, int lineNumber, const QString& filePath ); + static QStringList tokenize( const QString& line, const QString& separator ); + static void verifyNonEmptyTokens( const QStringList& tokens, + const QStringList& nameOfNonEmptyTokens, + int lineNumber, + const QString& filePath ); + + static QDate parseDate( const QString& token, const QString& propertyName, int lineNumber, const QString& filePath ); + static double parseDouble( const QString& token, const QString& propertyName, int lineNumber, const QString& filePath ); + static int parseInt( const QString& token, const QString& propertyName, int lineNumber, const QString& filePath ); + + static bool isEmptyLine( const QString& line ); + static bool isCommentLine( const QString& line ); +}; diff --git a/ApplicationCode/ModelVisualization/RivWellPathPartMgr.cpp b/ApplicationCode/ModelVisualization/RivWellPathPartMgr.cpp index 6e83b1856d..c48e8426d4 100644 --- a/ApplicationCode/ModelVisualization/RivWellPathPartMgr.cpp +++ b/ApplicationCode/ModelVisualization/RivWellPathPartMgr.cpp @@ -20,6 +20,7 @@ #include "RivWellPathPartMgr.h" +#include "RiaColorTables.h" #include "RiaGuiApplication.h" #include "RigEclipseCaseData.h" @@ -36,14 +37,19 @@ #include "RimFishbonesMultipleSubs.h" #include "RimPerforationCollection.h" #include "RimPerforationInterval.h" +#include "RimRegularLegendConfig.h" +#include "RimTools.h" +#include "RimWellMeasurement.h" +#include "RimWellMeasurementCollection.h" +#include "RimWellMeasurementFilter.h" +#include "RimWellMeasurementInViewCollection.h" #include "RimWellPath.h" #include "RimWellPathAttribute.h" #include "RimWellPathAttributeCollection.h" #include "RimWellPathCollection.h" -#include "RimWellPathValve.h" - #include "RimWellPathFracture.h" #include "RimWellPathFractureCollection.h" +#include "RimWellPathValve.h" #include "Riv3dWellLogPlanePartMgr.h" #include "RivFishbonesSubsPartMgr.h" @@ -66,7 +72,7 @@ #include "cvfFont.h" #include "cvfModelBasicList.h" #include "cvfPart.h" -#include "cvfScalarMapperDiscreteLinear.h" +#include "cvfScalarMapperContinuousLinear.h" #include "cvfTransform.h" #include "cvfqtUtils.h" @@ -249,6 +255,104 @@ void RivWellPathPartMgr::appendWellPathAttributesToModel( cvf::ModelBasicList* } } +cvf::Color3f RivWellPathPartMgr::mapWellMeasurementToColor( const QString& measurementKind, double value ) +{ + if ( measurementKind == "TH" ) return cvf::Color3f::RED; + if ( measurementKind == "LE" ) return cvf::Color3f::BLUE; + if ( measurementKind == "BA" ) return cvf::Color3f::GREEN; + if ( measurementKind == "CORE" ) return cvf::Color3f::BLACK; + + QStringList rangeBasedMeasurements; + rangeBasedMeasurements << "XLOT" + << "LOT" + << "FIT" + << "MCF" + << "MNF" + << "PPG"; + if ( rangeBasedMeasurements.contains( measurementKind ) ) + { + cvf::ScalarMapperContinuousLinear mapper; + mapper.setColors( RiaColorTables::normalPaletteColors().color3ubArray() ); + mapper.setRange( 1.0, 3.0 ); + cvf::Color3ub color = mapper.mapToColor( value ); + return cvf::Color3f( color ); + } + + return cvf::Color3f::RED; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RivWellPathPartMgr::appendWellMeasurementsToModel( cvf::ModelBasicList* model, + const caf::DisplayCoordTransform* displayCoordTransform, + double characteristicCellSize ) +{ + if ( !m_rimWellPath ) return; + + RimGridView* gridView = dynamic_cast( m_rimView.p() ); + if ( !gridView ) return; + + RimWellPathCollection* wellPathCollection = RimTools::wellPathCollection(); + if ( !wellPathCollection ) return; + + RimWellMeasurementCollection* wellMeasurementCollection = wellPathCollection->measurementCollection(); + if ( !wellMeasurementCollection ) return; + + if ( !gridView->measurementCollection()->isChecked() ) return; + + std::vector measurementKinds = gridView->measurementCollection()->measurementKinds(); + + RivPipeGeometryGenerator geoGenerator; + std::vector wellMeasurements = + RimWellMeasurementFilter::filterMeasurements( wellMeasurementCollection->measurements(), + *wellPathCollection, + *m_rimWellPath, + measurementKinds ); + + for ( RimWellMeasurement* wellMeasurement : wellMeasurements ) + { + double wellPathRadius = this->wellPathRadius( characteristicCellSize, this->wellPathCollection() ); + double startMD = wellMeasurement->MD() - wellPathRadius * 0.5; + double endMD = wellMeasurement->MD() + wellPathRadius * 0.5; + + std::vector displayCoords; + displayCoords.push_back( displayCoordTransform->transformToDisplayCoord( + m_rimWellPath->wellPathGeometry()->interpolatedPointAlongWellPath( startMD ) ) ); + displayCoords.push_back( displayCoordTransform->transformToDisplayCoord( + m_rimWellPath->wellPathGeometry()->interpolatedPointAlongWellPath( startMD ) ) ); + displayCoords.push_back( displayCoordTransform->transformToDisplayCoord( + m_rimWellPath->wellPathGeometry()->interpolatedPointAlongWellPath( endMD ) ) ); + displayCoords.push_back( displayCoordTransform->transformToDisplayCoord( + m_rimWellPath->wellPathGeometry()->interpolatedPointAlongWellPath( endMD ) ) ); + + std::vector radii; + radii.push_back( wellPathRadius ); + radii.push_back( wellPathRadius * 2.5 ); + radii.push_back( wellPathRadius * 2.5 ); + radii.push_back( wellPathRadius ); + + cvf::ref objectSourceInfo = new RivObjectSourceInfo( wellMeasurement ); + + cvf::Collection parts; + cvf::Color3f color = mapWellMeasurementToColor( wellMeasurement->kind(), wellMeasurement->value() ); + + // Use the view legend config to find color, if only one type of measurement is selected. + if ( measurementKinds.size() == 1 ) + { + color = cvf::Color3f( gridView->measurementCollection()->legendConfig()->scalarMapper()->mapToColor( + wellMeasurement->value() ) ); + } + + geoGenerator.tubeWithCenterLinePartsAndVariableWidth( &parts, displayCoords, radii, color ); + for ( auto part : parts ) + { + part->setSourceInfo( objectSourceInfo.p() ); + model->addPart( part.p() ); + } + } +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -711,6 +815,7 @@ void RivWellPathPartMgr::appendStaticGeometryPartsToModel( cvf::ModelBasicList* appendFishboneSubsPartsToModel( model, displayCoordTransform, characteristicCellSize ); appendImportedFishbonesToModel( model, displayCoordTransform, characteristicCellSize ); appendWellPathAttributesToModel( model, displayCoordTransform, characteristicCellSize ); + appendWellMeasurementsToModel( model, displayCoordTransform, characteristicCellSize ); RimGridView* gridView = dynamic_cast( m_rimView.p() ); if ( !gridView ) return; diff --git a/ApplicationCode/ModelVisualization/RivWellPathPartMgr.h b/ApplicationCode/ModelVisualization/RivWellPathPartMgr.h index fd2bc04cbc..d77f207b20 100644 --- a/ApplicationCode/ModelVisualization/RivWellPathPartMgr.h +++ b/ApplicationCode/ModelVisualization/RivWellPathPartMgr.h @@ -93,6 +93,10 @@ private: const caf::DisplayCoordTransform* displayCoordTransform, double characteristicCellSize ); + void appendWellMeasurementsToModel( cvf::ModelBasicList* model, + const caf::DisplayCoordTransform* displayCoordTransform, + double characteristicCellSize ); + void appendImportedFishbonesToModel( cvf::ModelBasicList* model, const caf::DisplayCoordTransform* displayCoordTransform, double characteristicCellSize ); @@ -125,6 +129,8 @@ private: bool isWellPathWithinBoundingBox( const cvf::BoundingBox& wellPathClipBoundingBox ) const; + static cvf::Color3f mapWellMeasurementToColor( const QString& measurementKind, double value ); + private: caf::PdmPointer m_rimWellPath; caf::PdmPointer m_rimView; diff --git a/ApplicationCode/ProjectDataModel/CMakeLists_files.cmake b/ApplicationCode/ProjectDataModel/CMakeLists_files.cmake index 0e05b1be15..e413b23947 100644 --- a/ApplicationCode/ProjectDataModel/CMakeLists_files.cmake +++ b/ApplicationCode/ProjectDataModel/CMakeLists_files.cmake @@ -26,6 +26,8 @@ ${CMAKE_CURRENT_LIST_DIR}/RimSimWellInViewCollection.h ${CMAKE_CURRENT_LIST_DIR}/RimWellPath.h ${CMAKE_CURRENT_LIST_DIR}/RimFileWellPath.h ${CMAKE_CURRENT_LIST_DIR}/RimModeledWellPath.h +${CMAKE_CURRENT_LIST_DIR}/RimWellMeasurement.h +${CMAKE_CURRENT_LIST_DIR}/RimWellMeasurementCollection.h ${CMAKE_CURRENT_LIST_DIR}/RimWellPathGeometryDef.h ${CMAKE_CURRENT_LIST_DIR}/RimWellPathAttribute.h ${CMAKE_CURRENT_LIST_DIR}/RimWellPathAttributeCollection.h @@ -141,6 +143,9 @@ ${CMAKE_CURRENT_LIST_DIR}/RimPlotAxisAnnotation.h ${CMAKE_CURRENT_LIST_DIR}/RimObservedDataCollection.h ${CMAKE_CURRENT_LIST_DIR}/RimObservedFmuRftData.h ${CMAKE_CURRENT_LIST_DIR}/RimMultiPlotCollection.h +${CMAKE_CURRENT_LIST_DIR}/RimWellMeasurementCurve.h +${CMAKE_CURRENT_LIST_DIR}/RimWellMeasurementFilter.h +${CMAKE_CURRENT_LIST_DIR}/RimWellMeasurementInViewCollection.h ) @@ -171,6 +176,8 @@ ${CMAKE_CURRENT_LIST_DIR}/RimSimWellInViewCollection.cpp ${CMAKE_CURRENT_LIST_DIR}/RimWellPath.cpp ${CMAKE_CURRENT_LIST_DIR}/RimFileWellPath.cpp ${CMAKE_CURRENT_LIST_DIR}/RimModeledWellPath.cpp +${CMAKE_CURRENT_LIST_DIR}/RimWellMeasurement.cpp +${CMAKE_CURRENT_LIST_DIR}/RimWellMeasurementCollection.cpp ${CMAKE_CURRENT_LIST_DIR}/RimWellPathGeometryDef.cpp ${CMAKE_CURRENT_LIST_DIR}/RimWellPathAttribute.cpp ${CMAKE_CURRENT_LIST_DIR}/RimWellPathAttributeCollection.cpp @@ -286,6 +293,9 @@ ${CMAKE_CURRENT_LIST_DIR}/RimPlotAxisAnnotation.cpp ${CMAKE_CURRENT_LIST_DIR}/RimObservedDataCollection.cpp ${CMAKE_CURRENT_LIST_DIR}/RimObservedFmuRftData.cpp ${CMAKE_CURRENT_LIST_DIR}/RimMultiPlotCollection.cpp +${CMAKE_CURRENT_LIST_DIR}/RimWellMeasurementCurve.cpp +${CMAKE_CURRENT_LIST_DIR}/RimWellMeasurementFilter.cpp +${CMAKE_CURRENT_LIST_DIR}/RimWellMeasurementInViewCollection.cpp ) list(APPEND CODE_HEADER_FILES diff --git a/ApplicationCode/ProjectDataModel/RimContextCommandBuilder.cpp b/ApplicationCode/ProjectDataModel/RimContextCommandBuilder.cpp index 7dc7192cb3..5dccc998db 100644 --- a/ApplicationCode/ProjectDataModel/RimContextCommandBuilder.cpp +++ b/ApplicationCode/ProjectDataModel/RimContextCommandBuilder.cpp @@ -310,6 +310,7 @@ caf::CmdFeatureMenuBuilder RimContextCommandBuilder::commandsFromSelection() menuBuilder.addSeparator(); menuBuilder << "RicWellPathImportPerforationIntervalsFeature"; menuBuilder << "RicWellPathImportCompletionsFileFeature"; + menuBuilder << "RicImportWellMeasurementsFeature"; menuBuilder.subMenuEnd(); menuBuilder.addSeparator(); menuBuilder.subMenuStart( "Export Well Paths", QIcon( ":/Save.png" ) ); diff --git a/ApplicationCode/ProjectDataModel/RimEclipseView.cpp b/ApplicationCode/ProjectDataModel/RimEclipseView.cpp index 9227ec38ca..24e9d10a7d 100644 --- a/ApplicationCode/ProjectDataModel/RimEclipseView.cpp +++ b/ApplicationCode/ProjectDataModel/RimEclipseView.cpp @@ -76,6 +76,7 @@ #include "RimViewLinker.h" #include "RimViewNameConfig.h" #include "RimVirtualPerforationResults.h" +#include "RimWellMeasurementInViewCollection.h" #include "RimWellPathCollection.h" #include "Riu3dSelectionManager.h" @@ -1311,6 +1312,12 @@ void RimEclipseView::onUpdateLegends() nativeOrOverrideViewer()->addColorLegendToBottomLeftCorner( virtLegend->titledOverlayFrame(), isUsingOverrideViewer() ); } + + if ( m_wellMeasurementCollection->isChecked() && m_wellMeasurementCollection->legendConfig()->showLegend() ) + { + m_wellMeasurementCollection->updateLegendRangesTextAndVisibility( nativeOrOverrideViewer(), + isUsingOverrideViewer() ); + } } //-------------------------------------------------------------------------------------------------- @@ -1719,6 +1726,7 @@ void RimEclipseView::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrderin uiTreeOrdering.add( faultResultSettings() ); uiTreeOrdering.add( &m_intersectionResultDefCollection ); uiTreeOrdering.add( wellCollection() ); + uiTreeOrdering.add( &m_wellMeasurementCollection ); { bool showFractureColors = false; @@ -1856,6 +1864,8 @@ void RimEclipseView::onResetLegendsInViewer() sepInterResDef->ternaryLegendConfig()->recreateLegend(); } + m_wellMeasurementCollection->legendConfig()->recreateLegend(); + nativeOrOverrideViewer()->removeAllColorLegends(); } @@ -2014,6 +2024,8 @@ std::vector RimEclipseView::legendConfigs() const absLegends.push_back( sepInterResDef->ternaryLegendConfig() ); } + absLegends.push_back( m_wellMeasurementCollection->legendConfig() ); + return absLegends; } diff --git a/ApplicationCode/ProjectDataModel/RimGeoMechView.cpp b/ApplicationCode/ProjectDataModel/RimGeoMechView.cpp index 846a7879d1..78e270c1db 100644 --- a/ApplicationCode/ProjectDataModel/RimGeoMechView.cpp +++ b/ApplicationCode/ProjectDataModel/RimGeoMechView.cpp @@ -46,6 +46,7 @@ #include "RimTernaryLegendConfig.h" #include "RimViewLinker.h" #include "RimViewNameConfig.h" +#include "RimWellMeasurementInViewCollection.h" #include "Riu3DMainWindowTools.h" #include "Riu3dSelectionManager.h" @@ -433,6 +434,8 @@ void RimGeoMechView::onResetLegendsInViewer() sepInterResDef->ternaryLegendConfig()->recreateLegend(); } + m_wellMeasurementCollection->legendConfig()->recreateLegend(); + nativeOrOverrideViewer()->removeAllColorLegends(); } @@ -483,6 +486,12 @@ void RimGeoMechView::onUpdateLegends() isUsingOverrideViewer() ); } } + + if ( m_wellMeasurementCollection->isChecked() && m_wellMeasurementCollection->legendConfig()->showLegend() ) + { + m_wellMeasurementCollection->updateLegendRangesTextAndVisibility( nativeOrOverrideViewer(), + isUsingOverrideViewer() ); + } } } @@ -907,6 +916,7 @@ void RimGeoMechView::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrderin uiTreeOrdering.add( cellResult() ); uiTreeOrdering.add( m_tensorResults() ); uiTreeOrdering.add( &m_intersectionResultDefCollection ); + uiTreeOrdering.add( &m_wellMeasurementCollection ); uiTreeOrdering.add( m_intersectionCollection() ); diff --git a/ApplicationCode/ProjectDataModel/RimGridView.cpp b/ApplicationCode/ProjectDataModel/RimGridView.cpp index 09931d0a61..6947ee2b13 100644 --- a/ApplicationCode/ProjectDataModel/RimGridView.cpp +++ b/ApplicationCode/ProjectDataModel/RimGridView.cpp @@ -33,6 +33,7 @@ #include "RimViewLinker.h" #include "RimViewLinkerCollection.h" #include "RimViewNameConfig.h" +#include "RimWellMeasurementInViewCollection.h" #include "Riu3DMainWindowTools.h" #include "RiuMainWindow.h" @@ -85,6 +86,10 @@ RimGridView::RimGridView() m_overlayInfoConfig = new Rim3dOverlayInfoConfig(); m_overlayInfoConfig->setReservoirView( this ); m_overlayInfoConfig.uiCapability()->setUiHidden( true ); + + CAF_PDM_InitFieldNoDefault( &m_wellMeasurementCollection, "WellMeasurements", "Well Measurements", "", "", "" ); + m_wellMeasurementCollection = new RimWellMeasurementInViewCollection; + m_wellMeasurementCollection.uiCapability()->setUiHidden( true ); } //-------------------------------------------------------------------------------------------------- @@ -117,6 +122,7 @@ RimGridView::~RimGridView( void ) delete this->m_overlayInfoConfig(); + delete m_wellMeasurementCollection; delete m_rangeFilterCollection; delete m_overrideRangeFilterCollection; delete m_intersectionCollection; @@ -161,6 +167,14 @@ RimIntersectionCollection* RimGridView::intersectionCollection() const return m_intersectionCollection(); } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimWellMeasurementInViewCollection* RimGridView::measurementCollection() const +{ + return m_wellMeasurementCollection; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationCode/ProjectDataModel/RimGridView.h b/ApplicationCode/ProjectDataModel/RimGridView.h index d0d33e27e3..d28621eb5a 100644 --- a/ApplicationCode/ProjectDataModel/RimGridView.h +++ b/ApplicationCode/ProjectDataModel/RimGridView.h @@ -30,6 +30,7 @@ class RimIntersectionResultsDefinitionCollection; class RimPropertyFilterCollection; class RimGridCollection; class RimCellRangeFilterCollection; +class RimWellMeasurementInViewCollection; class RimGridView : public Rim3dView { @@ -48,6 +49,7 @@ public: RimIntersectionCollection* intersectionCollection() const; RimIntersectionResultsDefinitionCollection* separateIntersectionResultsCollection() const; RimAnnotationInViewCollection* annotationCollection() const; + RimWellMeasurementInViewCollection* measurementCollection() const; virtual const RimPropertyFilterCollection* propertyFilterCollection() const = 0; void rangeFiltersUpdated(); @@ -87,11 +89,12 @@ protected: // Fields caf::PdmChildField m_intersectionResultDefCollection; - caf::PdmChildField m_overlayInfoConfig; - caf::PdmChildField m_rangeFilterCollection; - caf::PdmChildField m_overrideRangeFilterCollection; - caf::PdmChildField m_gridCollection; - caf::PdmChildField m_annotationCollection; + caf::PdmChildField m_overlayInfoConfig; + caf::PdmChildField m_rangeFilterCollection; + caf::PdmChildField m_overrideRangeFilterCollection; + caf::PdmChildField m_gridCollection; + caf::PdmChildField m_annotationCollection; + caf::PdmChildField m_wellMeasurementCollection; private: cvf::ref m_currentReservoirCellVisibility; diff --git a/ApplicationCode/ProjectDataModel/RimWellMeasurement.cpp b/ApplicationCode/ProjectDataModel/RimWellMeasurement.cpp new file mode 100644 index 0000000000..c5fc290898 --- /dev/null +++ b/ApplicationCode/ProjectDataModel/RimWellMeasurement.cpp @@ -0,0 +1,179 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2019- Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RimWellMeasurement.h" + +#include "RimProject.h" +#include "RimWellMeasurementCollection.h" +#include "RimWellPath.h" + +CAF_PDM_SOURCE_INIT( RimWellMeasurement, "WellMeasurement" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimWellMeasurement::RimWellMeasurement() +{ + CAF_PDM_InitObject( "RimWellMeasurement", "", "", "" ); + + CAF_PDM_InitFieldNoDefault( &m_wellName, "WellName", "Well Name", "", "", "" ); + CAF_PDM_InitField( &m_MD, "Depth", -1.0, "MD", "", "", "" ); + CAF_PDM_InitFieldNoDefault( &m_date, "Date", "Date", "", "", "" ); + CAF_PDM_InitField( &m_value, "Value", 0.0, "Value", "", "", "" ); + CAF_PDM_InitFieldNoDefault( &m_kind, "Kind", "Kind", "", "", "" ); + CAF_PDM_InitFieldNoDefault( &m_quality, "Quality", "Quality", "", "", "" ); + CAF_PDM_InitFieldNoDefault( &m_remark, "Remark", "Remark", "", "", "" ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimWellMeasurement::~RimWellMeasurement() {} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RimWellMeasurement::wellName() const +{ + return m_wellName(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellMeasurement::setWellName( const QString& wellName ) +{ + m_wellName = wellName; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +double RimWellMeasurement::MD() const +{ + return m_MD(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellMeasurement::setMD( double md ) +{ + m_MD = md; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QDate RimWellMeasurement::date() const +{ + return m_date(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellMeasurement::setDate( const QDate& date ) +{ + m_date = date; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +double RimWellMeasurement::value() const +{ + return m_value(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellMeasurement::setValue( double value ) +{ + m_value = value; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RimWellMeasurement::kind() const +{ + return m_kind(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellMeasurement::setKind( const QString& kind ) +{ + m_kind = kind; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +int RimWellMeasurement::quality() const +{ + return m_quality(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellMeasurement::setQuality( int quality ) +{ + m_quality = quality; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RimWellMeasurement::remark() const +{ + return m_remark(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellMeasurement::setRemark( const QString& remark ) +{ + m_remark = remark; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellMeasurement::fieldChangedByUi( const caf::PdmFieldHandle* changedField, + const QVariant& oldValue, + const QVariant& newValue ) +{ + RimProject* proj; + this->firstAncestorOrThisOfTypeAsserted( proj ); + proj->scheduleCreateDisplayModelAndRedrawAllViews(); + + RimWellMeasurementCollection* wellMeasurementCollection; + this->firstAncestorOrThisOfTypeAsserted( wellMeasurementCollection ); + wellMeasurementCollection->updateAllReferringTracks(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellMeasurement::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) {} diff --git a/ApplicationCode/ProjectDataModel/RimWellMeasurement.h b/ApplicationCode/ProjectDataModel/RimWellMeasurement.h new file mode 100644 index 0000000000..c9f15ebc84 --- /dev/null +++ b/ApplicationCode/ProjectDataModel/RimWellMeasurement.h @@ -0,0 +1,70 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2019- Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "cafPdmObject.h" + +#include "cafPdmField.h" + +#include +#include + +class RimWellPath; + +//================================================================================================== +/// +//================================================================================================== +class RimWellMeasurement : public caf::PdmObject +{ + CAF_PDM_HEADER_INIT; + +public: + RimWellMeasurement(); + ~RimWellMeasurement() override; + + QString wellName() const; + void setWellName( const QString& wellName ); + double MD() const; + void setMD( double md ); + QDate date() const; + void setDate( const QDate& date ); + double value() const; + void setValue( double value ); + QString kind() const; + void setKind( const QString& kind ); + int quality() const; + void setQuality( int quality ); + QString remark() const; + void setRemark( const QString& remark ); + +private: + void fieldChangedByUi( const caf::PdmFieldHandle* changedField, + const QVariant& oldValue, + const QVariant& newValue ) override; + void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override; + +private: + caf::PdmField m_wellName; + caf::PdmField m_MD; + caf::PdmField m_date; + caf::PdmField m_value; + caf::PdmField m_kind; + caf::PdmField m_quality; + caf::PdmField m_remark; +}; diff --git a/ApplicationCode/ProjectDataModel/RimWellMeasurementCollection.cpp b/ApplicationCode/ProjectDataModel/RimWellMeasurementCollection.cpp new file mode 100644 index 0000000000..09316f06d4 --- /dev/null +++ b/ApplicationCode/ProjectDataModel/RimWellMeasurementCollection.cpp @@ -0,0 +1,173 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2019- Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// +#include "RimWellMeasurementCollection.h" + +#include "RimProject.h" +#include "RimWellLogTrack.h" +#include "RimWellMeasurement.h" + +#include "cafCmdFeatureMenuBuilder.h" +#include "cafPdmUiTableViewEditor.h" +#include "cafPdmUiTreeOrdering.h" +#include "cafPdmUiTreeSelectionEditor.h" + +CAF_PDM_SOURCE_INIT( RimWellMeasurementCollection, "WellMeasurements" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimWellMeasurementCollection::RimWellMeasurementCollection() +{ + CAF_PDM_InitObject( "Well Measurement", "", "", "" ); + + CAF_PDM_InitFieldNoDefault( &m_measurements, "Measurements", "Well Measurements", "", "", "" ); + m_measurements.uiCapability()->setUiEditorTypeName( caf::PdmUiTableViewEditor::uiEditorTypeName() ); + m_measurements.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::TOP ); + m_measurements.uiCapability()->setUiTreeHidden( true ); + + this->setName( "Well Measurements" ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimWellMeasurementCollection::~RimWellMeasurementCollection() {} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellMeasurementCollection::updateAllReferringTracks() +{ + std::vector wellLogTracks; + + this->objectsWithReferringPtrFieldsOfType( wellLogTracks ); + for ( RimWellLogTrack* track : wellLogTracks ) + { + track->loadDataAndUpdate(); + } + this->updateConnectedEditors(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RimWellMeasurementCollection::measurements() const +{ + std::vector attrs; + + for ( auto attr : m_measurements ) + { + attrs.push_back( attr.p() ); + } + return attrs; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellMeasurementCollection::insertMeasurement( RimWellMeasurement* insertBefore, RimWellMeasurement* measurement ) +{ + size_t index = m_measurements.index( insertBefore ); + if ( index < m_measurements.size() ) + m_measurements.insert( index, measurement ); + else + m_measurements.push_back( measurement ); + + this->updateAllReferringTracks(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellMeasurementCollection::appendMeasurement( RimWellMeasurement* measurement ) +{ + m_measurements.push_back( measurement ); + this->updateAllReferringTracks(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellMeasurementCollection::deleteMeasurement( RimWellMeasurement* measurementToDelete ) +{ + m_measurements.removeChildObject( measurementToDelete ); + delete measurementToDelete; + + this->updateAllReferringTracks(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellMeasurementCollection::deleteAllMeasurements() +{ + m_measurements.deleteAllChildObjects(); + this->updateAllReferringTracks(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellMeasurementCollection::defineEditorAttribute( const caf::PdmFieldHandle* field, + QString uiConfigName, + caf::PdmUiEditorAttribute* attribute ) +{ + if ( field == &m_measurements ) + { + auto tvAttribute = dynamic_cast( attribute ); + if ( tvAttribute ) + { + tvAttribute->resizePolicy = caf::PdmUiTableViewEditorAttribute::RESIZE_TO_FILL_CONTAINER; + tvAttribute->alwaysEnforceResizePolicy = true; + tvAttribute->minimumHeight = 300; + } + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellMeasurementCollection::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) +{ + uiOrdering.add( &m_measurements ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellMeasurementCollection::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, + QString uiConfigName /*= ""*/ ) +{ + uiTreeOrdering.skipRemainingChildren( true ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellMeasurementCollection::fieldChangedByUi( const caf::PdmFieldHandle* changedField, + const QVariant& oldValue, + const QVariant& newValue ) +{ + if ( changedField == &m_isChecked ) + { + RimProject* proj; + this->firstAncestorOrThisOfTypeAsserted( proj ); + proj->scheduleCreateDisplayModelAndRedrawAllViews(); + this->updateAllReferringTracks(); + } +} diff --git a/ApplicationCode/ProjectDataModel/RimWellMeasurementCollection.h b/ApplicationCode/ProjectDataModel/RimWellMeasurementCollection.h new file mode 100644 index 0000000000..2ea86124b6 --- /dev/null +++ b/ApplicationCode/ProjectDataModel/RimWellMeasurementCollection.h @@ -0,0 +1,60 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2019- Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "RimCheckableNamedObject.h" + +#include "cafPdmChildArrayField.h" +#include "cafPdmField.h" +#include "cafPdmObject.h" +#include "cafPdmProxyValueField.h" + +class RimWellMeasurement; + +//================================================================================================== +/// +//================================================================================================== +class RimWellMeasurementCollection : public RimCheckableNamedObject +{ + CAF_PDM_HEADER_INIT; + +public: + RimWellMeasurementCollection(); + ~RimWellMeasurementCollection() override; + + std::vector measurements() const; + + void updateAllReferringTracks(); + void insertMeasurement( RimWellMeasurement* insertBefore, RimWellMeasurement* measurement ); + void appendMeasurement( RimWellMeasurement* measurement ); + void deleteMeasurement( RimWellMeasurement* measurementToDelete ); + void deleteAllMeasurements(); + +protected: + void defineEditorAttribute( const caf::PdmFieldHandle* field, + QString uiConfigName, + caf::PdmUiEditorAttribute* attribute ) override; + void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override; + void defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName = "" ) override; + void fieldChangedByUi( const caf::PdmFieldHandle* changedField, + const QVariant& oldValue, + const QVariant& newValue ) override; + +private: + caf::PdmChildArrayField m_measurements; +}; diff --git a/ApplicationCode/ProjectDataModel/RimWellMeasurementCurve.cpp b/ApplicationCode/ProjectDataModel/RimWellMeasurementCurve.cpp new file mode 100644 index 0000000000..b01edc4b77 --- /dev/null +++ b/ApplicationCode/ProjectDataModel/RimWellMeasurementCurve.cpp @@ -0,0 +1,392 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2019- Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RimWellMeasurementCurve.h" + +#include "RigWellLogCurveData.h" +#include "RigWellPath.h" + +#include "RimProject.h" +#include "RimTools.h" +#include "RimWellLogFile.h" +#include "RimWellLogPlot.h" +#include "RimWellLogTrack.h" +#include "RimWellMeasurement.h" +#include "RimWellMeasurementCollection.h" +#include "RimWellMeasurementFilter.h" +#include "RimWellPath.h" +#include "RimWellPathCollection.h" +#include "RimWellPlotTools.h" +#include "RimWellRftPlot.h" + +#include "RiuQwtPlotCurve.h" +#include "RiuQwtPlotWidget.h" + +#include "RiaApplication.h" +#include "RiaPreferences.h" + +#include "cafPdmUiTreeOrdering.h" + +#include +#include + +CAF_PDM_SOURCE_INIT( RimWellMeasurementCurve, "WellMeasurementCurve" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimWellMeasurementCurve::RimWellMeasurementCurve() +{ + CAF_PDM_InitObject( "Well Measurement Curve", "", "", "" ); + + CAF_PDM_InitFieldNoDefault( &m_wellPath, "CurveWellPath", "Well Path", "", "", "" ); + m_wellPath.uiCapability()->setUiTreeChildrenHidden( true ); + + CAF_PDM_InitFieldNoDefault( &m_measurementKind, "CurveMeasurementKind", "Measurement Kind", "", "", "" ); + m_measurementKind.uiCapability()->setUiTreeChildrenHidden( true ); + + m_wellPath = nullptr; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimWellMeasurementCurve::~RimWellMeasurementCurve() {} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellMeasurementCurve::onLoadDataAndUpdate( bool updateParentPlot ) +{ + this->RimPlotCurve::updateCurvePresentation( updateParentPlot ); + + if ( isCurveVisible() ) + { + RimWellLogPlot* wellLogPlot; + firstAncestorOrThisOfType( wellLogPlot ); + CVF_ASSERT( wellLogPlot ); + + RimWellPathCollection* wellPathCollection = RimTools::wellPathCollection(); + if ( m_wellPath && !m_measurementKind().isEmpty() && wellPathCollection ) + { + const RimWellMeasurementCollection* measurementCollection = wellPathCollection->measurementCollection(); + + std::vector measurementKinds; + measurementKinds.push_back( measurementKind() ); + + std::vector measurements = + RimWellMeasurementFilter::filterMeasurements( measurementCollection->measurements(), + *wellPathCollection, + *m_wellPath, + measurementKinds ); + + // Extract the values for this measurement kind + std::vector values; + std::vector measuredDepthValues; + for ( auto& measurement : measurements ) + { + if ( measurement->kind() == measurementKind() ) + { + values.push_back( measurement->value() ); + measuredDepthValues.push_back( measurement->MD() ); + } + } + + if ( values.size() == measuredDepthValues.size() ) + { + this->setValuesAndDepths( values, + measuredDepthValues, + RiaDefines::MEASURED_DEPTH, + RiaDefines::UNIT_METER, + false ); + + RigWellPath* rigWellPath = m_wellPath->wellPathGeometry(); + if ( rigWellPath ) + { + std::vector trueVerticalDepthValues; + + for ( double measuredDepthValue : measuredDepthValues ) + { + trueVerticalDepthValues.push_back( + -rigWellPath->interpolatedPointAlongWellPath( measuredDepthValue ).z() ); + } + this->setValuesAndDepths( values, + measuredDepthValues, + RiaDefines::TRUE_VERTICAL_DEPTH, + RiaDefines::UNIT_METER, + false ); + } + } + + if ( m_isUsingAutoName ) + { + m_qwtPlotCurve->setTitle( createCurveAutoName() ); + } + + setSymbol( getSymbolForMeasurementKind( m_measurementKind() ) ); + setColor( getColorForMeasurementKind( measurementKind() ) ); + setLineStyle( RiuQwtPlotCurve::STYLE_NONE ); + + RiaDefines::DepthUnitType displayUnit = RiaDefines::UNIT_METER; + if ( wellLogPlot ) + { + displayUnit = wellLogPlot->depthUnit(); + } + if ( wellLogPlot && wellLogPlot->depthType() == RiaDefines::TRUE_VERTICAL_DEPTH && + this->curveData()->availableDepthTypes().count( RiaDefines::TRUE_VERTICAL_DEPTH ) ) + { + m_qwtPlotCurve->setSamples( this->curveData()->xPlotValues().data(), + this->curveData() + ->depthPlotValues( RiaDefines::TRUE_VERTICAL_DEPTH, displayUnit ) + .data(), + static_cast( this->curveData()->xPlotValues().size() ) ); + } + else + { + m_qwtPlotCurve + ->setSamples( this->curveData()->xPlotValues().data(), + this->curveData()->depthPlotValues( RiaDefines::MEASURED_DEPTH, displayUnit ).data(), + static_cast( this->curveData()->xPlotValues().size() ) ); + } + m_qwtPlotCurve->setLineSegmentStartStopIndices( this->curveData()->polylineStartStopIndices() ); + } + + if ( updateParentPlot ) + { + updateZoomInParentPlot(); + } + + if ( m_parentQwtPlot ) + { + m_parentQwtPlot->replot(); + } + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellMeasurementCurve::setWellPath( RimWellPath* wellPath ) +{ + m_wellPath = wellPath; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimWellPath* RimWellMeasurementCurve::wellPath() const +{ + return m_wellPath; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellMeasurementCurve::fieldChangedByUi( const caf::PdmFieldHandle* changedField, + const QVariant& oldValue, + const QVariant& newValue ) +{ + RimWellLogCurve::fieldChangedByUi( changedField, oldValue, newValue ); + + if ( changedField == &m_wellPath || changedField == &m_measurementKind ) + { + this->loadDataAndUpdate( true ); + } + if ( m_parentQwtPlot ) m_parentQwtPlot->replot(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellMeasurementCurve::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) +{ + RimPlotCurve::updateOptionSensitivity(); + + caf::PdmUiGroup* curveDataGroup = uiOrdering.addNewGroup( "Curve Data" ); + curveDataGroup->add( &m_wellPath ); + curveDataGroup->add( &m_measurementKind ); + + caf::PdmUiGroup* appearanceGroup = uiOrdering.addNewGroup( "Appearance" ); + RimPlotCurve::appearanceUiOrdering( *appearanceGroup ); + + caf::PdmUiGroup* nameGroup = uiOrdering.addNewGroup( "Curve Name" ); + nameGroup->add( &m_showLegend ); + RimPlotCurve::curveNameUiOrdering( *nameGroup ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellMeasurementCurve::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, + QString uiConfigName /*= ""*/ ) +{ + uiTreeOrdering.skipRemainingChildren( true ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QList + RimWellMeasurementCurve::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions, bool* useOptionsOnly ) +{ + QList options; + + options = RimWellLogCurve::calculateValueOptions( fieldNeedingOptions, useOptionsOnly ); + if ( options.size() > 0 ) return options; + + if ( fieldNeedingOptions == &m_wellPath ) + { + auto wellPathColl = RimTools::wellPathCollection(); + if ( wellPathColl ) + { + caf::PdmChildArrayField& wellPaths = wellPathColl->wellPaths; + + for ( size_t i = 0; i < wellPaths.size(); i++ ) + { + options.push_back( caf::PdmOptionItemInfo( wellPaths[i]->name(), wellPaths[i] ) ); + } + + if ( options.size() > 0 ) + { + options.push_front( caf::PdmOptionItemInfo( "None", nullptr ) ); + } + } + } + + if ( fieldNeedingOptions == &m_measurementKind ) + { + if ( m_wellPath() ) + { + // TODO: take all options from somewhere and filter based on + // what the current well path has set. + options.push_back( caf::PdmOptionItemInfo( "XLOT", "XLOT" ) ); + options.push_back( caf::PdmOptionItemInfo( "LOT", "LOT" ) ); + options.push_back( caf::PdmOptionItemInfo( "FIT", "FIT" ) ); + options.push_back( caf::PdmOptionItemInfo( "DP", "DP" ) ); + } + } + + return options; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellMeasurementCurve::initAfterRead() +{ + if ( !m_wellPath ) return; + + // TODO: do something here? +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RimWellMeasurementCurve::createCurveAutoName() +{ + if ( m_wellPath ) + { + QStringList name; + name.push_back( wellName() ); + name.push_back( measurementKind() ); + + return name.join( ", " ); + } + + return "Empty curve"; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RimWellMeasurementCurve::wellLogChannelName() const +{ + // Does not really make sense for measurement curve. + return QString( "" ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RimWellMeasurementCurve::wellName() const +{ + if ( m_wellPath ) + { + return m_wellPath->name(); + } + + return QString( "" ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RimWellMeasurementCurve::measurementKind() const +{ + if ( !m_measurementKind().isEmpty() ) + { + return m_measurementKind(); + } + + return QString( "None" ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellMeasurementCurve::setMeasurementKind( const QString& measurementKind ) +{ + m_measurementKind = measurementKind; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RiuQwtSymbol::PointSymbolEnum RimWellMeasurementCurve::getSymbolForMeasurementKind( const QString& measurementKind ) +{ + std::map symbolTable; + symbolTable["XLOT"] = RiuQwtSymbol::SYMBOL_RECT; + symbolTable["LOT"] = RiuQwtSymbol::SYMBOL_TRIANGLE; + symbolTable["FIT"] = RiuQwtSymbol::SYMBOL_DIAMOND; + symbolTable["MCF"] = RiuQwtSymbol::SYMBOL_ELLIPSE; + symbolTable["MNF"] = RiuQwtSymbol::SYMBOL_ELLIPSE; + symbolTable["TH"] = RiuQwtSymbol::SYMBOL_STAR1; + symbolTable["LE"] = RiuQwtSymbol::SYMBOL_STAR2; + symbolTable["BA"] = RiuQwtSymbol::SYMBOL_STAR1; + symbolTable["CORE"] = RiuQwtSymbol::SYMBOL_RECT; + symbolTable["PPG"] = RiuQwtSymbol::SYMBOL_RECT; + + auto it = symbolTable.find( measurementKind ); + if ( it != symbolTable.end() ) + return it->second; + else + return RiuQwtSymbol::SYMBOL_CROSS; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +cvf::Color3f RimWellMeasurementCurve::getColorForMeasurementKind( const QString& measurementKind ) +{ + if ( measurementKind == "TH" ) return cvf::Color3f::RED; + if ( measurementKind == "LE" ) return cvf::Color3f::BLUE; + if ( measurementKind == "BA" ) return cvf::Color3f::GREEN; + if ( measurementKind == "CORE" ) return cvf::Color3f::BLACK; + + return cvf::Color3f::CRIMSON; +} diff --git a/ApplicationCode/ProjectDataModel/RimWellMeasurementCurve.h b/ApplicationCode/ProjectDataModel/RimWellMeasurementCurve.h new file mode 100644 index 0000000000..d80122f4c0 --- /dev/null +++ b/ApplicationCode/ProjectDataModel/RimWellMeasurementCurve.h @@ -0,0 +1,74 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2019- Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "RimWellLogCurve.h" + +#include "RiuQwtSymbol.h" + +#include "cafPdmField.h" +#include "cafPdmPtrField.h" + +#include + +class RimWellPath; +class RimWellMeasurement; + +//================================================================================================== +/// +//================================================================================================== +class RimWellMeasurementCurve : public RimWellLogCurve +{ + CAF_PDM_HEADER_INIT; + +public: + RimWellMeasurementCurve(); + ~RimWellMeasurementCurve() override; + + void setWellPath( RimWellPath* wellPath ); + RimWellPath* wellPath() const; + void setMeasurementKind( const QString& measurementKind ); + QString measurementKind() const; + + // Overrides from RimWellLogPlotCurve + QString wellName() const override; + QString wellLogChannelName() const override; + +protected: + // Overrides from RimWellLogCurve + QString createCurveAutoName() override; + void onLoadDataAndUpdate( bool updateParentPlot ) override; + + // Pdm overrrides + void fieldChangedByUi( const caf::PdmFieldHandle* changedField, + const QVariant& oldValue, + const QVariant& newValue ) override; + void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override; + void defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName = "" ) override; + QList calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions, + bool* useOptionsOnly ) override; + void initAfterRead() override; + + RiuQwtSymbol::PointSymbolEnum getSymbolForMeasurementKind( const QString& measurementKind ); + cvf::Color3f getColorForMeasurementKind( const QString& measurementKind ); + +protected: + caf::PdmPtrField m_wellPath; + caf::PdmField m_measurementKind; +}; diff --git a/ApplicationCode/ProjectDataModel/RimWellMeasurementFilter.cpp b/ApplicationCode/ProjectDataModel/RimWellMeasurementFilter.cpp new file mode 100644 index 0000000000..ab75f3ad56 --- /dev/null +++ b/ApplicationCode/ProjectDataModel/RimWellMeasurementFilter.cpp @@ -0,0 +1,66 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2019- Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// +#include "RimWellMeasurementFilter.h" + +#include "RimWellMeasurement.h" +#include "RimWellMeasurementCollection.h" +#include "RimWellPath.h" +#include "RimWellPathCollection.h" + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector + RimWellMeasurementFilter::filterMeasurements( const std::vector& measurements, + const RimWellPathCollection& wellPathCollection, + const RimWellPath& wellPath, + const std::vector& measurementKinds ) +{ + std::vector filteredMeasurements; + + for ( auto& measurement : measurements ) + { + if ( std::find( measurementKinds.begin(), measurementKinds.end(), measurement->kind() ) != measurementKinds.end() ) + { + RimWellPath* matchedWellPath = wellPathCollection.tryFindMatchingWellPath( measurement->wellName() ); + if ( matchedWellPath && matchedWellPath == &wellPath ) + { + filteredMeasurements.push_back( measurement ); + } + } + } + + return filteredMeasurements; +} + +std::vector + RimWellMeasurementFilter::filterMeasurements( const std::vector& measurements, + const std::vector& measurementKinds ) +{ + std::vector filteredMeasurements; + + for ( auto& measurement : measurements ) + { + if ( std::find( measurementKinds.begin(), measurementKinds.end(), measurement->kind() ) != measurementKinds.end() ) + { + filteredMeasurements.push_back( measurement ); + } + } + + return filteredMeasurements; +} diff --git a/ApplicationCode/ProjectDataModel/RimWellMeasurementFilter.h b/ApplicationCode/ProjectDataModel/RimWellMeasurementFilter.h new file mode 100644 index 0000000000..1ccdf3f57f --- /dev/null +++ b/ApplicationCode/ProjectDataModel/RimWellMeasurementFilter.h @@ -0,0 +1,40 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2019- Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include + +class RimWellPath; +class RimWellMeasurement; +class RimWellPathCollection; + +class QString; + +class RimWellMeasurementFilter +{ +public: + static std::vector filterMeasurements( const std::vector& measurements, + const RimWellPathCollection& wellPathCollection, + const RimWellPath& rimWellPath, + const std::vector& measurementKinds ); + static std::vector filterMeasurements( const std::vector& measurements, + const std::vector& measurementKinds ); + +private: + RimWellMeasurementFilter(); +}; diff --git a/ApplicationCode/ProjectDataModel/RimWellMeasurementInViewCollection.cpp b/ApplicationCode/ProjectDataModel/RimWellMeasurementInViewCollection.cpp new file mode 100644 index 0000000000..ed27b6352f --- /dev/null +++ b/ApplicationCode/ProjectDataModel/RimWellMeasurementInViewCollection.cpp @@ -0,0 +1,197 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2019- Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// +#include "RimWellMeasurementInViewCollection.h" + +#include "Rim3dView.h" +#include "RimProject.h" +#include "RimRegularLegendConfig.h" +#include "RimTools.h" +#include "RimWellLogTrack.h" +#include "RimWellMeasurement.h" +#include "RimWellMeasurementCollection.h" +#include "RimWellMeasurementFilter.h" +#include "RimWellPathCollection.h" + +#include "RiuViewer.h" + +#include "cafCmdFeatureMenuBuilder.h" +#include "cafPdmUiTableViewEditor.h" +#include "cafPdmUiTreeOrdering.h" +#include "cafPdmUiTreeSelectionEditor.h" + +#include + +CAF_PDM_SOURCE_INIT( RimWellMeasurementInViewCollection, "WellMeasurementsInView" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimWellMeasurementInViewCollection::RimWellMeasurementInViewCollection() +{ + CAF_PDM_InitObject( "Well Measurement", "", "", "" ); + + CAF_PDM_InitFieldNoDefault( &m_legendConfig, "LegendDefinition", "Color Legend", "", "", "" ); + m_legendConfig = new RimRegularLegendConfig(); + m_legendConfig.uiCapability()->setUiHidden( true ); + + CAF_PDM_InitFieldNoDefault( &m_measurementKinds, "MeasurementKinds", "Measurent Kinds", "", "", "" ); + m_measurementKinds.uiCapability()->setAutoAddingOptionFromValue( false ); + m_measurementKinds.uiCapability()->setUiEditorTypeName( caf::PdmUiTreeSelectionEditor::uiEditorTypeName() ); + m_measurementKinds.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::HIDDEN ); + m_measurementKinds.xmlCapability()->disableIO(); + + this->setName( "Well Measurements" ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimWellMeasurementInViewCollection::~RimWellMeasurementInViewCollection() +{ + delete m_legendConfig; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellMeasurementInViewCollection::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, + QString uiConfigName /*= ""*/ ) +{ + uiTreeOrdering.add( &m_legendConfig ); + uiTreeOrdering.skipRemainingChildren( true ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellMeasurementInViewCollection::fieldChangedByUi( const caf::PdmFieldHandle* changedField, + const QVariant& oldValue, + const QVariant& newValue ) +{ + if ( changedField == &m_isChecked || changedField == &m_measurementKinds || changedField == &m_legendConfig ) + { + updateLegendData(); + RimProject* proj; + this->firstAncestorOrThisOfTypeAsserted( proj ); + proj->scheduleCreateDisplayModelAndRedrawAllViews(); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimRegularLegendConfig* RimWellMeasurementInViewCollection::legendConfig() +{ + return m_legendConfig; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RimWellMeasurementInViewCollection::updateLegendData() +{ + RimWellPathCollection* wellPathCollection = RimTools::wellPathCollection(); + if ( !wellPathCollection ) return false; + + RimWellMeasurementCollection* wellMeasurementCollection = wellPathCollection->measurementCollection(); + if ( !wellMeasurementCollection ) return false; + + std::vector selectedMeasurementKinds = measurementKinds(); + + // Only show legend when only one measurement is selected + if ( selectedMeasurementKinds.size() == 1 ) + { + std::vector wellMeasurements = + RimWellMeasurementFilter::filterMeasurements( wellMeasurementCollection->measurements(), + selectedMeasurementKinds ); + + double minValue = HUGE_VAL; + double maxValue = -HUGE_VAL; + double posClosestToZero = HUGE_VAL; + double negClosestToZero = -HUGE_VAL; + + for ( auto& measurement : wellMeasurements ) + { + minValue = std::min( measurement->value(), minValue ); + maxValue = std::max( measurement->value(), maxValue ); + } + + if ( minValue != HUGE_VAL ) + { + m_legendConfig->setTitle( QString( "Well Measurement: \n" ) + selectedMeasurementKinds[0] ); + m_legendConfig->setAutomaticRanges( minValue, maxValue, minValue, maxValue ); + m_legendConfig->setClosestToZeroValues( posClosestToZero, negClosestToZero, posClosestToZero, negClosestToZero ); + return true; + } + } + + return false; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellMeasurementInViewCollection::updateLegendRangesTextAndVisibility( RiuViewer* nativeOrOverrideViewer, + bool isUsingOverrideViewer ) +{ + bool addToViewer = updateLegendData(); + if ( addToViewer ) + { + nativeOrOverrideViewer->addColorLegendToBottomLeftCorner( m_legendConfig->titledOverlayFrame(), + isUsingOverrideViewer ); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QList + RimWellMeasurementInViewCollection::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions, + bool* useOptionsOnly ) +{ + QList options; + if ( fieldNeedingOptions == &m_measurementKinds ) + { + RimWellPathCollection* wellPathCollection = RimTools::wellPathCollection(); + if ( wellPathCollection ) + { + std::vector measurements = wellPathCollection->measurementCollection()->measurements(); + + std::set measurementKindsInData; + for ( auto measurement : measurements ) + { + measurementKindsInData.insert( measurement->kind() ); + } + + for ( auto measurementKind : measurementKindsInData ) + { + options.push_back( caf::PdmOptionItemInfo( measurementKind, measurementKind ) ); + } + } + } + + return options; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RimWellMeasurementInViewCollection::measurementKinds() const +{ + return m_measurementKinds; +} diff --git a/ApplicationCode/ProjectDataModel/RimWellMeasurementInViewCollection.h b/ApplicationCode/ProjectDataModel/RimWellMeasurementInViewCollection.h new file mode 100644 index 0000000000..6468d5f6f0 --- /dev/null +++ b/ApplicationCode/ProjectDataModel/RimWellMeasurementInViewCollection.h @@ -0,0 +1,60 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2019- Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "RimCheckableNamedObject.h" + +#include "cafPdmChildArrayField.h" +#include "cafPdmChildField.h" +#include "cafPdmField.h" +#include "cafPdmObject.h" +#include "cafPdmProxyValueField.h" + +#include + +class RimWellMeasurement; +class RimRegularLegendConfig; +class RiuViewer; + +class RimWellMeasurementInViewCollection : public RimCheckableNamedObject +{ + CAF_PDM_HEADER_INIT; + +public: + RimWellMeasurementInViewCollection(); + ~RimWellMeasurementInViewCollection() override; + + RimRegularLegendConfig* legendConfig(); + std::vector measurementKinds() const; + + void updateLegendRangesTextAndVisibility( RiuViewer* nativeOrOverrideViewer, bool isUsingOverrideViewer ); + +protected: + void defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName = "" ) override; + void fieldChangedByUi( const caf::PdmFieldHandle* changedField, + const QVariant& oldValue, + const QVariant& newValue ) override; + QList calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions, + bool* useOptionsOnly ); + + bool updateLegendData(); + +private: + caf::PdmChildField m_legendConfig; + caf::PdmField> m_measurementKinds; +}; diff --git a/ApplicationCode/ProjectDataModel/RimWellPathCollection.cpp b/ApplicationCode/ProjectDataModel/RimWellPathCollection.cpp index cb845f1790..be165e56b8 100644 --- a/ApplicationCode/ProjectDataModel/RimWellPathCollection.cpp +++ b/ApplicationCode/ProjectDataModel/RimWellPathCollection.cpp @@ -38,6 +38,7 @@ #include "RimPerforationCollection.h" #include "RimProject.h" #include "RimWellLogFile.h" +#include "RimWellMeasurementCollection.h" #include "RimWellPath.h" #include "Riu3DMainWindowTools.h" @@ -103,9 +104,12 @@ RimWellPathCollection::RimWellPathCollection() CAF_PDM_InitField( &wellPathClipZDistance, "WellPathClipZDistance", 100, "Well Path Clipping Depth Distance", "", "", "" ); CAF_PDM_InitFieldNoDefault( &wellPaths, "WellPaths", "Well Paths", "", "", "" ); - wellPaths.uiCapability()->setUiHidden( true ); + CAF_PDM_InitFieldNoDefault( &m_wellMeasurements, "WellMeasurements", "Measurements", "", "", "" ); + m_wellMeasurements = new RimWellMeasurementCollection; + m_wellMeasurements->uiCapability()->setUiTreeHidden( true ); + m_wellPathImporter = new RifWellPathImporter; m_wellPathFormationsImporter = new RifWellPathFormationsImporter; m_mostRecentlyUpdatedWellPath = nullptr; @@ -221,7 +225,8 @@ std::vector RimWellPathCollection::addWellPaths( QStringList f QString s2 = f2.fileName(); if ( s1 == s2 ) { - // printf("Attempting to open well path JSON file that is already open:\n %s\n", (const char*) filePath.toLocal8Bit()); + // printf("Attempting to open well path JSON file that is already open:\n %s\n", (const char*) + // filePath.toLocal8Bit()); alreadyOpen = true; errorMessages->push_back( QString( "%1 is already loaded" ).arg( filePath ) ); break; @@ -654,3 +659,21 @@ RiaEclipseUnitTools::UnitSystemType RimWellPathCollection::findUnitSystemForWell } return RiaEclipseUnitTools::UNITS_UNKNOWN; } + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- + +RimWellMeasurementCollection* RimWellPathCollection::measurementCollection() +{ + return m_wellMeasurements; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- + +const RimWellMeasurementCollection* RimWellPathCollection::measurementCollection() const +{ + return m_wellMeasurements; +} diff --git a/ApplicationCode/ProjectDataModel/RimWellPathCollection.h b/ApplicationCode/ProjectDataModel/RimWellPathCollection.h index 4d406638a5..37e61ceb42 100644 --- a/ApplicationCode/ProjectDataModel/RimWellPathCollection.h +++ b/ApplicationCode/ProjectDataModel/RimWellPathCollection.h @@ -42,6 +42,7 @@ class RimProject; class RimWellLogFile; class RimWellPath; class RifWellPathFormationsImporter; +class RimWellMeasurementCollection; class QString; namespace cvf @@ -111,6 +112,9 @@ public: bool anyWellsContainingPerforationIntervals() const; size_t modelledWellPathCount() const; + RimWellMeasurementCollection* measurementCollection(); + const RimWellMeasurementCollection* measurementCollection() const; + protected: void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, @@ -125,7 +129,8 @@ private: RiaEclipseUnitTools::UnitSystemType findUnitSystemForWellPath( const RimWellPath* wellPath ); - RifWellPathImporter* m_wellPathImporter; - RifWellPathFormationsImporter* m_wellPathFormationsImporter; - caf::PdmPointer m_mostRecentlyUpdatedWellPath; + RifWellPathImporter* m_wellPathImporter; + RifWellPathFormationsImporter* m_wellPathFormationsImporter; + caf::PdmPointer m_mostRecentlyUpdatedWellPath; + caf::PdmChildField m_wellMeasurements; }; diff --git a/ApplicationCode/UnitTests/CMakeLists_files.cmake b/ApplicationCode/UnitTests/CMakeLists_files.cmake index e75e795f68..b645857343 100644 --- a/ApplicationCode/UnitTests/CMakeLists_files.cmake +++ b/ApplicationCode/UnitTests/CMakeLists_files.cmake @@ -61,6 +61,7 @@ ${CMAKE_CURRENT_LIST_DIR}/RifActiveCellsReader-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RifCsvDataTableFormatter-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RiaSummaryCurveAnalyzer-Test.cpp ${CMAKE_CURRENT_LIST_DIR}/RiaStdStringTools-Test.cpp +${CMAKE_CURRENT_LIST_DIR}/RifWellMeasurementReader-Test.cpp ) if (RESINSIGHT_ENABLE_GRPC) diff --git a/ApplicationCode/UnitTests/RifWellMeasurementReader-Test.cpp b/ApplicationCode/UnitTests/RifWellMeasurementReader-Test.cpp new file mode 100644 index 0000000000..5a4bfd9a1c --- /dev/null +++ b/ApplicationCode/UnitTests/RifWellMeasurementReader-Test.cpp @@ -0,0 +1,257 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2019- Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "gtest/gtest.h" + +#include "RifFileParseTools.h" +#include "RifWellMeasurementReader.h" + +#include +#include +#include + +#include + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RifWellMeasurementReaderTest, ReadCorrectInputFile ) +{ + QTemporaryFile file; + EXPECT_TRUE( file.open() ); + + { + QTextStream out( &file ); + out << "NO 1234/5-6, 1234.56, 2019-11-13, 99.9, XLOT, 3, Good test\n" + << "NO 4321/7-8, 4321.79, 2024-12-24, 88.8, DP, 1, Poor test\n"; + } + + QStringList filePaths; + filePaths.append( file.fileName() ); + + std::vector wellMeasurements; + RifWellMeasurementReader::readWellMeasurements( wellMeasurements, filePaths ); + + ASSERT_EQ( 2u, wellMeasurements.size() ); + + ASSERT_EQ( "NO 1234/5-6", wellMeasurements[0].wellName.toStdString() ); + ASSERT_EQ( "NO 4321/7-8", wellMeasurements[1].wellName.toStdString() ); + + ASSERT_DOUBLE_EQ( 1234.56, wellMeasurements[0].MD ); + ASSERT_DOUBLE_EQ( 4321.79, wellMeasurements[1].MD ); + + QDate date0( 2019, 11, 13 ); + ASSERT_EQ( date0.year(), wellMeasurements[0].date.year() ); + ASSERT_EQ( date0.month(), wellMeasurements[0].date.month() ); + ASSERT_EQ( date0.day(), wellMeasurements[0].date.day() ); + + QDate date1( 2024, 12, 24 ); + ASSERT_EQ( date1.year(), wellMeasurements[1].date.year() ); + ASSERT_EQ( date1.month(), wellMeasurements[1].date.month() ); + ASSERT_EQ( date1.day(), wellMeasurements[1].date.day() ); + + ASSERT_DOUBLE_EQ( 99.9, wellMeasurements[0].value ); + ASSERT_DOUBLE_EQ( 88.8, wellMeasurements[1].value ); + + ASSERT_EQ( "XLOT", wellMeasurements[0].kind.toStdString() ); + ASSERT_EQ( "DP", wellMeasurements[1].kind.toStdString() ); + + ASSERT_EQ( 3, wellMeasurements[0].quality ); + ASSERT_EQ( 1, wellMeasurements[1].quality ); + + ASSERT_EQ( "Good test", wellMeasurements[0].remark.toStdString() ); + ASSERT_EQ( "Poor test", wellMeasurements[1].remark.toStdString() ); +} + +//-------------------------------------------------------------------------------------------------- +/// Helper to check exception messages when reading invalid files +//-------------------------------------------------------------------------------------------------- +::testing::AssertionResult readingThrowsException( const QStringList& filePaths, const QString& expectedMessage ) +{ + std::vector wellMeasurements; + try + { + RifWellMeasurementReader::readWellMeasurements( wellMeasurements, filePaths ); + // No exception thrown: fail! + return ::testing::AssertionFailure() << "readWellMeasurements did not throw exception"; + } + catch ( FileParseException& error ) + { + // Should always have cleaned up the well measurements on failure + EXPECT_EQ( 0u, wellMeasurements.size() ); + // Check that we get the expected message + EXPECT_EQ( expectedMessage.toStdString(), error.message.toStdString() ); + return ::testing::AssertionSuccess(); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RifWellMeasurementReaderTest, ReadMissingFileThrows ) +{ + QStringList filePaths; + QString nonExistingFile( "this/is/a/file/which/does/not/exist.csv" ); + filePaths.append( nonExistingFile ); + ASSERT_TRUE( readingThrowsException( filePaths, QString( "Unable to open file: %1" ).arg( nonExistingFile ) ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RifWellMeasurementReaderTest, ReadShortLinesFileThrows ) +{ + QTemporaryFile file; + EXPECT_TRUE( file.open() ); + + { + QTextStream out( &file ); + out << "NO 1234/5-6, 1234.56\n" + << "NO 4321/7-8, 4321.79, 2024-12-24\n"; + } + + QStringList filePaths; + filePaths.append( file.fileName() ); + ASSERT_TRUE( readingThrowsException( filePaths, QString( "Incomplete data on line 1: %1" ).arg( file.fileName() ) ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RifWellMeasurementReaderTest, ReadEmptyWellNameThrows ) +{ + QTemporaryFile file; + EXPECT_TRUE( file.open() ); + + { + QTextStream out( &file ); + out << "NO 1234/5-6, 1234.56, 2019-11-13, 99.9, XLOT, 3, Good test\n"; + out << ", 1234.56, 2019-11-13, 99.9, XLOT, 3, Good test\n"; + } + + QStringList filePaths; + filePaths.append( file.fileName() ); + ASSERT_TRUE( + readingThrowsException( filePaths, + QString( "Unexpected empty 'Well Name' on line 2: %1" ).arg( file.fileName() ) ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RifWellMeasurementReaderTest, ReadInvalidMeasureDepthThrows ) +{ + QTemporaryFile file; + EXPECT_TRUE( file.open() ); + + { + QTextStream out( &file ); + out << "well 1, 1234.56, 2019-11-13, 99.9, XLOT, 3, Good test\n"; + out << "well 2, this is should be a number, 2019-11-13, 99.9, XLOT, 3, Good test\n"; + } + + QStringList filePaths; + filePaths.append( file.fileName() ); + ASSERT_TRUE( + readingThrowsException( filePaths, + QString( "Invalid number for 'Measured Depth' on line 2: %1" ).arg( file.fileName() ) ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RifWellMeasurementReaderTest, ReadInvalidQualityThrows ) +{ + QTemporaryFile file; + EXPECT_TRUE( file.open() ); + + { + QTextStream out( &file ); + out << "well 1, 1234.56, 2019-11-13, 99.9, XLOT, this is not an int, Good test\n"; + out << "well 2, 1234.56, 2019-11-13, 99.9, XLOT, 3, Good test\n"; + } + + QStringList filePaths; + filePaths.append( file.fileName() ); + ASSERT_TRUE( + readingThrowsException( filePaths, + QString( "Invalid number for 'Quality' on line 1: %1" ).arg( file.fileName() ) ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RifWellMeasurementReaderTest, ReadInvalidDateFormateThrows ) +{ + QTemporaryFile file; + EXPECT_TRUE( file.open() ); + + { + QTextStream out( &file ); + out << "well 1, 1234.56, 17th May 1814, 99.9, XLOT, 1, Good test\n"; + out << "well 2, 1234.56, 2019-11-13, 99.9, XLOT, 3, Good test\n"; + } + + QStringList filePaths; + filePaths.append( file.fileName() ); + ASSERT_TRUE( readingThrowsException( filePaths, + QString( "Invalid date format (must be ISO 8601) for 'Date' on line 1: %1" ) + .arg( file.fileName() ) ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +TEST( RifWellMeasurementReaderTest, CommentsAndEmptyLinesAreIgnored ) +{ + QTemporaryFile file; + EXPECT_TRUE( file.open() ); + + { + QTextStream out( &file ); + // Comment should be ignored + out << "# This is a comment.\n"; + out << "#This is also a comment.\n"; + // Should skip empty lines + out << "\n"; + out << "\t\n"; + out << " \n"; + // Then some data + out << "well 1, 1234.56, 2019-11-11, 199.9, XLOT, 1, Good test\n"; + // Comment in-between data should be ignored + out << "# One more comment in-between the data\n"; + out << "well 2, 2234.56, 2019-11-12, 299.9, XLOT, 2, Good test\n"; + // Empty line in-between data should be ignored + out << "\n"; + // Data with comment sign inside it is not ignored + out << "well #3, 3234.56, 2019-11-13, 399.9, XLOT, 3, Good test\n"; + // Trailing empty lines should be ignored + out << "\n\n\n"; + } + + QStringList filePaths; + filePaths.append( file.fileName() ); + std::vector wellMeasurements; + RifWellMeasurementReader::readWellMeasurements( wellMeasurements, filePaths ); + + ASSERT_EQ( 3u, wellMeasurements.size() ); + + ASSERT_EQ( "well 1", wellMeasurements[0].wellName.toStdString() ); + ASSERT_EQ( "well 2", wellMeasurements[1].wellName.toStdString() ); + ASSERT_EQ( "well #3", wellMeasurements[2].wellName.toStdString() ); +}