/* MDAL - Mesh Data Abstraction Library (MIT License) Copyright (C) 2019 Peter Petrik (zilolv at gmail dot com) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mdal_selafin.hpp" #include "mdal.h" #include "mdal_utils.hpp" #include MDAL::SerafinStreamReader::SerafinStreamReader() = default; void MDAL::SerafinStreamReader::initialize( const std::string &fileName ) { mFileName = fileName; if ( !MDAL::fileExists( mFileName ) ) { throw MDAL_Status::Err_FileNotFound; } mIn = std::ifstream( mFileName, std::ifstream::in | std::ifstream::binary ); if ( !mIn ) throw MDAL_Status::Err_FileNotFound; // Couldn't open the file // get length of file: mIn.seekg( 0, mIn.end ); mFileSize = mIn.tellg(); mIn.seekg( 0, mIn.beg ); mStreamInFloatPrecision = getStreamPrecision(); mIsNativeLittleEndian = MDAL::isNativeLittleEndian(); } bool MDAL::SerafinStreamReader::getStreamPrecision( ) { ignore_array_length( ); ignore( 72 ); std::string varType = read_string_without_length( 8 ); bool ret; if ( varType == "SERAFIN" ) { ret = true; } else if ( varType == "SERAFIND" ) { ret = false; } else { throw MDAL_Status::Err_UnknownFormat; } ignore_array_length( ); return ret; } std::string MDAL::SerafinStreamReader::read_string( size_t len ) { size_t length = read_sizet(); if ( length != len ) throw MDAL_Status::Err_UnknownFormat; std::string ret = read_string_without_length( len ); ignore_array_length(); return ret; } std::vector MDAL::SerafinStreamReader::read_double_arr( size_t len ) { size_t length = read_sizet(); if ( mStreamInFloatPrecision ) { if ( length != len * 4 ) throw MDAL_Status::Err_UnknownFormat; } else { if ( length != len * 8 ) throw MDAL_Status::Err_UnknownFormat; } std::vector ret( len ); for ( size_t i = 0; i < len; ++i ) { ret[i] = read_double(); } ignore_array_length(); return ret; } std::vector MDAL::SerafinStreamReader::read_int_arr( size_t len ) { size_t length = read_sizet(); if ( length != len * 4 ) throw MDAL_Status::Err_UnknownFormat; std::vector ret( len ); for ( size_t i = 0; i < len; ++i ) { ret[i] = read_int(); } ignore_array_length(); return ret; } std::vector MDAL::SerafinStreamReader::read_size_t_arr( size_t len ) { size_t length = read_sizet(); if ( length != len * 4 ) throw MDAL_Status::Err_UnknownFormat; std::vector ret( len ); for ( size_t i = 0; i < len; ++i ) { ret[i] = read_sizet(); } ignore_array_length(); return ret; } std::string MDAL::SerafinStreamReader::read_string_without_length( size_t len ) { std::vector ptr( len ); mIn.read( ptr.data(), static_cast( len ) ); if ( !mIn ) throw MDAL_Status::Err_UnknownFormat; size_t str_length = 0; for ( size_t i = len; i > 0; --i ) { if ( ptr[i - 1] != 0x20 ) { str_length = i; break; } } std::string ret( ptr.data(), str_length ); return ret; } double MDAL::SerafinStreamReader::read_double( ) { double ret; if ( mStreamInFloatPrecision ) { float ret_f; if ( !readValue( ret_f, mIn, mIsNativeLittleEndian ) ) throw MDAL_Status::Err_UnknownFormat; ret = static_cast( ret_f ); } else { if ( !readValue( ret, mIn, mIsNativeLittleEndian ) ) throw MDAL_Status::Err_UnknownFormat; } return ret; } int MDAL::SerafinStreamReader::read_int( ) { unsigned char data[4]; if ( mIn.read( reinterpret_cast< char * >( &data ), 4 ) ) if ( !mIn ) throw MDAL_Status::Err_UnknownFormat; if ( mIsNativeLittleEndian ) { std::reverse( reinterpret_cast< char * >( &data ), reinterpret_cast< char * >( &data ) + 4 ); } int var; memcpy( reinterpret_cast< char * >( &var ), reinterpret_cast< char * >( &data ), 4 ); return var; } size_t MDAL::SerafinStreamReader::read_sizet() { int var = read_int( ); return static_cast( var ); } size_t MDAL::SerafinStreamReader::remainingBytes() { return static_cast( mFileSize - mIn.tellg() ); } void MDAL::SerafinStreamReader::ignore( int len ) { mIn.ignore( len ); if ( !mIn ) throw MDAL_Status::Err_UnknownFormat; } void MDAL::SerafinStreamReader::ignore_array_length( ) { ignore( 4 ); } // ////////////////////////////// // DRIVER // ////////////////////////////// MDAL::DriverSelafin::DriverSelafin(): Driver( "SELAFIN", "Selafin File", "*.slf", Capability::ReadMesh ) { } MDAL::DriverSelafin *MDAL::DriverSelafin::create() { return new DriverSelafin(); } MDAL::DriverSelafin::~DriverSelafin() = default; void MDAL::DriverSelafin::parseFile( std::vector &var_names, double *xOrigin, double *yOrigin, size_t *nElem, size_t *nPoint, size_t *nPointsPerElem, std::vector &ikle, std::vector &x, std::vector &y, std::vector &data, DateTime &referenceTime ) { /* 1 record containing the title of the study (72 characters) and a 8 characters string indicating the type of format (SERAFIN or SERAFIND) */ mReader.initialize( mFileName ); /* 1 record containing the two integers NBV(1) and NBV(2) (number of linear and quadratic variables, NBV(2) with the value of 0 for Telemac, as quadratic values are not saved so far), numbered from 1 in docs */ std::vector nbv = mReader.read_size_t_arr( 2 ); /* NBV(1) records containing the names and units of each variab le (over 32 characters) */ for ( size_t i = 0; i < nbv[0]; ++i ) { var_names.push_back( mReader.read_string( 32 ) ); } /* 1 record containing the integers table IPARAM (10 integers, of which only the 6 are currently being used), numbered from 1 in docs - if IPARAM (3) != 0: the value corresponds to the x-coordinate of the origin of the mesh, - if IPARAM (4) != 0: the value corresponds to the y-coordinate of the origin of the mesh, - if IPARAM (7) != 0: the value corresponds to the number of planes on the vertical (3D computation), - if IPARAM (8) != 0: the value corresponds to the number of boundary points (in parallel), - if IPARAM (9) != 0: the value corresponds to the number of interface points (in parallel), - if IPARAM(8) or IPARAM(9) != 0: the array IPOBO below is replaced by the array KNOLG (total initial number of points). All the other numbers are local to the sub-domain, including IKLE */ std::vector params = mReader.read_int_arr( 10 ); *xOrigin = static_cast( params[2] ); *yOrigin = static_cast( params[3] ); if ( params[6] != 0 ) { // would need additional parsing throw MDAL_Status::Err_MissingDriver; } /* if IPARAM (10) = 1: a record containing the computation starting date */ if ( params[9] == 1 ) { std::vector datetime = mReader.read_int_arr( 6 ); referenceTime = DateTime( datetime[0], datetime[1], datetime[2], datetime[3], datetime[4], double( datetime[5] ) ); } /* 1 record containing the integers NELEM,NPOIN,NDP,1 (number of elements, number of points, number of points per element and the value 1) */ std::vector numbers = mReader.read_size_t_arr( 4 ); *nElem = numbers[0]; *nPoint = numbers[1]; *nPointsPerElem = numbers[2]; /* 1 record containing table IKLE (integer array of dimension (NDP,NELEM) which is the connectivity table. Attention: in TELEMAC-2D, the dimensions of this array are (NELEM,NDP)) */ ikle = mReader.read_size_t_arr( ( *nElem ) * ( *nPointsPerElem ) ); for ( size_t i = 0; i < ikle.size(); ++i ) { -- ikle[i]; //numbered from 1 } /* 1 record containing table IPOBO (integer array of dimension NPOIN); the value of one element is 0 for an internal point, and gives the numbering of boundary points for the others */ std::vector iPointBoundary = mReader.read_int_arr( *nPoint ); MDAL_UNUSED( iPointBoundary ) /* 1 record containing table X (real array of dimension NPOIN containing the abscissae of the points) */ x = mReader.read_double_arr( *nPoint ); /* 1 record containing table Y (real array of dimension NPOIN containing the abscissae of the points) */ y = mReader.read_double_arr( *nPoint ); /* Next, for each time step, the following are found: - 1 record containing time T (real), - NBV(1)+NBV(2) records containing the results tables for each variable at time */ data.resize( var_names.size() ); size_t nTimesteps = mReader.remainingBytes() / ( 12 + ( 4 + ( *nPoint ) * 4 + 4 ) * var_names.size() ); for ( size_t nT = 0; nT < nTimesteps; ++nT ) { std::vector times = mReader.read_double_arr( 1 ); double time = times[0]; for ( size_t i = 0; i < var_names.size(); ++i ) { timestep_map &datait = data[i]; std::vector datavals = mReader.read_double_arr( *nPoint ); datait[time] = datavals; } } } void MDAL::DriverSelafin::createMesh( double xOrigin, double yOrigin, size_t nElems, size_t nPoints, size_t nPointsPerElem, std::vector &ikle, std::vector &x, std::vector &y ) { Vertices nodes( nPoints ); Vertex *nodesPtr = nodes.data(); for ( size_t n = 0; n < nPoints; ++n, ++nodesPtr ) { nodesPtr->x = xOrigin + x[n]; nodesPtr->y = yOrigin + y[n]; } Faces elements( nElems ); for ( size_t e = 0; e < nElems; ++e ) { if ( nPointsPerElem != 3 ) { throw MDAL_Status::Err_IncompatibleMesh; //we can add it, but it is uncommon for this format } // elemPtr->setId(e); elements[e].resize( 3 ); for ( size_t p = 0; p < 3; p++ ) { size_t val = ikle[e * 3 + p]; if ( val > nPoints - 1 ) { elements[e][p] = 0; } else { elements[e][p] = val; } } } mMesh.reset( new MemoryMesh( "SELAFIN", nodes.size(), elements.size(), 3, //Triangles computeExtent( nodes ), mFileName ) ); mMesh->faces = elements; mMesh->vertices = nodes; } void MDAL::DriverSelafin::addData( const std::vector &var_names, const std::vector &data, size_t nPoints, const DateTime &referenceTime ) { for ( size_t nName = 0; nName < var_names.size(); ++nName ) { std::string var_name( var_names[nName] ); var_name = MDAL::toLower( MDAL::trim( var_name ) ); var_name = MDAL::replace( var_name, "/", "" ); // slash is represented as sub-dataset group but we do not have such subdatasets groups in Selafin bool is_vector = false; bool is_x = true; if ( MDAL::contains( var_name, "velocity u" ) || MDAL::contains( var_name, "along x" ) ) { is_vector = true; var_name = MDAL::replace( var_name, "velocity u", "velocity" ); var_name = MDAL::replace( var_name, "along x", "" ); } else if ( MDAL::contains( var_name, "velocity v" ) || MDAL::contains( var_name, "along y" ) ) { is_vector = true; is_x = false; var_name = MDAL::replace( var_name, "velocity v", "velocity" ); var_name = MDAL::replace( var_name, "along y", "" ); } if ( MDAL::contains( var_name, "vitesse u" ) || MDAL::contains( var_name, "suivant x" ) ) { is_vector = true; var_name = MDAL::replace( var_name, "vitesse u", "vitesse" ); var_name = MDAL::replace( var_name, "suivant x", "" ); } else if ( MDAL::contains( var_name, "vitesse v" ) || MDAL::contains( var_name, "suivant y" ) ) { is_vector = true; is_x = false; var_name = MDAL::replace( var_name, "vitesse v", "vitesse" ); var_name = MDAL::replace( var_name, "suivant y", "" ); } std::shared_ptr group = mMesh->group( var_name ); if ( !group ) { group = std::make_shared< DatasetGroup >( mMesh->driverName(), mMesh.get(), mMesh->uri(), var_name ); group->setDataLocation( MDAL_DataLocation::DataOnVertices2D ); group->setIsScalar( !is_vector ); mMesh->datasetGroups.push_back( group ); } group->setReferenceTime( referenceTime ); size_t i = 0; for ( timestep_map::const_iterator it = data[nName].begin(); it != data[nName].end(); ++it, ++i ) { std::shared_ptr dataset; if ( group->datasets.size() > i ) { dataset = std::dynamic_pointer_cast( group->datasets[i] ); } else { dataset = std::make_shared< MemoryDataset2D >( group.get(), true ); // see https://github.com/lutraconsulting/MDAL/issues/185 dataset->setTime( it->first, RelativeTimestamp::seconds ); group->datasets.push_back( dataset ); } for ( size_t nP = 0; nP < nPoints; nP++ ) { double val = it->second.at( nP ); if ( MDAL::equals( val, 0 ) ) { val = std::numeric_limits::quiet_NaN(); } if ( is_vector ) { if ( is_x ) { dataset->setValueX( nP, val ); } else { dataset->setValueY( nP, val ); } } else { dataset->setScalarValue( nP, val ); } } } } // now activate faces and calculate statistics for ( auto group : mMesh->datasetGroups ) { for ( auto dataset : group->datasets ) { std::shared_ptr dts = std::dynamic_pointer_cast( dataset ); if ( dts ) dts->activateFaces( mMesh.get() ); MDAL::Statistics stats = MDAL::calculateStatistics( dataset ); dataset->setStatistics( stats ); } MDAL::Statistics stats = MDAL::calculateStatistics( group ); group->setStatistics( stats ); } } bool MDAL::DriverSelafin::canReadMesh( const std::string &uri ) { if ( !MDAL::fileExists( uri ) ) return false; std::ifstream in( uri, std::ifstream::in | std::ifstream::binary ); if ( !in ) return false; // The first four bytes of the file should contain the values (in hexadecimal): 00 00 00 50. // This actually indicates the start of a string of length 80 in the file. // At position 84 in the file, the eight next bytes should read (in hexadecimal): 00 00 00 50 00 00 00 04. unsigned char data[ 92 ]; in.read( reinterpret_cast< char * >( &data ), 92 ); if ( !in ) return false; if ( data[0] != 0 || data[1] != 0 || data[2] != 0 || data[3] != 0x50 ) return false; if ( data[84 + 0] != 0 || data[84 + 1] != 0 || data[84 + 2] != 0 || data[84 + 3] != 0x50 || data[84 + 4] != 0 || data[84 + 5] != 0 || data[84 + 6] != 0 || data[84 + 7] != 8 ) return false; return true; } std::unique_ptr MDAL::DriverSelafin::load( const std::string &meshFile, MDAL_Status *status ) { if ( status ) *status = MDAL_Status::None; mFileName = meshFile; mMesh.reset(); std::vector var_names; double xOrigin; double yOrigin; size_t nElems; size_t nPoints; size_t nPointsPerElem; std::vector ikle; std::vector x; std::vector y; std::vector data; DateTime referenceTime; try { parseFile( var_names, &xOrigin, &yOrigin, &nElems, &nPoints, &nPointsPerElem, ikle, x, y, data, referenceTime ); createMesh( xOrigin, yOrigin, nElems, nPoints, nPointsPerElem, ikle, x, y ); addData( var_names, data, nPoints, referenceTime ); } catch ( MDAL_Status error ) { if ( status ) *status = ( error ); mMesh.reset(); } return std::unique_ptr( mMesh.release() ); }