diff --git a/docs/.azure-pipelines.yml b/docs/.azure-pipelines.yml index e38b8874..61a6f136 100644 --- a/docs/.azure-pipelines.yml +++ b/docs/.azure-pipelines.yml @@ -20,7 +20,7 @@ variables: jobs: - job: 'Documentation' pool: - vmImage: 'ubuntu-16.04' + vmImage: 'ubuntu-22.04' container: image: osgeo/proj-docs options: --privileged diff --git a/docs/source/_extensions/driverproperties.py b/docs/source/_extensions/driverproperties.py index 555955f8..4831bf4f 100644 --- a/docs/source/_extensions/driverproperties.py +++ b/docs/source/_extensions/driverproperties.py @@ -70,6 +70,7 @@ class shortname(nodes.Admonition, nodes.Element): def visit_shortname_node(self, node): self.body.append(self.starttag( node, 'div', CLASS=('admonition shortname'))) + self.context.append('\n') class built_in_by_default(nodes.Admonition, nodes.Element): pass @@ -77,6 +78,7 @@ class built_in_by_default(nodes.Admonition, nodes.Element): def visit_built_in_by_default_node(self, node): self.body.append(self.starttag( node, 'div', CLASS=('admonition built_in_by_default'))) + self.context.append('\n') class build_dependencies(nodes.Admonition, nodes.Element): pass @@ -84,6 +86,7 @@ class build_dependencies(nodes.Admonition, nodes.Element): def visit_build_dependencies_node(self, node): self.body.append(self.starttag( node, 'div', CLASS=('admonition build_dependencies'))) + self.context.append('\n') class supports_create(nodes.Admonition, nodes.Element): pass @@ -91,6 +94,7 @@ class supports_create(nodes.Admonition, nodes.Element): def visit_supports_create_node(self, node): self.body.append(self.starttag( node, 'div', CLASS=('admonition supports_create'))) + self.context.append('\n') class supports_createcopy(nodes.Admonition, nodes.Element): pass @@ -98,6 +102,7 @@ class supports_createcopy(nodes.Admonition, nodes.Element): def visit_supports_createcopy_node(self, node): self.body.append(self.starttag( node, 'div', CLASS=('admonition supports_createcopy'))) + self.context.append('\n') class supports_virtualio(nodes.Admonition, nodes.Element): pass @@ -105,6 +110,7 @@ class supports_virtualio(nodes.Admonition, nodes.Element): def visit_supports_georeferencing_node(self, node): self.body.append(self.starttag( node, 'div', CLASS=('admonition supports_georeferencing'))) + self.context.append('\n') class supports_georeferencing(nodes.Admonition, nodes.Element): pass @@ -112,6 +118,7 @@ class supports_georeferencing(nodes.Admonition, nodes.Element): def visit_supports_virtualio_node(self, node): self.body.append(self.starttag( node, 'div', CLASS=('admonition supports_virtualio'))) + self.context.append('\n') from docutils.parsers.rst import Directive diff --git a/docs/source/conf.py b/docs/source/conf.py index 9190fd37..ffd2f4bb 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -62,7 +62,6 @@ 'canonical_url': 'https://mdal.xyz', 'analytics_id': '', # Provided by Google in your dashboard 'logo_only': True, - 'display_version': True, 'prev_next_buttons_location': 'both', 'style_external_links': False, #'vcs_pageview_mode': '', @@ -127,7 +126,7 @@ # Additional stuff for the LaTeX preamble. 'preamble': preamble, -'inputenc':'\\usepackage[utf8]{inputenc}\n\\usepackage{CJKutf8}\n\\usepackage{substitutefont}', +'inputenc':'\\usepackage[utf8]{inputenc}\n\\usepackage{CJKutf8}', 'babel':'\\usepackage[russian,main=english]{babel}\n\\selectlanguage{english}', 'fontenc':'\\usepackage[LGR,X2,T1]{fontenc}' diff --git a/mdal/frmts/mdal_sww.cpp b/mdal/frmts/mdal_sww.cpp index c424c3b4..fee28598 100644 --- a/mdal/frmts/mdal_sww.cpp +++ b/mdal/frmts/mdal_sww.cpp @@ -35,6 +35,14 @@ size_t MDAL::DriverSWW::getVertexCount( const NetCDFFile &ncFile ) const return res; } +size_t MDAL::DriverSWW::getFaceCount( const NetCDFFile &ncFile ) const +{ + int nVolumesId; + size_t res; + ncFile.getDimension( "number_of_volumes", &res, &nVolumesId ); + return res; +} + bool MDAL::DriverSWW::canReadMesh( const std::string &uri ) { @@ -99,16 +107,21 @@ std::vector MDAL::DriverSWW::readZCoords( const NetCDFFile &ncFile ) con void MDAL::DriverSWW::addBedElevation( const NetCDFFile &ncFile, MDAL::MemoryMesh *mesh, - const std::vector × + const std::vector ×, + const MDAL::DateTime &referenceTime ) const { if ( ncFile.hasArr( "elevation" ) ) { + size_t nPoints = getVertexCount( ncFile ); std::shared_ptr grp = readScalarGroup( ncFile, mesh, times, "Bed Elevation", - "elevation" ); + "elevation", + nPoints, + MDAL_DataLocation::DataOnVertices, + referenceTime ); mesh->datasetGroups.emplace_back( std::move( grp ) ); } else @@ -180,9 +193,13 @@ std::vector MDAL::DriverSWW::readTimes( const NetCDFFile &ncFile ) const void MDAL::DriverSWW::readDatasetGroups( const NetCDFFile &ncFile, MDAL::MemoryMesh *mesh, - const std::vector × + const std::vector ×, + const MDAL::DateTime &referenceTime ) const { + size_t nPoints = getVertexCount( ncFile ); + size_t nVolumes = getFaceCount( ncFile ); + std::set parsedVariableNames; // already parsed arrays somewhere else parsedVariableNames.insert( "x" ); parsedVariableNames.insert( "y" ); @@ -191,53 +208,58 @@ void MDAL::DriverSWW::readDatasetGroups( parsedVariableNames.insert( "time" ); std::vector names = ncFile.readArrNames(); - std::set namesSet( names.begin(), names.end() ); - // Add bed elevation group + // Add bed elevation group (reads "elevation" variable) parsedVariableNames.insert( "elevations" ); - addBedElevation( ncFile, mesh, times ); + addBedElevation( ncFile, mesh, times, referenceTime ); 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" ) ) + // skip already parsed variables + if ( parsedVariableNames.find( name ) != parsedVariableNames.cend() ) continue; - // skip already parsed variables - if ( parsedVariableNames.find( name ) == parsedVariableNames.cend() ) - { - std::string xName, yName, groupName( name ); - bool isVector = parseGroupName( groupName, xName, yName ); + // Variables ending in "_c" hold values at triangle centroids (faces) + bool isCentroid = MDAL::endsWith( name, "_c" ); + size_t nValues = isCentroid ? nVolumes : nPoints; + MDAL_DataLocation dataLocation = isCentroid ? MDAL_DataLocation::DataOnFaces : MDAL_DataLocation::DataOnVertices; - std::shared_ptr grp; - if ( isVector && ncFile.hasArr( xName ) && ncFile.hasArr( yName ) ) - { - // vector dataset group - grp = readVectorGroup( - ncFile, - mesh, - times, - std::move( groupName ), - xName, - yName ); - parsedVariableNames.insert( std::move( xName ) ); - parsedVariableNames.insert( std::move( yName ) ); - } - else - { - // scalar dataset group - grp = readScalarGroup( - ncFile, - mesh, - times, - std::move( groupName ), - name ); - parsedVariableNames.insert( name ); - } - if ( grp ) - mesh->datasetGroups.emplace_back( std::move( grp ) ); + 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, + std::move( groupName ), + xName, + yName, + nValues, + dataLocation, + referenceTime ); + parsedVariableNames.insert( std::move( xName ) ); + parsedVariableNames.insert( std::move( yName ) ); + } + else + { + // scalar dataset group + grp = readScalarGroup( + ncFile, + mesh, + times, + std::move( groupName ), + name, + nValues, + dataLocation, + referenceTime ); + parsedVariableNames.insert( name ); } + if ( grp ) + mesh->datasetGroups.emplace_back( std::move( grp ) ); } } @@ -282,10 +304,12 @@ std::shared_ptr MDAL::DriverSWW::readScalarGroup( MDAL::MemoryMesh *mesh, const std::vector ×, const std::string groupName, - const std::string arrName + const std::string arrName, + size_t nValues, + MDAL_DataLocation dataLocation, + const MDAL::DateTime &referenceTime ) const { - size_t nPoints = getVertexCount( ncFile ); std::shared_ptr mds; int varxid; @@ -296,8 +320,10 @@ std::shared_ptr MDAL::DriverSWW::readScalarGroup( mesh, mFileName, groupName ); - mds->setDataLocation( MDAL_DataLocation::DataOnVertices ); + mds->setDataLocation( dataLocation ); mds->setIsScalar( true ); + if ( referenceTime.isValid() ) + mds->setReferenceTime( referenceTime ); int zDimsX = 0; if ( nc_inq_varndims( ncFile.handle(), varxid, &zDimsX ) != NC_NOERR ) @@ -308,8 +334,8 @@ std::shared_ptr MDAL::DriverSWW::readScalarGroup( // 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 ) + std::vector valuesX = ncFile.readDoubleArr( arrName, nValues ); + for ( size_t i = 0; i < nValues; ++i ) { o->setScalarValue( i, valuesX[i] ); } @@ -331,7 +357,7 @@ std::shared_ptr MDAL::DriverSWW::readScalarGroup( start[0] = t; start[1] = 0; count[0] = 1; - count[1] = nPoints; + count[1] = nValues; nc_get_vars_double( ncFile.handle(), varxid, start, count, stride, values ); mto->setStatistics( MDAL::calculateStatistics( mto ) ); mds->datasets.push_back( mto ); @@ -349,10 +375,12 @@ std::shared_ptr MDAL::DriverSWW::readVectorGroup( const std::vector ×, const std::string groupName, const std::string arrXName, - const std::string arrYName + const std::string arrYName, + size_t nValues, + MDAL_DataLocation dataLocation, + const MDAL::DateTime &referenceTime ) const { - size_t nPoints = getVertexCount( ncFile ); std::shared_ptr mds; int varxid, varyid; @@ -364,8 +392,10 @@ std::shared_ptr MDAL::DriverSWW::readVectorGroup( mesh, mFileName, groupName ); - mds->setDataLocation( MDAL_DataLocation::DataOnVertices ); + mds->setDataLocation( dataLocation ); mds->setIsScalar( false ); + if ( referenceTime.isValid() ) + mds->setReferenceTime( referenceTime ); int zDimsX = 0; int zDimsY = 0; @@ -383,9 +413,9 @@ std::shared_ptr MDAL::DriverSWW::readVectorGroup( // 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 ) + std::vector valuesX = ncFile.readDoubleArr( arrXName, nValues ); + std::vector valuesY = ncFile.readDoubleArr( arrYName, nValues ); + for ( size_t i = 0; i < nValues; ++i ) { o->setVectorValue( i, valuesX[i], valuesY[i] ); } @@ -394,12 +424,12 @@ std::shared_ptr MDAL::DriverSWW::readVectorGroup( } else { - std::vector valuesX( nPoints ), valuesY( nPoints ); + std::vector valuesX( nValues ), valuesY( nValues ); // 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. ); + mto->setTime( static_cast( times[t] ), RelativeTimestamp::seconds ); // Time is always in seconds // fetching data for one timestep size_t start[2], count[2]; @@ -407,11 +437,11 @@ std::shared_ptr MDAL::DriverSWW::readVectorGroup( start[0] = t; start[1] = 0; count[0] = 1; - count[1] = nPoints; + count[1] = nValues; 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 ) + for ( size_t i = 0; i < nValues; ++i ) { mto->setVectorValue( i, static_cast( valuesX[i] ), static_cast( valuesY[i] ) ); } @@ -453,11 +483,33 @@ std::unique_ptr MDAL::DriverSWW::load( mesh->setFaces( std::move( faces ) ); mesh->setVertices( std::move( vertices ) ); + // Read CRS from global epsg attribute + int epsgVal = 0; + if ( nc_get_att_int( ncFile.handle(), NC_GLOBAL, "epsg", &epsgVal ) == NC_NOERR && epsgVal != 0 ) + mesh->setSourceCrsFromEPSG( epsgVal ); + + // Read timezone as mesh metadata (informational) + char tzBuf[128]; + size_t tzLen = 0; + if ( nc_inq_attlen( ncFile.handle(), NC_GLOBAL, "timezone", &tzLen ) == NC_NOERR && + tzLen < sizeof( tzBuf ) ) + { + memset( tzBuf, 0, sizeof( tzBuf ) ); + nc_get_att_text( ncFile.handle(), NC_GLOBAL, "timezone", tzBuf ); + mesh->setMetadata( "timezone", std::string( tzBuf ) ); + } + + // Build reference time from starttime (seconds since Unix epoch 1970-01-01T00:00:00 UTC) + MDAL::DateTime referenceTime; + double starttime = 0.0; + if ( nc_get_att_double( ncFile.handle(), NC_GLOBAL, "starttime", &starttime ) == NC_NOERR ) + referenceTime = MDAL::DateTime( starttime, MDAL::DateTime::Epoch::Unix ); + // Read times std::vector times = readTimes( ncFile ); - // Create a dataset(s) - readDatasetGroups( ncFile, mesh.get(), times ); + // Create dataset groups + readDatasetGroups( ncFile, mesh.get(), times, referenceTime ); // Success! return std::unique_ptr( mesh.release() ); diff --git a/mdal/frmts/mdal_sww.hpp b/mdal/frmts/mdal_sww.hpp index 23073521..f83d6485 100644 --- a/mdal/frmts/mdal_sww.hpp +++ b/mdal/frmts/mdal_sww.hpp @@ -14,6 +14,7 @@ #include "mdal_data_model.hpp" #include "mdal_memory_data_model.hpp" +#include "mdal_datetime.hpp" #include "mdal.h" #include "mdal_driver.hpp" #include "mdal_netcdf.hpp" @@ -45,15 +46,19 @@ namespace MDAL private: size_t getVertexCount( const NetCDFFile &ncFile ) const; + size_t getFaceCount( const NetCDFFile &ncFile ) const; std::vector readZCoords( const NetCDFFile &ncFile ) const; MDAL::Vertices readVertices( const NetCDFFile &ncFile ) const; MDAL::Faces readFaces( const NetCDFFile &ncFile ) const; std::vector readTimes( const NetCDFFile &ncFile ) const; /** * Finds all variables (arrays) in netcdf file and base on the name add it as - * vector or scalar dataset group + * vector or scalar dataset group. + * Variables with a "_c" suffix hold values at triangle centroids (faces). */ - void readDatasetGroups( const NetCDFFile &ncFile, MDAL::MemoryMesh *mesh, const std::vector × ) const; + void readDatasetGroups( const NetCDFFile &ncFile, MDAL::MemoryMesh *mesh, + const std::vector ×, + const MDAL::DateTime &referenceTime ) const; bool parseGroupName( std::string &groupName, std::string &xName, std::string &yName ) const; std::shared_ptr readScalarGroup( @@ -61,7 +66,10 @@ namespace MDAL MDAL::MemoryMesh *mesh, const std::vector ×, const std::string variableBaseName, - const std::string arrName + const std::string arrName, + size_t nValues, + MDAL_DataLocation dataLocation, + const MDAL::DateTime &referenceTime ) const; std::shared_ptr readVectorGroup( @@ -70,13 +78,17 @@ namespace MDAL const std::vector ×, const std::string variableBaseName, const std::string arrXName, - const std::string arrYName + const std::string arrYName, + size_t nValues, + MDAL_DataLocation dataLocation, + const MDAL::DateTime &referenceTime ) const; void addBedElevation( const NetCDFFile &ncFile, MDAL::MemoryMesh *mesh, - const std::vector × + const std::vector ×, + const MDAL::DateTime &referenceTime ) const; std::string mFileName; diff --git a/tests/data/sww/merimbula_basic_mesh.sww b/tests/data/sww/merimbula_basic_mesh.sww new file mode 100644 index 00000000..8b2051fb Binary files /dev/null and b/tests/data/sww/merimbula_basic_mesh.sww differ diff --git a/tests/test_sww.cpp b/tests/test_sww.cpp index c94b4c1b..d131089c 100644 --- a/tests/test_sww.cpp +++ b/tests/test_sww.cpp @@ -198,26 +198,59 @@ TEST( MeshSWWTest, Catchment ) int f_count = MDAL_M_faceCount( m ); EXPECT_EQ( 6388, f_count ); - ASSERT_EQ( 9, MDAL_M_datasetGroupCount( m ) ); + // friction_c shifts momentum and elevation groups by one compared to the + // old code that skipped all _c variables. The file also gains momentum_c, + // elevation_c and stage_c face groups. + ASSERT_EQ( 13, MDAL_M_datasetGroupCount( m ) ); { - MDAL_DatasetGroupH g = MDAL_M_datasetGroup( m, 3 ); + MDAL_DatasetGroupH g = MDAL_M_datasetGroup( m, 4 ); ASSERT_NE( g, nullptr ); EXPECT_EQ( std::string( "momentum" ), std::string( MDAL_G_name( g ) ) ); EXPECT_EQ( false, MDAL_G_hasScalarData( g ) ); } { - MDAL_DatasetGroupH g = MDAL_M_datasetGroup( m, 4 ); + MDAL_DatasetGroupH g = MDAL_M_datasetGroup( m, 5 ); ASSERT_NE( g, nullptr ); EXPECT_EQ( std::string( "momentum/Maximums" ), std::string( MDAL_G_name( g ) ) ); EXPECT_EQ( false, MDAL_G_hasScalarData( g ) ); } { - MDAL_DatasetGroupH g = MDAL_M_datasetGroup( m, 5 ); + MDAL_DatasetGroupH g = MDAL_M_datasetGroup( m, 6 ); ASSERT_NE( g, nullptr ); EXPECT_EQ( std::string( "elevation" ), std::string( MDAL_G_name( g ) ) ); EXPECT_EQ( true, MDAL_G_hasScalarData( g ) ); } + // New face groups + { + MDAL_DatasetGroupH g = MDAL_M_datasetGroup( m, 3 ); + ASSERT_NE( g, nullptr ); + EXPECT_EQ( std::string( "friction_c" ), std::string( MDAL_G_name( g ) ) ); + EXPECT_EQ( true, MDAL_G_hasScalarData( g ) ); + EXPECT_EQ( MDAL_DataLocation::DataOnFaces, MDAL_G_dataLocation( g ) ); + EXPECT_EQ( 6388, MDAL_D_valueCount( MDAL_G_dataset( g, 0 ) ) ); + } + { + MDAL_DatasetGroupH g = MDAL_M_datasetGroup( m, 10 ); + ASSERT_NE( g, nullptr ); + EXPECT_EQ( std::string( "momentum_c" ), std::string( MDAL_G_name( g ) ) ); + EXPECT_EQ( false, MDAL_G_hasScalarData( g ) ); + EXPECT_EQ( MDAL_DataLocation::DataOnFaces, MDAL_G_dataLocation( g ) ); + } + { + MDAL_DatasetGroupH g = MDAL_M_datasetGroup( m, 11 ); + ASSERT_NE( g, nullptr ); + EXPECT_EQ( std::string( "elevation_c" ), std::string( MDAL_G_name( g ) ) ); + EXPECT_EQ( true, MDAL_G_hasScalarData( g ) ); + EXPECT_EQ( MDAL_DataLocation::DataOnFaces, MDAL_G_dataLocation( g ) ); + } + { + MDAL_DatasetGroupH g = MDAL_M_datasetGroup( m, 12 ); + ASSERT_NE( g, nullptr ); + EXPECT_EQ( std::string( "stage_c" ), std::string( MDAL_G_name( g ) ) ); + EXPECT_EQ( true, MDAL_G_hasScalarData( g ) ); + EXPECT_EQ( MDAL_DataLocation::DataOnFaces, MDAL_G_dataLocation( g ) ); + } MDAL_CloseMesh( m ); } @@ -271,6 +304,138 @@ TEST( MeshSWWTest, Wave ) MDAL_CloseMesh( m ); } +TEST( MeshSWWTest, Merimbula ) +{ + std::string path = test_file( "/sww/merimbula_basic_mesh.sww" ); + EXPECT_EQ( MDAL_MeshNames( path.c_str() ), "SWW:\"" + path + "\"" ); + MDAL_MeshH m = MDAL_LoadMesh( path.c_str() ); + ASSERT_NE( m, nullptr ); + MDAL_Status s = MDAL_LastStatus(); + EXPECT_EQ( MDAL_Status::None, s ); + + // CRS + const char *projection = MDAL_M_projection( m ); + EXPECT_EQ( std::string( "EPSG:32755" ), std::string( projection ) ); + + std::string driverName = MDAL_M_driverName( m ); + EXPECT_EQ( driverName, "SWW" ); + + // /////////// + // Vertices + // /////////// + int v_count = MDAL_M_vertexCount( m ); + EXPECT_EQ( v_count, 5719 ); + + // /////////// + // Faces + // /////////// + int f_count = MDAL_M_faceCount( m ); + EXPECT_EQ( f_count, 10785 ); + + // /////////// + // Dataset groups + // /////////// + // Expected groups (in file variable order): + // 0 Bed Elevation - vertex scalar (static, from "elevation") + // 1 friction - vertex scalar (static) + // 2 friction/Maximums - vertex scalar (static) + // 3 elevation - vertex scalar (static, same data as Bed Elevation) + // 4 elevation/Maximums - vertex scalar (static) + // 5 friction_c - face scalar (static) + // 6 elevation_c - face scalar (static) + // 7 momentum - vertex vector (time-dependent, xmomentum+ymomentum) + // 8 momentum/Maximums - vertex vector (static range) + // 9 stage - vertex scalar (time-dependent) + // 10 stage/Maximums - vertex scalar (static range) + // 11 momentum_c - face vector (time-dependent, xmomentum_c+ymomentum_c) + // 12 stage_c - face scalar (time-dependent) + ASSERT_EQ( 13, MDAL_M_datasetGroupCount( m ) ); + + // --- Bed Elevation (group 0) --- + { + MDAL_DatasetGroupH g = MDAL_M_datasetGroup( m, 0 ); + ASSERT_NE( g, nullptr ); + EXPECT_EQ( std::string( "Bed Elevation" ), std::string( MDAL_G_name( g ) ) ); + EXPECT_TRUE( MDAL_G_hasScalarData( g ) ); + EXPECT_EQ( MDAL_DataLocation::DataOnVertices, MDAL_G_dataLocation( g ) ); + ASSERT_EQ( 1, MDAL_G_datasetCount( g ) ); + MDAL_DatasetH ds = MDAL_G_dataset( g, 0 ); + EXPECT_EQ( 5719, MDAL_D_valueCount( ds ) ); + EXPECT_TRUE( compareReferenceTime( g, "1970-01-01T00:00:00" ) ); + } + + // --- friction_c (group 5) — static scalar on faces --- + { + MDAL_DatasetGroupH g = MDAL_M_datasetGroup( m, 5 ); + ASSERT_NE( g, nullptr ); + EXPECT_EQ( std::string( "friction_c" ), std::string( MDAL_G_name( g ) ) ); + EXPECT_TRUE( MDAL_G_hasScalarData( g ) ); + EXPECT_EQ( MDAL_DataLocation::DataOnFaces, MDAL_G_dataLocation( g ) ); + ASSERT_EQ( 1, MDAL_G_datasetCount( g ) ); + MDAL_DatasetH ds = MDAL_G_dataset( g, 0 ); + EXPECT_EQ( 10785, MDAL_D_valueCount( ds ) ); + EXPECT_DOUBLE_EQ( 0.0, getValue( ds, 0 ) ); + } + + // --- elevation_c (group 6) — static scalar on faces --- + { + MDAL_DatasetGroupH g = MDAL_M_datasetGroup( m, 6 ); + ASSERT_NE( g, nullptr ); + EXPECT_EQ( std::string( "elevation_c" ), std::string( MDAL_G_name( g ) ) ); + EXPECT_TRUE( MDAL_G_hasScalarData( g ) ); + EXPECT_EQ( MDAL_DataLocation::DataOnFaces, MDAL_G_dataLocation( g ) ); + ASSERT_EQ( 1, MDAL_G_datasetCount( g ) ); + MDAL_DatasetH ds = MDAL_G_dataset( g, 0 ); + EXPECT_EQ( 10785, MDAL_D_valueCount( ds ) ); + EXPECT_NEAR( -0.04933963, getValue( ds, 0 ), 1e-5 ); + EXPECT_NEAR( -0.04984199, getValue( ds, 1 ), 1e-5 ); + } + + // --- stage (group 9) — time-dependent scalar on vertices --- + { + MDAL_DatasetGroupH g = MDAL_M_datasetGroup( m, 9 ); + ASSERT_NE( g, nullptr ); + EXPECT_EQ( std::string( "stage" ), std::string( MDAL_G_name( g ) ) ); + EXPECT_TRUE( MDAL_G_hasScalarData( g ) ); + EXPECT_EQ( MDAL_DataLocation::DataOnVertices, MDAL_G_dataLocation( g ) ); + ASSERT_EQ( 6, MDAL_G_datasetCount( g ) ); + EXPECT_TRUE( compareReferenceTime( g, "1970-01-01T00:00:00" ) ); + // times are 0, 10, 20, 30, 40, 50 seconds + EXPECT_TRUE( compareDurationInHours( MDAL_D_time( MDAL_G_dataset( g, 1 ) ), + 10.0 / 3600.0 ) ); + } + + // --- momentum_c (group 11) — time-dependent vector on faces --- + { + MDAL_DatasetGroupH g = MDAL_M_datasetGroup( m, 11 ); + ASSERT_NE( g, nullptr ); + EXPECT_EQ( std::string( "momentum_c" ), std::string( MDAL_G_name( g ) ) ); + EXPECT_FALSE( MDAL_G_hasScalarData( g ) ); + EXPECT_EQ( MDAL_DataLocation::DataOnFaces, MDAL_G_dataLocation( g ) ); + ASSERT_EQ( 6, MDAL_G_datasetCount( g ) ); + MDAL_DatasetH ds = MDAL_G_dataset( g, 0 ); + EXPECT_EQ( 10785, MDAL_D_valueCount( ds ) ); + EXPECT_TRUE( compareReferenceTime( g, "1970-01-01T00:00:00" ) ); + } + + // --- stage_c (group 12) — time-dependent scalar on faces --- + { + MDAL_DatasetGroupH g = MDAL_M_datasetGroup( m, 12 ); + ASSERT_NE( g, nullptr ); + EXPECT_EQ( std::string( "stage_c" ), std::string( MDAL_G_name( g ) ) ); + EXPECT_TRUE( MDAL_G_hasScalarData( g ) ); + EXPECT_EQ( MDAL_DataLocation::DataOnFaces, MDAL_G_dataLocation( g ) ); + ASSERT_EQ( 6, MDAL_G_datasetCount( g ) ); + MDAL_DatasetH ds = MDAL_G_dataset( g, 0 ); + EXPECT_EQ( 10785, MDAL_D_valueCount( ds ) ); + EXPECT_NEAR( 2.0, getValue( ds, 0 ), 1e-5 ); + EXPECT_NEAR( 1.0, getValue( ds, 2 ), 1e-5 ); + EXPECT_TRUE( compareReferenceTime( g, "1970-01-01T00:00:00" ) ); + } + + MDAL_CloseMesh( m ); +} + int main( int argc, char **argv ) { testing::InitGoogleTest( &argc, argv );