/* MDAL - Mesh Data Abstraction Library (MIT License) Copyright (C) 2016 Lutra Consulting Copyright (C) 2018 Peter Petrik (zilolv at gmail dot com) */ #include #include #include #include #include #include "mdal_sww.hpp" #include "mdal_utils.hpp" #include "mdal_logger.hpp" MDAL::DriverSWW::DriverSWW() : Driver( "SWW", "AnuGA", "*.sww", Capability::ReadMesh ) { } MDAL::DriverSWW *MDAL::DriverSWW::create() { return new DriverSWW(); } size_t MDAL::DriverSWW::getVertexCount( const NetCDFFile &ncFile ) const { int nPointsId; size_t res; ncFile.getDimension( "number_of_points", &res, &nPointsId ); return res; } bool MDAL::DriverSWW::canReadMesh( const std::string &uri ) { NetCDFFile ncFile; try { ncFile.openFile( uri ); getVertexCount( ncFile ); } catch ( MDAL_Status ) { return false; } catch ( MDAL::Error ) { return false; } return true; } std::vector MDAL::DriverSWW::readZCoords( const NetCDFFile &ncFile ) const { size_t nPoints = getVertexCount( ncFile ); std::vector pz; // newer sww files does have elevation array that is time-dependent if ( ncFile.hasArr( "z" ) ) { pz = ncFile.readDoubleArr( "z", nPoints ); } else if ( ncFile.hasArr( "elevation" ) ) { int zDims = 0; int zid; if ( nc_inq_varid( ncFile.handle(), "elevation", &zid ) == NC_NOERR && nc_inq_varndims( ncFile.handle(), zid, &zDims ) == NC_NOERR ) { if ( zDims == 1 ) { // just one elevation for all times, treat as z coord pz = ncFile.readDoubleArr( "elevation", nPoints ); } else { pz.resize( nPoints ); // fetching "elevation" data for the first timestep, // and threat it as z coord size_t start[2], count[2]; const ptrdiff_t stride[2] = {1, 1}; start[0] = 0; // t = 0 start[1] = 0; count[0] = 1; count[1] = nPoints; nc_get_vars_double( ncFile.handle(), zid, start, count, stride, pz.data() ); } } } return pz; } void MDAL::DriverSWW::addBedElevation( const NetCDFFile &ncFile, MDAL::MemoryMesh *mesh, const std::vector × ) const { if ( ncFile.hasArr( "elevation" ) ) { std::shared_ptr grp = readScalarGroup( ncFile, mesh, times, "Bed Elevation", "elevation" ); mesh->datasetGroups.push_back( grp ); } else { MDAL::addBedElevationDatasetGroup( mesh, mesh->vertices() ); } } MDAL::Vertices MDAL::DriverSWW::readVertices( const NetCDFFile &ncFile ) const { size_t nPoints = getVertexCount( ncFile ); // load mesh data std::vector px = ncFile.readDoubleArr( "x", nPoints ); std::vector py = ncFile.readDoubleArr( "y", nPoints ); std::vector pz = readZCoords( ncFile ); // we may need to apply a shift to the X,Y coordinates double xLLcorner = 0.0; if ( ncFile.hasAttrDouble( NC_GLOBAL, "xllcorner" ) ) xLLcorner = ncFile.getAttrDouble( NC_GLOBAL, "xllcorner" ); double yLLcorner = 0.0; if ( ncFile.hasAttrDouble( NC_GLOBAL, "yllcorner" ) ) yLLcorner = ncFile.getAttrDouble( NC_GLOBAL, "yllcorner" ); MDAL::Vertices vertices( nPoints ); Vertex *vertexPtr = vertices.data(); for ( size_t i = 0; i < nPoints; ++i, ++vertexPtr ) { vertexPtr->x = px[i] + xLLcorner ; vertexPtr->y = py[i] + yLLcorner ; if ( !pz.empty() ) // missing both "z" and "elevation" vertexPtr->z = pz[i]; } return vertices; } MDAL::Faces MDAL::DriverSWW::readFaces( const NetCDFFile &ncFile ) const { // get dimensions int nVolumesId, nVerticesId; size_t nVolumes, nVertices; ncFile.getDimension( "number_of_volumes", &nVolumes, &nVolumesId ); ncFile.getDimension( "number_of_vertices", &nVertices, &nVerticesId ); if ( nVertices != 3 ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Number of vertices is not allowed" ); std::vector pvolumes = ncFile.readIntArr( "volumes", nVertices * nVolumes ); MDAL::Faces faces( nVolumes ); for ( size_t i = 0; i < nVolumes; ++i ) { faces[i].resize( 3 ); faces[i][0] = static_cast( pvolumes[3 * i + 0] ); faces[i][1] = static_cast( pvolumes[3 * i + 1] ); faces[i][2] = static_cast( pvolumes[3 * i + 2] ); } return faces; } std::vector MDAL::DriverSWW::readTimes( const NetCDFFile &ncFile ) const { size_t nTimesteps; int nTimestepsId; ncFile.getDimension( "number_of_timesteps", &nTimesteps, &nTimestepsId ); std::vector times = ncFile.readDoubleArr( "time", nTimesteps ); return times; } void MDAL::DriverSWW::readDatasetGroups( const NetCDFFile &ncFile, MDAL::MemoryMesh *mesh, const std::vector × ) const { std::set parsedVariableNames; // already parsed arrays somewhere else parsedVariableNames.insert( "x" ); parsedVariableNames.insert( "y" ); parsedVariableNames.insert( "z" ); parsedVariableNames.insert( "volumes" ); parsedVariableNames.insert( "time" ); std::vector names = ncFile.readArrNames(); std::set namesSet( names.begin(), names.end() ); // Add bed elevation group parsedVariableNames.insert( "elevations" ); addBedElevation( ncFile, mesh, times ); for ( const std::string &name : names ) { // currently we do not support variables like elevation_c, friction_c, stage_c, xmomentum_c, ymomentum_c // which contain values per volume instead of per vertex if ( MDAL::endsWith( name, "_c" ) ) continue; // skip already parsed variables if ( parsedVariableNames.find( name ) == parsedVariableNames.cend() ) { std::string xName, yName, groupName( name ); bool isVector = parseGroupName( groupName, xName, yName ); std::shared_ptr grp; if ( isVector && ncFile.hasArr( xName ) && ncFile.hasArr( yName ) ) { // vector dataset group grp = readVectorGroup( ncFile, mesh, times, groupName, xName, yName ); parsedVariableNames.insert( xName ); parsedVariableNames.insert( yName ); } else { // scalar dataset group grp = readScalarGroup( ncFile, mesh, times, groupName, name ); parsedVariableNames.insert( name ); } if ( grp ) mesh->datasetGroups.push_back( grp ); } } } bool MDAL::DriverSWW::parseGroupName( std::string &groupName, std::string &xName, std::string &yName ) const { bool isVector = false; std::string baseName( groupName ); // X and Y variables if ( groupName.size() > 1 ) { if ( MDAL::startsWith( groupName, "x" ) ) { baseName = groupName.substr( 1, groupName.size() - 1 ); xName = groupName; yName = "y" + baseName; isVector = true; } else if ( MDAL::startsWith( groupName, "y" ) ) { baseName = groupName.substr( 1, groupName.size() - 1 ); xName = "x" + baseName; yName = groupName; isVector = true; } } // Maximums groupName = baseName; if ( MDAL::endsWith( baseName, "_range" ) ) { groupName = groupName.substr( 0, baseName.size() - 6 ) + "/Maximums"; } return isVector; } std::shared_ptr MDAL::DriverSWW::readScalarGroup( const NetCDFFile &ncFile, MDAL::MemoryMesh *mesh, const std::vector ×, const std::string groupName, const std::string arrName ) const { size_t nPoints = getVertexCount( ncFile ); std::shared_ptr mds; int varxid; if ( nc_inq_varid( ncFile.handle(), arrName.c_str(), &varxid ) == NC_NOERR ) { mds = std::make_shared ( name(), mesh, mFileName, groupName ); mds->setDataLocation( MDAL_DataLocation::DataOnVertices ); mds->setIsScalar( true ); int zDimsX = 0; if ( nc_inq_varndims( ncFile.handle(), varxid, &zDimsX ) != NC_NOERR ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Fail while reading scalar group: invalid variable id or bad ncid" ); if ( zDimsX == 1 ) { // TIME INDEPENDENT std::shared_ptr o = std::make_shared( mds.get() ); o->setTime( RelativeTimestamp() ); std::vector valuesX = ncFile.readDoubleArr( arrName, nPoints ); for ( size_t i = 0; i < nPoints; ++i ) { o->setScalarValue( i, valuesX[i] ); } o->setStatistics( MDAL::calculateStatistics( o ) ); mds->datasets.push_back( o ); } else { // TIME DEPENDENT for ( size_t t = 0; t < times.size(); ++t ) { std::shared_ptr mto = std::make_shared( mds.get() ); mto->setTime( static_cast( times[t] ), RelativeTimestamp::seconds ); // Time is always in seconds double *values = mto->values(); // fetching data for one timestep size_t start[2], count[2]; const ptrdiff_t stride[2] = {1, 1}; start[0] = t; start[1] = 0; count[0] = 1; count[1] = nPoints; nc_get_vars_double( ncFile.handle(), varxid, start, count, stride, values ); mto->setStatistics( MDAL::calculateStatistics( mto ) ); mds->datasets.push_back( mto ); } } mds->setStatistics( MDAL::calculateStatistics( mds ) ); } return mds; } std::shared_ptr MDAL::DriverSWW::readVectorGroup( const NetCDFFile &ncFile, MDAL::MemoryMesh *mesh, const std::vector ×, const std::string groupName, const std::string arrXName, const std::string arrYName ) const { size_t nPoints = getVertexCount( ncFile ); std::shared_ptr mds; int varxid, varyid; if ( nc_inq_varid( ncFile.handle(), arrXName.c_str(), &varxid ) == NC_NOERR && nc_inq_varid( ncFile.handle(), arrYName.c_str(), &varyid ) == NC_NOERR ) { mds = std::make_shared ( name(), mesh, mFileName, groupName ); mds->setDataLocation( MDAL_DataLocation::DataOnVertices ); mds->setIsScalar( false ); int zDimsX = 0; int zDimsY = 0; if ( nc_inq_varndims( ncFile.handle(), varxid, &zDimsX ) != NC_NOERR ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Fail while reading vector group: invalid variable id or bad ncid" ); if ( nc_inq_varndims( ncFile.handle(), varyid, &zDimsY ) != NC_NOERR ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Fail while reading vector group: invalid variable id or bad ncid" ); if ( zDimsX != zDimsY ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Fail while reading vector group: dimensions do not match" ); if ( zDimsX == 1 ) { // TIME INDEPENDENT std::shared_ptr o = std::make_shared( mds.get() ); o->setTime( 0.0 ); std::vector valuesX = ncFile.readDoubleArr( arrXName, nPoints ); std::vector valuesY = ncFile.readDoubleArr( arrYName, nPoints ); for ( size_t i = 0; i < nPoints; ++i ) { o->setVectorValue( i, valuesX[i], valuesY[i] ); } o->setStatistics( MDAL::calculateStatistics( o ) ); mds->datasets.push_back( o ); } else { std::vector valuesX( nPoints ), valuesY( nPoints ); // TIME DEPENDENT for ( size_t t = 0; t < times.size(); ++t ) { std::shared_ptr mto = std::make_shared( mds.get() ); mto->setTime( static_cast( times[t] ) / 3600. ); // fetching data for one timestep size_t start[2], count[2]; const ptrdiff_t stride[2] = {1, 1}; start[0] = t; start[1] = 0; count[0] = 1; count[1] = nPoints; nc_get_vars_double( ncFile.handle(), varxid, start, count, stride, valuesX.data() ); nc_get_vars_double( ncFile.handle(), varyid, start, count, stride, valuesY.data() ); for ( size_t i = 0; i < nPoints; ++i ) { mto->setVectorValue( i, static_cast( valuesX[i] ), static_cast( valuesY[i] ) ); } mto->setStatistics( MDAL::calculateStatistics( mto ) ); mds->datasets.push_back( mto ); } } mds->setStatistics( MDAL::calculateStatistics( mds ) ); } return mds; } std::unique_ptr MDAL::DriverSWW::load( const std::string &resultsFile, const std::string & ) { mFileName = resultsFile; MDAL::Log::resetLastStatus(); NetCDFFile ncFile; try { // Open file for reading ncFile.openFile( mFileName ); // Read mesh MDAL::Vertices vertices = readVertices( ncFile ); MDAL::Faces faces = readFaces( ncFile ); std::unique_ptr< MDAL::MemoryMesh > mesh( new MemoryMesh( name(), 3, // triangles mFileName ) ); mesh->setFaces( std::move( faces ) ); mesh->setVertices( std::move( vertices ) ); // Read times std::vector times = readTimes( ncFile ); // Create a dataset(s) readDatasetGroups( ncFile, mesh.get(), times ); // Success! return std::unique_ptr( mesh.release() ); } catch ( MDAL_Status err ) { MDAL::Log::error( err, "Error while loading file " + resultsFile ); return std::unique_ptr< MDAL::Mesh >(); } catch ( MDAL::Error err ) { MDAL::Log::error( err, name() ); return std::unique_ptr< MDAL::Mesh >(); } }