/* MDAL - Mesh Data Abstraction Library (MIT License) Copyright (C) 2022 Vincent Cloarec (vcloarec at gmail dot com) */ #include "mdal_h2i.hpp" #include #include #include #include #include "mdal_utils.hpp" #include "mdal_logger.hpp" #include "mdal_memory_data_model.hpp" #define DRIVER_NAME "H2I" MDAL::DriverH2i::DriverH2i(): Driver( DRIVER_NAME, "H2i Mesh File", "*.json", Capability::ReadMesh ) {} MDAL::DriverH2i *MDAL::DriverH2i::create() { return new DriverH2i(); } class GpkgDataset { public: GpkgDataset() = default; ~GpkgDataset() { if ( mHDataset ) GDALClose( mHDataset ); } bool open( std::string fileName ) { GDALAllRegister(); GDALDriverH hDriver = GDALGetDriverByName( "GPKG" ); if ( !hDriver ) throw MDAL::Error( MDAL_Status::Err_MissingDriver, "No GDAL GPKG driver found, unable to read H2i format" ); char **papszAllowedDrivers = nullptr; papszAllowedDrivers = CSLAddString( papszAllowedDrivers, "GPKG" ); mHDataset = GDALOpenEx( fileName.c_str(), GDAL_OF_VECTOR, papszAllowedDrivers, nullptr, nullptr ); CSLDestroy( papszAllowedDrivers ); return ( mHDataset != nullptr ); } bool hasLayer( std::string layerName ) { OGRLayerH hLayer; hLayer = GDALDatasetGetLayerByName( mHDataset, layerName.c_str() ); return hLayer != nullptr; } GDALDatasetH mHDataset = nullptr; }; bool MDAL::DriverH2i::canReadMesh( const std::string &uri ) { MetadataH2i metadata; if ( !parseJsonFile( uri, metadata ) ) return false; GpkgDataset gridDataset; if ( !gridDataset.open( metadata.dirPath + '/' + metadata.gridFile ) ) return false; if ( !gridDataset.hasLayer( metadata.gridlayer ) ) return false; return true; } std::string MDAL::DriverH2i::buildUri( const std::string &meshFile ) { MetadataH2i metadata; if ( !parseJsonFile( meshFile, metadata ) ) return std::string(); return MDAL::buildMeshUri( meshFile, metadata.meshName, name() ); } bool MDAL::DriverH2i::parseJsonFile( const std::string filePath, MetadataH2i &metadata ) { using Json = nlohmann::json; std::ifstream file = MDAL::openInputFile( filePath ); if ( !file.is_open() ) return false; std::stringstream stream; stream << file.rdbuf(); std::string jsonString = stream.str(); try { Json jsonMeta; jsonMeta = Json::parse( jsonString ); if ( !jsonMeta.contains( "name" ) || !jsonMeta.contains( "crs" ) || !jsonMeta.contains( "mesh" ) || !jsonMeta.contains( "timesteps" ) ) return false; metadata.meshName = jsonMeta["name"].get(); metadata.crs = jsonMeta["crs"].get(); metadata.gridFile = jsonMeta["mesh"]["nodes_mesh"]["file"].get(); metadata.gridlayer = jsonMeta["mesh"]["nodes_mesh"]["layer"].get(); metadata.referenceTime = jsonMeta["timesteps"]["default"]["start_datetime"].get(); metadata.timeStepFile = jsonMeta["timesteps"]["default"]["timesteps_file"].get(); for ( Json::iterator it = jsonMeta["results"].begin(); it != jsonMeta["results"].end(); ++it ) { MetadataH2iDataset metaDataset; metaDataset.layer = ( *it )["layer"]; metaDataset.file = ( *it )["result_file"]; metaDataset.type = ( *it )["type"]; metaDataset.units = ( *it )["units"]; metaDataset.topology_file = ( *it )["topology_file"]; metaDataset.isScalar = !( ( *it ).contains( "vector" ) && ( *it )["vector"] ); metadata.datasetGroups.push_back( metaDataset ); } metadata.metadataFilePath = filePath; metadata.dirPath = MDAL::dirName( filePath ); } catch ( Json::exception & ) { return false; } return true; } struct VertexFactory { VertexFactory( std::vector &vertices ): verticesRef( vertices ) {} size_t getVertex( double x, double y ) { auto it = createdVertexPosition.find( {x, y} ); if ( it == createdVertexPosition.end() ) { int vertIndex; vertIndex = verticesRef.size(); verticesRef.push_back( {x, y, 0} ); createdVertexPosition[ {x, y}] = vertIndex; return vertIndex; } else { return it->second; } } struct VertexPosition { double x = 0; double y = 0; bool operator<( const VertexPosition &other ) const { double epsX = ( this->x + other.x ) * std::numeric_limits::epsilon(); if ( other.x - this->x > epsX ) return true; if ( this->x - other.x > epsX ) return false; double epsY = ( this->y + other.y ) * std::numeric_limits::epsilon(); if ( other.y - this->y > epsY ) return true; return false; } }; std::vector &verticesRef; // this map stores created vertices considering the position store in VertexPosition struct std::map createdVertexPosition; }; std::unique_ptr MDAL::DriverH2i::createMeshFrame( const MDAL::DriverH2i::MetadataH2i &metadata ) { GpkgDataset gridDataset; const std::string gridFile = metadata.dirPath + '/' + metadata.gridFile; if ( !gridDataset.open( gridFile ) ) throw MDAL::Error( MDAL_Status::Err_FileNotFound, "Unable to open the grid file: " + gridFile ); OGRLayerH hLayer; hLayer = GDALDatasetGetLayerByName( gridDataset.mHDataset, metadata.gridlayer.c_str() ); if ( hLayer == nullptr ) throw MDAL::Error( MDAL_Status::Err_IncompatibleMesh, "Unable to find the gpkg layer containing the mesh: " + metadata.gridlayer ); OGREnvelope layerExtent; OGR_L_GetExtent( hLayer, &layerExtent, 1 ); OGRFeatureDefnH hFDefn = OGR_L_GetLayerDefn( hLayer ); int nodeFieldIndex = OGR_FD_GetFieldIndex( hFDefn, "number" ); if ( nodeFieldIndex == -1 ) throw MDAL::Error( MDAL_Status::Err_IncompatibleMesh, "Unable to find the field \"number\" in the gpkg layer: " + metadata.gridlayer ); std::vector vertices; int maxVerticesCount = 0; VertexFactory vertexFactory( vertices ); size_t facesCount = static_cast( OGR_L_GetFeatureCount( hLayer, 1 ) ); std::vector faces( facesCount ); OGRFeatureH hFeature; OGR_L_ResetReading( hLayer ); while ( ( hFeature = OGR_L_GetNextFeature( hLayer ) ) != nullptr ) { size_t faceIndex = static_cast( OGR_F_GetFieldAsInteger( hFeature, nodeFieldIndex ) - 1 ); if ( faceIndex >= facesCount ) throw MDAL::Error( MDAL_Status::Err_IncompatibleMesh, "Bad face indexing in the gpkg layer: " + metadata.gridlayer ); OGRGeometryH hGeometry = OGR_F_GetGeometryRef( hFeature ); if ( hGeometry != nullptr && wkbFlatten( OGR_G_GetGeometryType( hGeometry ) ) == wkbPolygon ) { OGRGeometryH exteriorRing = OGR_G_GetGeometryRef( hGeometry, 0 ); int pointCount = OGR_G_GetPointCount( exteriorRing ); Face face; for ( int i = 0; i < pointCount - 1; ++i ) { double x = OGR_G_GetX( exteriorRing, i ); double y = OGR_G_GetY( exteriorRing, i ); size_t vertexIndex = vertexFactory.getVertex( x, y ); face.push_back( vertexIndex ); } if ( MDAL::toInt( face.size() ) > maxVerticesCount ) maxVerticesCount = MDAL::toInt( face.size() ); faces[faceIndex] = face; } } std::unique_ptr mesh = std::make_unique( name(), maxVerticesCount, metadata.metadataFilePath ); mesh->setVertices( std::move( vertices ) ); mesh->setFaces( std::move( faces ) ); return mesh; } std::unique_ptr MDAL::DriverH2i::load( const std::string &meshFile, const std::string & ) { MDAL::Log::resetLastStatus(); MetadataH2i metadata; if ( !parseJsonFile( meshFile, metadata ) ) { MDAL::Log::error( MDAL_Status::Err_UnknownFormat, name(), meshFile + " could not be opened" ); return nullptr; } std::unique_ptr mesh = createMeshFrame( metadata ); mesh->setSourceCrs( metadata.crs ); // read time information DateTime referenceTime; std::vector timeSteps; parseTime( metadata, referenceTime, timeSteps ); // build the dataset groups for all temporal quantities. const std::vector &metaGroups = metadata.datasetGroups; for ( const MetadataH2iDataset &metadatasetGroup : metaGroups ) { std::shared_ptr group = std::make_shared( name(), mesh.get(), metadatasetGroup.file, metadatasetGroup.layer ); std::string datasetGroupFile = metadata.dirPath + '/' + metadatasetGroup.file; std::shared_ptr in = std::make_shared( datasetGroupFile, std::ifstream::binary ); if ( !in->is_open() ) continue; group->setReferenceTime( referenceTime ); group->setDataLocation( MDAL_DataLocation::DataOnFaces ); group->setMetadata( "units", metadatasetGroup.units ); group->setMetadata( "type", metadatasetGroup.type ); group->setIsScalar( metadatasetGroup.isScalar ); if ( metadatasetGroup.topology_file == "2d_nodes" ) { if ( metadatasetGroup.isScalar ) { for ( size_t datasetIndex = 0; datasetIndex < timeSteps.size(); ++datasetIndex ) { std::shared_ptr dataset = std::make_shared( group.get(), in, datasetIndex ); dataset->setStatistics( MDAL::calculateStatistics( dataset ) ); group->datasets.push_back( dataset ); dataset->clear(); // Lazy loading, so we clear the loaded data during statistic calculation dataset->setTime( timeSteps.at( datasetIndex ) ); } } else { for ( size_t datasetIndex = 0; datasetIndex < timeSteps.size(); ++datasetIndex ) { std::shared_ptr dataset = std::make_shared( group.get(), in, datasetIndex ); dataset->setStatistics( MDAL::calculateStatistics( dataset ) ); group->datasets.push_back( dataset ); dataset->clear(); dataset->setTime( timeSteps.at( datasetIndex ) ); } } group->setStatistics( MDAL::calculateStatistics( group ) ); mesh->datasetGroups.push_back( group ); } } return mesh; } void MDAL::DriverH2i::parseTime( const MDAL::DriverH2i::MetadataH2i &metadata, MDAL::DateTime &referenceTime, std::vector &timeSteps ) { referenceTime = DateTime( metadata.referenceTime ); const std::string timeFilePath = metadata.dirPath + '/' + metadata.timeStepFile; std::ifstream timeFile = MDAL::openInputFile( timeFilePath ); if ( !timeFile.is_open() ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Could not open file " + timeFilePath ); timeSteps.clear(); std::string line; while ( std::getline( timeFile, line ) ) { const std::vector lineElements = split( line, ' ' ); if ( lineElements.size() != 2 ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "File format not recognized: " + timeFilePath ); timeSteps.emplace_back( toDouble( lineElements.at( 1 ) ), RelativeTimestamp::seconds ); } } MDAL::DatasetH2iScalar::DatasetH2iScalar( MDAL::DatasetGroup *grp, std::shared_ptr in, size_t datasetIndex ) : DatasetH2i( grp, in, datasetIndex ) {} size_t MDAL::DatasetH2iScalar::scalarData( size_t indexStart, size_t count, double *buffer ) { if ( !mDataLoaded ) loadData(); size_t nValues = valuesCount(); if ( ( count < 1 ) || ( indexStart >= nValues ) ) return 0; size_t copyValues = std::min( nValues - indexStart, count ); memcpy( buffer, mValues.data() + indexStart, copyValues * sizeof( double ) ); return copyValues; } MDAL::DatasetH2i::DatasetH2i( MDAL::DatasetGroup *grp, std::shared_ptr in, size_t datasetIndex ) : Dataset2D( grp ) , mIn( in ) , mDatasetIndex( datasetIndex ) {} void MDAL::DatasetH2i::clear() { mValues.clear(); mValues.shrink_to_fit(); mDataLoaded = false; } void MDAL::DatasetH2iScalar::loadData() { mIn->seekg( beginingInFile() ); int datasetSize = 0; bool changeEndianness = false; MDAL::readValue( datasetSize, *mIn, changeEndianness ); if ( datasetSize != MDAL::toInt( valuesCount() *sizeof( double ) ) ) { changeEndianness = true; mIn->seekg( beginingInFile() ); MDAL::readValue( datasetSize, *mIn, changeEndianness ); if ( datasetSize != MDAL::toInt( valuesCount() *sizeof( double ) ) ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "File format not recognized: " + group()->uri() ); } mValues.resize( valuesCount() ); for ( size_t i = 0; i < valuesCount(); ++i ) { if ( !readValue( mValues[i], *mIn, changeEndianness ) ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Error when reading file: " + group()->uri() ); } mDataLoaded = true; } std::streampos MDAL::DatasetH2iScalar::beginingInFile() const { return ( sizeof( int ) * 2 + sizeof( double ) * valuesCount() ) * mDatasetIndex; } MDAL::DatasetH2iVector::DatasetH2iVector( MDAL::DatasetGroup *grp, std::shared_ptr in, size_t datasetIndex ) : DatasetH2i( grp, in, datasetIndex ) {} size_t MDAL::DatasetH2iVector::vectorData( size_t indexStart, size_t count, double *buffer ) { if ( !mDataLoaded ) loadData(); size_t nValues = mValues.size() / 2; if ( ( count < 1 ) || ( indexStart >= nValues ) ) return 0; size_t copyValues = 2 * std::min( nValues - indexStart, count ); memcpy( buffer, &mValues.data()[+indexStart * 2], copyValues * sizeof( double ) ); return copyValues / 2; } void MDAL::DatasetH2iVector::loadData() { mIn->seekg( beginingInFile() ); int datasetSize = 0; bool changeEndianness = false; // First, magnitude values MDAL::readValue( datasetSize, *mIn, changeEndianness ); if ( datasetSize != MDAL::toInt( valuesCount() *sizeof( double ) ) ) { changeEndianness = true; mIn->seekg( beginingInFile() ); MDAL::readValue( datasetSize, *mIn, changeEndianness ); if ( datasetSize != MDAL::toInt( 2 * valuesCount() *sizeof( double ) ) ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "File format not recognized: " + group()->uri() ); } mValues = std::vector( valuesCount() * 2, 0 ); for ( size_t i = 0; i < valuesCount() ; ++i ) { double magnitude = 0; if ( !readValue( magnitude, *mIn, changeEndianness ) ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Error when reading file: " + group()->uri() ); mValues[2 * i] = magnitude; } MDAL::readValue( datasetSize, *mIn, changeEndianness );//last record is again the data count // Now, direction values MDAL::readValue( datasetSize, *mIn, changeEndianness ); if ( datasetSize != MDAL::toInt( valuesCount() *sizeof( double ) ) ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "File format not recognized: " + group()->uri() ); for ( size_t i = 0; i < valuesCount() ; ++i ) { double direction = 0; if ( !readValue( direction, *mIn, changeEndianness ) ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Error when reading file: " + group()->uri() ); mValues[2 * i + 1] = mValues[2 * i] * sin( -direction ); mValues[2 * i] = mValues[2 * i] * cos( -direction ); } mDataLoaded = true; } std::streampos MDAL::DatasetH2iVector::beginingInFile() const { return ( sizeof( int ) * 4 + sizeof( double ) * valuesCount() * 2 ) * mDatasetIndex; }