///////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2017 - Statoil ASA // // ResInsight is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. // // See the GNU General Public License at // for more details. // ///////////////////////////////////////////////////////////////////////////////// #include "RifStimPlanXmlReader.h" #include "RiaDefines.h" #include "RiaEclipseUnitTools.h" #include "RiaFractureDefines.h" #include "RiaLogging.h" #include "RiaTextStringTools.h" #include "RigStimPlanFractureDefinition.h" #include #include #include // Needed for HUGE_VAL on Linux //-------------------------------------------------------------------------------------------------- /// Internal functions //-------------------------------------------------------------------------------------------------- bool hasNegativeValues( std::vector xs ); RigStimPlanFractureDefinition::Orientation mapTextToOrientation( const QString text ); //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- cvf::ref RifStimPlanXmlReader::readStimPlanXMLFile( const QString& stimPlanFileName, double conductivityScalingFactor, MirrorMode mirrorMode, RiaDefines::EclipseUnitSystem requiredUnit, QString* errorMessage ) { RiaLogging::info( QString( "Starting to open StimPlan XML file: '%1'" ).arg( stimPlanFileName ) ); cvf::ref stimPlanFileData = new RigStimPlanFractureDefinition; { QFile dataFile( stimPlanFileName ); if ( !dataFile.open( QFile::ReadOnly ) ) { if ( errorMessage ) ( *errorMessage ) += "Could not open the File: " + ( stimPlanFileName ) + "\n"; return nullptr; } QXmlStreamReader xmlStream; xmlStream.setDevice( &dataFile ); xmlStream.readNext(); readStimplanGridAndTimesteps( xmlStream, stimPlanFileData.p(), mirrorMode, requiredUnit ); caf::AppEnum unitSystem = stimPlanFileData->unitSet(); if ( unitSystem != RiaDefines::EclipseUnitSystem::UNITS_UNKNOWN ) RiaLogging::info( QString( "Setting unit system for StimPlan fracture template %1 to %2" ).arg( stimPlanFileName ).arg( unitSystem.uiText() ) ); else RiaLogging::error( QString( "Found invalid units for %1. Unit system not set." ).arg( stimPlanFileName ) ); if ( xmlStream.hasError() ) { RiaLogging::error( QString( "Failed to parse file '%1'" ).arg( dataFile.fileName() ) ); RiaLogging::error( xmlStream.errorString() ); } dataFile.close(); } size_t numberOfYValues = stimPlanFileData->yCount(); RiaLogging::debug( QString( "Grid size X: %1, Y: %2" ).arg( QString::number( stimPlanFileData->xCount() ), QString::number( numberOfYValues ) ) ); size_t numberOfTimeSteps = stimPlanFileData->timeSteps().size(); RiaLogging::debug( QString( "Number of time-steps: %1" ).arg( numberOfTimeSteps ) ); // Start reading from top: QFile dataFile( stimPlanFileName ); if ( !dataFile.open( QFile::ReadOnly ) ) { if ( errorMessage ) ( *errorMessage ) += "Could not open the File: " + ( stimPlanFileName ) + "\n"; return nullptr; } QXmlStreamReader xmlStream2; xmlStream2.setDevice( &dataFile ); QString parameter; QString unit; RiaLogging::info( QString( "Properties available in file:" ) ); int propertiesElementCount = 0; while ( !xmlStream2.atEnd() && propertiesElementCount < 2 ) { xmlStream2.readNext(); if ( xmlStream2.isStartElement() ) { if ( isTextEqual( xmlStream2.name(), "properties" ) ) { propertiesElementCount++; } else if ( isTextEqual( xmlStream2.name(), "property" ) ) { unit = getAttributeValueString( xmlStream2, "uom" ); parameter = getAttributeValueString( xmlStream2, "name" ); RiaLogging::info( QString( "%1 [%2]" ).arg( parameter, unit ) ); } else if ( isTextEqual( xmlStream2.name(), "time" ) ) { double timeStepValue = getAttributeValueDouble( xmlStream2, "value" ); std::vector> propertyValuesAtTimestep = stimPlanFileData->generateDataLayoutFromFileDataLayout( getAllDepthDataAtTimeStep( xmlStream2 ) ); bool valuesOK = stimPlanFileData->numberOfParameterValuesOK( propertyValuesAtTimestep ); if ( !valuesOK ) { RiaLogging::error( QString( "Inconsistency detected in reading XML file: '%1'" ).arg( dataFile.fileName() ) ); return nullptr; } if ( parameter.contains( RiaDefines::conductivityResultName(), Qt::CaseInsensitive ) ) { // Scale all parameters containing conductivity for ( auto& dataAtDepth : propertyValuesAtTimestep ) { for ( auto& dataValue : dataAtDepth ) { dataValue *= conductivityScalingFactor; } } } stimPlanFileData->setDataAtTimeValue( parameter, unit, propertyValuesAtTimestep, timeStepValue ); } } } dataFile.close(); if ( xmlStream2.hasError() ) { RiaLogging::error( QString( "Failed to parse file: '%1'" ).arg( dataFile.fileName() ) ); RiaLogging::error( xmlStream2.errorString() ); } else if ( dataFile.error() != QFile::NoError ) { RiaLogging::error( QString( "Cannot read file: '%1'" ).arg( dataFile.fileName() ) ); RiaLogging::error( dataFile.errorString() ); } else { RiaLogging::info( QString( "Successfully read XML file: '%1'" ).arg( stimPlanFileName ) ); } return stimPlanFileData; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RifStimPlanXmlReader::readStimplanGridAndTimesteps( QXmlStreamReader& xmlStream, RigStimPlanFractureDefinition* stimPlanFileData, MirrorMode mirrorMode, RiaDefines::EclipseUnitSystem requiredUnit ) { size_t startNegValuesYs = 0; xmlStream.readNext(); double tvdToTopPerf = HUGE_VAL; double tvdToBotPerf = HUGE_VAL; double mdToTopPerf = HUGE_VAL; double mdToBotPerf = HUGE_VAL; double formationDip = HUGE_VAL; RigStimPlanFractureDefinition::Orientation orientation = RigStimPlanFractureDefinition::Orientation::UNDEFINED; int gridSectionCount = 0; // First, read time steps and grid to establish data structures for putting data into later. while ( !xmlStream.atEnd() && gridSectionCount < 2 ) { xmlStream.readNext(); if ( xmlStream.isStartElement() ) { RiaDefines::EclipseUnitSystem destinationUnit = requiredUnit; if ( isTextEqual( xmlStream.name(), "grid" ) ) { // Support for one grid per file if ( gridSectionCount < 1 ) { QString gridunit = getAttributeValueString( xmlStream, "uom" ); if ( gridunit.compare( "m", Qt::CaseInsensitive ) == 0 ) stimPlanFileData->m_unitSet = RiaDefines::EclipseUnitSystem::UNITS_METRIC; else if ( gridunit.compare( "ft", Qt::CaseInsensitive ) == 0 ) stimPlanFileData->m_unitSet = RiaDefines::EclipseUnitSystem::UNITS_FIELD; else stimPlanFileData->m_unitSet = RiaDefines::EclipseUnitSystem::UNITS_UNKNOWN; if ( destinationUnit == RiaDefines::EclipseUnitSystem::UNITS_UNKNOWN ) { // Use file unit set if requested unit is unknown destinationUnit = stimPlanFileData->m_unitSet; } double tvdToTopPerfFt = getAttributeValueDouble( xmlStream, "TVDToTopPerfFt" ); double tvdToBotPerfFt = getAttributeValueDouble( xmlStream, "TVDToBottomPerfFt" ); tvdToTopPerf = RifStimPlanXmlReader::valueInRequiredUnitSystem( RiaDefines::EclipseUnitSystem::UNITS_FIELD, destinationUnit, tvdToTopPerfFt ); tvdToBotPerf = RifStimPlanXmlReader::valueInRequiredUnitSystem( RiaDefines::EclipseUnitSystem::UNITS_FIELD, destinationUnit, tvdToBotPerfFt ); } gridSectionCount++; } else if ( isTextEqual( xmlStream.name(), "perf" ) ) { QString perfUnit = getAttributeValueString( xmlStream, "uom" ); QString fracName = getAttributeValueString( xmlStream, "frac" ); } else if ( isTextEqual( xmlStream.name(), "topTVD" ) ) { auto valText = xmlStream.readElementText(); tvdToTopPerf = valText.toDouble(); } else if ( isTextEqual( xmlStream.name(), "bottomTVD" ) ) { auto valText = xmlStream.readElementText(); tvdToBotPerf = valText.toDouble(); } else if ( isTextEqual( xmlStream.name(), "topMD" ) ) { auto valText = xmlStream.readElementText(); mdToTopPerf = valText.toDouble(); } else if ( isTextEqual( xmlStream.name(), "bottomMD" ) ) { auto valText = xmlStream.readElementText(); mdToBotPerf = valText.toDouble(); } else if ( isTextEqual( xmlStream.name(), "FmDip" ) ) { auto valText = xmlStream.readElementText(); formationDip = valText.toDouble(); } else if ( isTextEqual( xmlStream.name(), "orientation" ) ) { auto valText = xmlStream.readElementText(); orientation = mapTextToOrientation( valText.trimmed() ); } else if ( isTextEqual( xmlStream.name(), "xs" ) ) { std::vector gridValuesXs; { size_t dummy; std::vector gridValues; getGriddingValues( xmlStream, gridValues, dummy ); gridValuesXs = RifStimPlanXmlReader::valuesInRequiredUnitSystem( stimPlanFileData->m_unitSet, destinationUnit, gridValues ); } stimPlanFileData->m_fileXs = gridValuesXs; stimPlanFileData->generateXsFromFileXs( mirrorMode == MirrorMode::MIRROR_AUTO ? !hasNegativeValues( gridValuesXs ) : (bool)mirrorMode ); } else if ( xmlStream.name() == "ys" ) { std::vector gridValuesYs; { std::vector gridValues; getGriddingValues( xmlStream, gridValues, startNegValuesYs ); gridValuesYs = RifStimPlanXmlReader::valuesInRequiredUnitSystem( stimPlanFileData->m_unitSet, destinationUnit, gridValues ); } // Reorder and change sign std::vector ys; for ( double y : gridValuesYs ) { ys.insert( ys.begin(), -y ); } stimPlanFileData->m_Ys = ys; } else if ( xmlStream.name() == "time" ) { double timeStepValue = getAttributeValueDouble( xmlStream, "value" ); stimPlanFileData->addTimeStep( timeStepValue ); } } } if ( tvdToTopPerf != HUGE_VAL ) { stimPlanFileData->setTvdToTopPerf( tvdToTopPerf ); } if ( tvdToBotPerf != HUGE_VAL ) { stimPlanFileData->setTvdToBottomPerf( tvdToBotPerf ); } if ( mdToTopPerf != HUGE_VAL ) { stimPlanFileData->setMdToTopPerf( mdToTopPerf ); } if ( mdToBotPerf != HUGE_VAL ) { stimPlanFileData->setMdToBottomPerf( mdToBotPerf ); } if ( formationDip != HUGE_VAL ) { stimPlanFileData->setFormationDip( formationDip ); } if ( orientation != RigStimPlanFractureDefinition::Orientation::UNDEFINED ) { stimPlanFileData->setOrientation( orientation ); } if ( startNegValuesYs > 0 ) { RiaLogging::error( QString( "Negative depth values detected in XML file" ) ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector> RifStimPlanXmlReader::getAllDepthDataAtTimeStep( QXmlStreamReader& xmlStream ) { std::vector> propertyValuesAtTimestep; while ( !( xmlStream.isEndElement() && isTextEqual( xmlStream.name(), "time" ) ) ) { xmlStream.readNext(); if ( isTextEqual( xmlStream.name(), "depth" ) ) { xmlStream.readElementText().toDouble(); std::vector propertyValuesAtDepth; xmlStream.readNext(); // read end depth token xmlStream.readNext(); // read cdata section with values QString depthDataStr; if ( xmlStream.isCDATA() ) { depthDataStr = xmlStream.text().toString(); } else { QString gridValuesString = xmlStream.readElementText().replace( '\n', ' ' ); gridValuesString = gridValuesString.replace( '[', ' ' ).replace( ']', ' ' ); depthDataStr = gridValuesString; } QStringList splitted = depthDataStr.split( ' ' ); for ( int i = 0; i < splitted.size(); i++ ) { QString value = splitted[i]; if ( value != "" ) { propertyValuesAtDepth.push_back( value.toDouble() ); } } propertyValuesAtTimestep.push_back( propertyValuesAtDepth ); } } return propertyValuesAtTimestep; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RifStimPlanXmlReader::valuesInRequiredUnitSystem( RiaDefines::EclipseUnitSystem sourceUnit, RiaDefines::EclipseUnitSystem requiredUnit, const std::vector& values ) { if ( sourceUnit == RiaDefines::EclipseUnitSystem::UNITS_FIELD && requiredUnit == RiaDefines::EclipseUnitSystem::UNITS_METRIC ) { std::vector convertedValues; for ( const auto& valueInFeet : values ) { convertedValues.push_back( RiaEclipseUnitTools::feetToMeter( valueInFeet ) ); } return convertedValues; } else if ( sourceUnit == RiaDefines::EclipseUnitSystem::UNITS_METRIC && requiredUnit == RiaDefines::EclipseUnitSystem::UNITS_FIELD ) { std::vector convertedValues; for ( const auto& valueInMeter : values ) { convertedValues.push_back( RiaEclipseUnitTools::meterToFeet( valueInMeter ) ); } return convertedValues; } return values; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- double RifStimPlanXmlReader::valueInRequiredUnitSystem( RiaDefines::EclipseUnitSystem sourceUnit, RiaDefines::EclipseUnitSystem requiredUnit, double value ) { if ( sourceUnit == RiaDefines::EclipseUnitSystem::UNITS_FIELD && requiredUnit == RiaDefines::EclipseUnitSystem::UNITS_METRIC ) { return RiaEclipseUnitTools::feetToMeter( value ); } else if ( sourceUnit == RiaDefines::EclipseUnitSystem::UNITS_METRIC && requiredUnit == RiaDefines::EclipseUnitSystem::UNITS_FIELD ) { return RiaEclipseUnitTools::meterToFeet( value ); } return value; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool RifStimPlanXmlReader::isTextEqual( const QStringRef& text, const QString& compareText ) { return text.compare( compareText, Qt::CaseInsensitive ) == 0; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RifStimPlanXmlReader::getGriddingValues( QXmlStreamReader& xmlStream, std::vector& gridValues, size_t& startNegValues ) { QString gridValuesString = xmlStream.readElementText().replace( '\n', ' ' ); gridValuesString = gridValuesString.replace( '[', ' ' ).replace( ']', ' ' ); for ( const QString& value : RiaTextStringTools::splitSkipEmptyParts( gridValuesString ) ) { if ( value.size() > 0 ) { double gridValue = value.toDouble(); gridValues.push_back( gridValue ); if ( gridValue < -RigStimPlanFractureDefinition::THRESHOLD_VALUE ) startNegValues++; } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- double RifStimPlanXmlReader::getAttributeValueDouble( QXmlStreamReader& xmlStream, const QString& parameterName ) { double value = HUGE_VAL; for ( const QXmlStreamAttribute& attr : xmlStream.attributes() ) { if ( isTextEqual( attr.name(), parameterName ) ) { value = attr.value().toString().toDouble(); } } return value; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RifStimPlanXmlReader::getAttributeValueString( QXmlStreamReader& xmlStream, const QString& parameterName ) { QString parameterValue; for ( const QXmlStreamAttribute& attr : xmlStream.attributes() ) { if ( isTextEqual( attr.name(), parameterName ) ) { parameterValue = attr.value().toString(); } } return parameterValue; } //-------------------------------------------------------------------------------------------------- /// Internal function //-------------------------------------------------------------------------------------------------- bool hasNegativeValues( std::vector xs ) { return xs[0] < -RigStimPlanFractureDefinition::THRESHOLD_VALUE; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RigStimPlanFractureDefinition::Orientation mapTextToOrientation( const QString text ) { if ( text.compare( "transverse", Qt::CaseInsensitive ) == 0 ) { return RigStimPlanFractureDefinition::Orientation::TRANSVERSE; } else if ( text.compare( "longitudinal", Qt::CaseInsensitive ) == 0 ) { return RigStimPlanFractureDefinition::Orientation::LONGITUDINAL; } else { return RigStimPlanFractureDefinition::Orientation::UNDEFINED; } }