/* MDAL - Mesh Data Abstraction Library (MIT License) Copyright (C) 2019 Peter Petrik (zilolv at gmail dot com) */ #include "mdal_ugrid.hpp" #include "mdal_utils.hpp" #include "mdal_logger.hpp" #include #include #include #include MDAL::DriverUgrid::DriverUgrid() : DriverCF( "Ugrid", "UGRID Results", "*.nc", Capability::ReadMesh | Capability::SaveMesh ) { } MDAL::DriverUgrid *MDAL::DriverUgrid::create() { return new DriverUgrid(); } std::vector MDAL::DriverUgrid::findMeshesNames() const { std::vector meshesInFile; const std::vector variables = mNcFile->readArrNames(); for ( const std::string &var : variables ) { bool isMeshTopology = mNcFile->getAttrStr( var, "cf_role" ) == "mesh_topology"; if ( isMeshTopology ) { // file can include more meshes meshesInFile.push_back( var ); } } return meshesInFile; } std::string MDAL::DriverUgrid::buildUri( const std::string &meshFile ) { mNcFile.reset( new NetCDFFile ); try { mNcFile->openFile( meshFile ); } catch ( MDAL::Error &err ) { err.setDriver( name() ); MDAL::Log::error( err ); return std::string(); } std::vector meshNames = findMeshesNames(); if ( !meshNames.size() ) { MDAL::Log::error( MDAL_Status::Err_UnknownFormat, name(), "No meshes found in file" + meshFile ); return std::string( "" ); } // ignore network variable std::vector::iterator position = std::find( meshNames.begin(), meshNames.end(), "network" ); if ( position != meshNames.end() ) meshNames.erase( position ); return MDAL::buildAndMergeMeshUris( meshFile, meshNames, name() ); } std::string MDAL::DriverUgrid::nodeZVariableName() const { const std::vector variables = mNcFile->readArrNames(); for ( const std::string &varName : variables ) { const std::string stdName = mNcFile->getAttrStr( varName, "standard_name" ); const std::string meshName = mNcFile->getAttrStr( varName, "mesh" ); const std::string location = mNcFile->getAttrStr( varName, "location" ); if ( stdName == "altitude" && meshName == mMeshName && location == "node" ) { return varName; } } // not found, the file in non UGRID standard conforming, // but lets try the common name return mMeshName + "_node_z"; } MDAL::CFDimensions MDAL::DriverUgrid::populateDimensions( ) { CFDimensions dims; size_t count; int ncid; mAllMeshNames = findMeshesNames(); if ( mAllMeshNames.empty() ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, name(), "File " + mFileName + " does not contain any valid mesh definition" ); if ( !mRequestedMeshName.empty() ) { if ( std::find( std::begin( mAllMeshNames ), std::end( mAllMeshNames ), mRequestedMeshName ) != std::end( mAllMeshNames ) ) mMeshName = mRequestedMeshName; else throw MDAL::Error( MDAL_Status::Err_InvalidData, "No such mesh with name: " + mRequestedMeshName, name() ); } else { if ( mAllMeshNames.size() == 1 ) mMeshName = mAllMeshNames.at( 0 ); else // there are more meshes in file { if ( MDAL::contains( mAllMeshNames.at( 0 ), "network" ) ) // ignore the network variable for a moment mMeshName = mAllMeshNames.at( 1 ); else mMeshName = mAllMeshNames.at( 0 ); MDAL::Log::warning( MDAL_Status::Warn_MultipleMeshesInFile, name(), "Found multiple meshes in file, working with: " + mMeshName ); } } if ( mMeshName.empty() ) throw MDAL::Error( MDAL_Status::Err_InvalidData, "Unable to parse mesh name from file" ); mMeshDimension = mNcFile->getAttrInt( mMeshName, "topology_dimension" ); if ( ( mMeshDimension < 1 ) || ( mMeshDimension > 2 ) ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, name(), "Unable to parse topology dimension from mesh or mesh is 3D" ); MDAL::Log::info( "Parsing " + std::to_string( mMeshDimension ) + "D mesh with name: " + mMeshName ); std::string nodeXVariable, nodeYVariable; if ( mMeshDimension == 1 ) parseCoordinatesFrom1DMesh( mMeshName, "node_coordinates", nodeXVariable, nodeYVariable ); else parse2VariablesFromAttribute( mMeshName, "node_coordinates", nodeXVariable, nodeYVariable, false ); std::vector nodeDimension; std::vector nodeDimensionId; mNcFile->getDimensions( nodeXVariable, nodeDimension, nodeDimensionId ); if ( nodeDimension.size() != 1 ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Error while parsing dimensions" ); dims.setDimension( CFDimensions::Vertex, nodeDimension.at( 0 ), nodeDimensionId.at( 0 ) ); /* continue parsing dimension dependent variables */ if ( mMeshDimension == 1 ) populate1DMeshDimensions( dims ); else populate2DMeshDimensions( dims, ncid ); /* Time variable - not required for UGRID format */ if ( mNcFile->hasDimension( "time" ) ) { mNcFile->getDimension( "time", &count, &ncid ); dims.setDimension( CFDimensions::Time, count, ncid ); } else { dims.setDimension( CFDimensions::Time, 0, -1 ); } return dims; } void MDAL::DriverUgrid::populate1DMeshDimensions( MDAL::CFDimensions &dims ) { /* Parse number of edges ( dimension ) from mesh */ std::string edgeConnectivityVariableName = mNcFile->getAttrStr( mMeshName, "edge_node_connectivity" ); if ( edgeConnectivityVariableName.empty() ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Did not find edge node connectivity attribute" ); std::vector edgeDimension; std::vector edgeDimensionId; mNcFile->getDimensions( edgeConnectivityVariableName, edgeDimension, edgeDimensionId ); if ( edgeDimension.size() != 2 ) throw MDAL::Error( MDAL_Status::Err_InvalidData, name(), "Unable to parse dimensions for edge_nodes_connectivity variable" ); size_t edgesCount = edgeDimension.at( 0 ); // Only interested in first value, edge will always have only 2 nodes int edgesCountId = edgeDimensionId.at( 0 ); dims.setDimension( CFDimensions::Edge, edgesCount, edgesCountId ); } void MDAL::DriverUgrid::populate2DMeshDimensions( MDAL::CFDimensions &dims, int &ncid ) { // face dimension location is retrieved from the face_node_connectivity variable // if face_dimension is defined as attribute, the dimension at this location help to desambiguate vertex per faces and number of faces std::string faceConnectivityVariablesName = mNcFile->getAttrStr( mMeshName, "face_node_connectivity" ); std::string faceDimensionLocation = mNcFile->getAttrStr( mMeshName, "face_dimension" ); if ( faceConnectivityVariablesName == "" ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Did not find face connectivity attribute" ); size_t facesCount; size_t maxVerticesPerFace; size_t count; std::vector faceDimension; std::vector faceDimensionId; int facesIndexDimensionId; int maxVerticesPerFaceDimensionId; mNcFile->getDimensions( faceConnectivityVariablesName, faceDimension, faceDimensionId ); if ( faceDimension.size() != 2 ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Face dimension is 2D" ); // if face_dimension is not present in file, get it from dimension element if ( faceDimensionLocation != "" ) { mNcFile->getDimension( faceDimensionLocation, &facesCount, &ncid ); if ( facesCount == faceDimension.at( 0 ) ) { facesIndexDimensionId = faceDimensionId.at( 0 ); maxVerticesPerFaceDimensionId = faceDimensionId.at( 1 ); maxVerticesPerFace = faceDimension.at( 1 ); } else { facesIndexDimensionId = faceDimensionId.at( 1 ); maxVerticesPerFaceDimensionId = faceDimensionId.at( 0 ); maxVerticesPerFace = faceDimension.at( 0 ); } } else { facesIndexDimensionId = faceDimensionId.at( 0 ); facesCount = faceDimension.at( 0 ); maxVerticesPerFaceDimensionId = faceDimensionId.at( 1 ); maxVerticesPerFace = faceDimension.at( 1 ); } dims.setDimension( CFDimensions::Face, facesCount, facesIndexDimensionId ); dims.setDimension( CFDimensions::MaxVerticesInFace, maxVerticesPerFace, maxVerticesPerFaceDimensionId ); // number of edges in the mesh, not required for UGRID format const std::string mesh2dEdge = mNcFile->getAttrStr( mMeshName, "edge_dimension" ); if ( mNcFile->hasDimension( mesh2dEdge ) ) { mNcFile->getDimension( mesh2dEdge, &count, &ncid ); dims.setDimension( CFDimensions::Face2DEdge, count, ncid ); } else { dims.setDimension( CFDimensions::Face2DEdge, 0, -1 ); } } void MDAL::DriverUgrid::populateElements( Vertices &vertices, Edges &edges, Faces &faces ) { populateVertices( vertices ); if ( mMeshDimension == 1 ) populateEdges( edges ); // 1D mesh else populateFaces( faces ); // 2D mesh } void MDAL::DriverUgrid::populateVertices( MDAL::Vertices &vertices ) { assert( vertices.empty() ); size_t vertexCount = mDimensions.size( CFDimensions::Vertex ); vertices.resize( vertexCount ); Vertex *vertexPtr = vertices.data(); // node_coordinates should be something like Mesh2D_node_x Mesh2D_node_y std::string verticesXName, verticesYName; if ( mMeshDimension == 1 ) parseCoordinatesFrom1DMesh( mMeshName, "node_coordinates", verticesXName, verticesYName ); else parse2VariablesFromAttribute( mMeshName, "node_coordinates", verticesXName, verticesYName, false ); const std::vector verticesX = mNcFile->readDoubleArr( verticesXName, vertexCount ); const std::vector verticesY = mNcFile->readDoubleArr( verticesYName, vertexCount ); std::vector verticesZ; if ( mNcFile->hasArr( nodeZVariableName() ) ) { verticesZ = mNcFile->readDoubleArr( nodeZVariableName(), vertexCount ); } for ( size_t i = 0; i < vertexCount; ++i, ++vertexPtr ) { vertexPtr->x = verticesX[i]; vertexPtr->y = verticesY[i]; if ( !verticesZ.empty() ) vertexPtr->z = verticesZ[i]; } } void MDAL::DriverUgrid::populateEdges( MDAL::Edges &edges ) { assert( edges.empty() ); // number of edges size_t edgesCount = mDimensions.size( CFDimensions::Edge ); edges.resize( edgesCount ); const std::string edgeNodeConnectivityVar = mNcFile->getAttrStr( mMeshName, "edge_node_connectivity" ); if ( edgeNodeConnectivityVar.empty() ) MDAL::Log::error( MDAL_Status::Err_MissingDriver, "Unable to find edge_node_connectivity attribute of " + mMeshName ); // load edges std::vector edgeNodesIdxs = mNcFile->readIntArr( edgeNodeConnectivityVar, edgesCount * 2 ); // two nodes per edge int startIndex = mNcFile->getAttrInt( edgeNodeConnectivityVar, "start_index" ); // iterate over all edge_nodes coordinates - those are indexes for nodes for ( size_t i = 0; i < edgesCount; ++i ) { int startEdgeIx = i * 2; int endEdgeIx = i * 2 + 1; edges[i].startVertex = edgeNodesIdxs[startEdgeIx] - startIndex; edges[i].endVertex = edgeNodesIdxs[endEdgeIx] - startIndex; } } void MDAL::DriverUgrid::populateFaces( MDAL::Faces &faces ) { assert( faces.empty() ); size_t faceCount = mDimensions.size( CFDimensions::Face ); faces.resize( faceCount ); // Parse 2D Mesh // face_node_connectivity is usually something like Mesh2D_face_nodes const std::string mesh2dFaceNodeConnectivity = mNcFile->getAttrStr( mMeshName, "face_node_connectivity" ); size_t verticesInFace = mDimensions.size( CFDimensions::MaxVerticesInFace ); int fillVal = -1; if ( mNcFile->hasAttrInt( mesh2dFaceNodeConnectivity, "_FillValue" ) ) fillVal = mNcFile->getAttrInt( mesh2dFaceNodeConnectivity, "_FillValue" ); int startIndex = mNcFile->getAttrInt( mesh2dFaceNodeConnectivity, "start_index" ); std::vector faceNodesConn = mNcFile->readIntArr( mesh2dFaceNodeConnectivity, faceCount * verticesInFace ); for ( size_t i = 0; i < faceCount; ++i ) { std::vector idxs; for ( size_t j = 0; j < verticesInFace; ++j ) { size_t idx = verticesInFace * i + j; int val = faceNodesConn[idx]; if ( fillVal == val ) { // found fill val break; } else { idxs.push_back( static_cast( val - startIndex ) ); } } faces[i] = idxs; } } void MDAL::DriverUgrid::addBedElevation( MDAL::MemoryMesh *mesh ) { if ( mNcFile->hasArr( nodeZVariableName() ) ) MDAL::addBedElevationDatasetGroup( mesh, mesh->vertices() ); } std::string MDAL::DriverUgrid::getCoordinateSystemVariableName() { std::string coordinateSystemVariable; // first try to get the coordinate system variable from grid definition std::vector nodeVariablesName = MDAL::split( mNcFile->getAttrStr( mMeshName, "node_coordinates" ), ' ' ); if ( nodeVariablesName.size() > 1 ) { if ( mNcFile->hasArr( nodeVariablesName[0] ) ) { coordinateSystemVariable = mNcFile->getAttrStr( nodeVariablesName[0], "grid_mapping" ); } } // if automatic discovery fails, try to check some hardcoded common variables that store projection if ( coordinateSystemVariable.empty() ) { if ( mNcFile->hasArr( "projected_coordinate_system" ) ) coordinateSystemVariable = "projected_coordinate_system"; else if ( mNcFile->hasArr( "wgs84" ) ) coordinateSystemVariable = "wgs84"; } // return, may be empty return coordinateSystemVariable; } std::set MDAL::DriverUgrid::ignoreNetCDFVariables() { std::set ignoreVariables; ignoreVariables.insert( "projected_coordinate_system" ); ignoreVariables.insert( "time" ); ignoreVariables.insert( "timestep" ); for ( const std::string &mesh : mAllMeshNames ) { ignoreVariables.insert( mesh ); int dim = mNcFile->getAttrInt( mesh, "topology_dimension" ); if ( dim == 1 ) ignore1DMeshVariables( mesh, ignoreVariables ); else ignore2DMeshVariables( mesh, ignoreVariables ); } return ignoreVariables; } void MDAL::DriverUgrid::ignore1DMeshVariables( const std::string &mesh, std::set &ignoreVariables ) { // ignore all variables with network in name // network topology does not contain any data if ( MDAL::contains( mesh, "network" ) ) { std::vector variables = mNcFile->readArrNames(); for ( const std::string &var : variables ) { if ( MDAL::contains( var, "network" ) ) ignoreVariables.insert( var ); } return; } ignoreVariables.insert( mNcFile->getAttrStr( mesh, "edge_node_connectivity" ) ); ignoreVariables.insert( mNcFile->getAttrStr( mesh, "node_id" ) ); ignoreVariables.insert( mNcFile->getAttrStr( mesh, "node_long_name" ) ); std::vector coordinateVarsToIgnore {"node_coordinates", "edge_coordinates"}; for ( const std::string &coordinateIt : coordinateVarsToIgnore ) { std::string coordinatesVar = mNcFile->getAttrStr( mesh, coordinateIt ); std::vector allCoords = MDAL::split( coordinatesVar, " " ); for ( const std::string &var : allCoords ) { ignoreVariables.insert( var ); ignoreVariables.insert( mNcFile->getAttrStr( var, "bounds" ) ); } } } void MDAL::DriverUgrid::ignore2DMeshVariables( const std::string &mesh, std::set &ignoreVariables ) { std::string xName, yName; parse2VariablesFromAttribute( mesh, "node_coordinates", xName, yName, true ); ignoreVariables.insert( xName ); ignoreVariables.insert( yName ); ignoreVariables.insert( nodeZVariableName() ); ignoreVariables.insert( mNcFile->getAttrStr( mesh, "edge_node_connectivity" ) ); parse2VariablesFromAttribute( mesh, "edge_coordinates", xName, yName, true ); if ( !xName.empty() ) { ignoreVariables.insert( xName ); ignoreVariables.insert( mNcFile->getAttrStr( xName, "bounds" ) ); } if ( !yName.empty() ) { ignoreVariables.insert( yName ); ignoreVariables.insert( mNcFile->getAttrStr( yName, "bounds" ) ); } ignoreVariables.insert( mNcFile->getAttrStr( mesh, "face_node_connectivity" ) ); parse2VariablesFromAttribute( mesh, "face_coordinates", xName, yName, true ); if ( !xName.empty() ) { ignoreVariables.insert( xName ); ignoreVariables.insert( mNcFile->getAttrStr( xName, "bounds" ) ); } if ( !yName.empty() ) { ignoreVariables.insert( yName ); ignoreVariables.insert( mNcFile->getAttrStr( yName, "bounds" ) ); } ignoreVariables.insert( mNcFile->getAttrStr( mesh, "edge_face_connectivity" ) ); } void MDAL::DriverUgrid::parseNetCDFVariableMetadata( int varid, std::string &variableName, std::string &name, bool *isVector, bool *isPolar, bool *invertedDirection, bool *isX ) { *isVector = false; *isX = true; *isPolar = false; *invertedDirection = false; std::string longName = mNcFile->getAttrStr( "long_name", varid ); if ( longName.empty() ) { std::string standardName = mNcFile->getAttrStr( "standard_name", varid ); if ( standardName.empty() ) { name = variableName; } else { variableName = standardName; if ( MDAL::contains( standardName, "_x_" ) ) { *isVector = true; name = MDAL::replace( standardName, "_x_", "" ); } else if ( MDAL::contains( standardName, "_y_" ) ) { *isVector = true; *isX = false; name = MDAL::replace( standardName, "_y_", "" ); } else if ( MDAL::contains( standardName, "_from_direction" ) ) { *isVector = true; *isPolar = true; *isX = false; *invertedDirection = true; name = MDAL::replace( standardName, "_speed", "_velocity" ); name = MDAL::replace( name, "_from_direction", "" ); } else if ( MDAL::contains( standardName, "_to_direction" ) ) { *isVector = true; *isPolar = true; *isX = false; name = MDAL::replace( standardName, "_speed", "_velocity" ); name = MDAL::replace( name, "_to_direction", "" ); } else { name = standardName; } } } else { variableName = longName; if ( MDAL::contains( longName, ", x-component" ) || MDAL::contains( longName, "u component of " ) ) { *isVector = true; name = MDAL::replace( longName, ", x-component", "" ); name = MDAL::replace( name, "u component of ", "" ); } else if ( MDAL::contains( longName, ", y-component" ) || MDAL::contains( longName, "v component of " ) ) { *isVector = true; *isX = false; name = MDAL::replace( longName, ", y-component", "" ); name = MDAL::replace( name, "v component of ", "" ); } else if ( MDAL::contains( longName, " magnitude" ) ) { *isVector = true; *isPolar = true; *isX = true; name = MDAL::replace( longName, "speed", "velocity" ); name = MDAL::removeFrom( name, " magnitude" ); } else if ( MDAL::contains( longName, "direction" ) ) { *isVector = true; *isPolar = true; *isX = false; // check from_/to_direction in standard_name std::string standardName = mNcFile->getAttrStr( "standard_name", varid ); *invertedDirection = MDAL::contains( longName, "from direction" ); name = MDAL::replace( longName, "speed", "velocity" ); name = MDAL::removeFrom( name, " from direction" ); name = MDAL::removeFrom( name, " to direction" ); name = MDAL::removeFrom( name, " direction" ); } else { name = longName; } } } std::string MDAL::DriverUgrid::getTimeVariableName() const { return "time"; } void MDAL::DriverUgrid::parseCoordinatesFrom1DMesh( const std::string &meshName, const std::string &attr_name, std::string &var1, std::string &var2 ) { std::vector nodeVariablesName = MDAL::split( mNcFile->getAttrStr( meshName, attr_name ), ' ' ); if ( nodeVariablesName.size() < 2 ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Error while parsing node coordinates" ); else if ( nodeVariablesName.size() > 3 ) { // format does not follow UGRID convention and have extra variables in coordinate attribute // tring to parse coordinate variables, it usually ends with _x and _y, e.g. mesh1d_node_x, mesh1d_node_y MDAL::Log::warning( MDAL_Status::Warn_InvalidElements, name(), "Node coordinates consists of more than 3 variables, taking variable with _x in name by default" ); for ( const auto &nodeVar : nodeVariablesName ) { if ( MDAL::contains( nodeVar, "_x" ) ) { var1 = nodeVar; } else if ( MDAL::contains( nodeVar, "_y" ) ) { var2 = nodeVar; } } if ( var1.empty() || var2.empty() ) throw MDAL::Error( MDAL_Status::Err_InvalidData, name(), "Could not parse node coordinates from mesh" ); } else // 2 variables as node coordinates { var1 = nodeVariablesName.at( 0 ); var2 = nodeVariablesName.at( 1 ); } } void MDAL::DriverUgrid::parse2VariablesFromAttribute( const std::string &name, const std::string &attr_name, std::string &var1, std::string &var2, bool optional ) const { const std::string mesh2dNodeCoordinates = mNcFile->getAttrStr( name, attr_name ); const std::vector chunks = MDAL::split( mesh2dNodeCoordinates, ' ' ); if ( chunks.size() != 2 ) { if ( optional ) { var1 = ""; var2 = ""; } else throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Unable to parse variables from attribute" ); } else { var1 = chunks[0]; var2 = chunks[1]; } } void MDAL::DriverUgrid::save( const std::string &uri, MDAL::Mesh *mesh ) { mFileName = uri; try { // Create file mNcFile.reset( new NetCDFFile ); mNcFile->createFile( mFileName ); // Write globals writeGlobals( ); // Write variables writeVariables( mesh ); } catch ( MDAL_Status error ) { MDAL::Log::error( error, name(), "could not save file " + uri ); } catch ( MDAL::Error err ) { MDAL::Log::error( err, name() ); } } void MDAL::DriverUgrid::writeVariables( MDAL::Mesh *mesh ) { // Global dimensions int dimNodeCountId = mNcFile->defineDimension( "nmesh2d_node", mesh->verticesCount() ); int dimFaceCountId = mNcFile->defineDimension( "nmesh2d_face", mesh->facesCount() ); mNcFile->defineDimension( "nmesh2d_edge", 1 ); // no data on edges, cannot be 0, since 0==NC_UNLIMITED int dimTimeId = mNcFile->defineDimension( "time", NC_UNLIMITED ); int dimMaxNodesPerFaceId = mNcFile->defineDimension( "max_nmesh2d_face_nodes", mesh->faceVerticesMaximumCount() ); // Mesh 2D Definition int mesh2dId = mNcFile->defineVar( "mesh2d", NC_INT, 0, nullptr ); mNcFile->putAttrStr( mesh2dId, "cf_role", "mesh_topology" ); mNcFile->putAttrStr( mesh2dId, "long_name", "Topology data of 2D network" ); mNcFile->putAttrInt( mesh2dId, "topology_dimension", 2 ); mNcFile->putAttrStr( mesh2dId, "node_coordinates", "mesh2d_node_x mesh2d_node_y" ); mNcFile->putAttrStr( mesh2dId, "node_dimension", "nmesh2d_node" ); mNcFile->putAttrStr( mesh2dId, "edge_dimension", "nmesh2d_edge" ); mNcFile->putAttrStr( mesh2dId, "max_face_nodes_dimension", "max_nmesh2d_face_nodes" ); mNcFile->putAttrStr( mesh2dId, "face_node_connectivity", "mesh2d_face_nodes" ); mNcFile->putAttrStr( mesh2dId, "face_dimension", "nmesh2d_face" ); // Nodes X coordinate int mesh2dNodeXId = mNcFile->defineVar( "mesh2d_node_x", NC_DOUBLE, 1, &dimNodeCountId ); mNcFile->putAttrStr( mesh2dNodeXId, "standard_name", "projection_x_coordinate" ); mNcFile->putAttrStr( mesh2dNodeXId, "long_name", "x-coordinate of mesh nodes" ); mNcFile->putAttrStr( mesh2dNodeXId, "mesh", "mesh2d" ); mNcFile->putAttrStr( mesh2dNodeXId, "location", "node" ); // Nodes Y coordinate int mesh2dNodeYId = mNcFile->defineVar( "mesh2d_node_y", NC_DOUBLE, 1, &dimNodeCountId ); mNcFile->putAttrStr( mesh2dNodeYId, "standard_name", "projection_y_coordinate" ); mNcFile->putAttrStr( mesh2dNodeYId, "long_name", "y-coordinate of mesh nodes" ); mNcFile->putAttrStr( mesh2dNodeYId, "mesh", "mesh2d" ); mNcFile->putAttrStr( mesh2dNodeYId, "location", "node" ); // Nodes Z coordinate int mesh2dNodeZId = mNcFile->defineVar( "mesh2d_node_z", NC_DOUBLE, 1, &dimNodeCountId ); mNcFile->putAttrStr( mesh2dNodeZId, "mesh", "mesh2d" ); mNcFile->putAttrStr( mesh2dNodeZId, "location", "node" ); mNcFile->putAttrStr( mesh2dNodeZId, "coordinates", "mesh2d_node_x mesh2d_node_y" ); mNcFile->putAttrStr( mesh2dNodeZId, "standard_name", "altitude" ); mNcFile->putAttrStr( mesh2dNodeZId, "long_name", "z-coordinate of mesh nodes" ); mNcFile->putAttrStr( mesh2dNodeZId, "grid_mapping", "projected_coordinate_system" ); double fillNodeZCoodVal = -999.0; mNcFile->putAttrDouble( mesh2dNodeZId, "_FillValue", fillNodeZCoodVal ); // Faces 2D Variable int mesh2FaceNodesId_dimIds [] { dimFaceCountId, dimMaxNodesPerFaceId }; int mesh2FaceNodesId = mNcFile->defineVar( "mesh2d_face_nodes", NC_INT, 2, mesh2FaceNodesId_dimIds ); mNcFile->putAttrStr( mesh2FaceNodesId, "cf_role", "face_node_connectivity" ); mNcFile->putAttrStr( mesh2FaceNodesId, "mesh", "mesh2d" ); mNcFile->putAttrStr( mesh2FaceNodesId, "location", "face" ); mNcFile->putAttrStr( mesh2FaceNodesId, "long_name", "Mapping from every face to its corner nodes (counterclockwise)" ); mNcFile->putAttrInt( mesh2FaceNodesId, "start_index", 0 ); int fillFace2DVertexValue = -999; mNcFile->putAttrInt( mesh2FaceNodesId, "_FillValue", fillFace2DVertexValue ); // Projected Coordinate System int pcsId = mNcFile->defineVar( "projected_coordinate_system", NC_INT, 0, nullptr ); if ( mesh->crs() == "" ) { mNcFile->putAttrInt( pcsId, "epsg", 0 ); mNcFile->putAttrStr( pcsId, "EPSG_code", "epgs:0" ); } else { std::vector words = MDAL::split( mesh->crs(), ":" ); if ( words[0] == "EPSG" && words.size() > 1 ) { mNcFile->putAttrInt( pcsId, "epsg", std::stoi( words[1] ) ); mNcFile->putAttrStr( pcsId, "EPSG_code", mesh->crs() ); } else { mNcFile->putAttrStr( pcsId, "wkt", mesh->crs() ); } } // Time array int timeId = mNcFile->defineVar( "time", NC_DOUBLE, 1, &dimTimeId ); mNcFile->putAttrStr( timeId, "units", "hours since 2000-01-01 00:00:00" ); // Turning off define mode - allows data write nc_enddef( mNcFile->handle() ); // Write vertices const size_t maxBufferSize = 1000; const size_t bufferSize = std::min( mesh->verticesCount(), maxBufferSize ); const size_t verticesCoordCount = bufferSize * 3; std::vector verticesCoordinates( verticesCoordCount ); std::unique_ptr vertexIterator = mesh->readVertices(); { size_t vertexIndex = 0; size_t vertexFileIndex = 0; while ( vertexIndex < mesh->verticesCount() ) { size_t verticesRead = vertexIterator->next( bufferSize, verticesCoordinates.data() ); if ( verticesRead == 0 ) break; for ( size_t i = 0; i < verticesRead; i++ ) { mNcFile->putDataDouble( mesh2dNodeXId, vertexFileIndex, verticesCoordinates[3 * i] ); mNcFile->putDataDouble( mesh2dNodeYId, vertexFileIndex, verticesCoordinates[3 * i + 1] ); if ( std::isnan( verticesCoordinates[3 * i + 2] ) ) mNcFile->putDataDouble( mesh2dNodeZId, vertexFileIndex, fillNodeZCoodVal ); else mNcFile->putDataDouble( mesh2dNodeZId, vertexFileIndex, verticesCoordinates[3 * i + 2] ); vertexFileIndex++; } vertexIndex += verticesRead; } } // Write faces std::unique_ptr faceIterator = mesh->readFaces(); const size_t faceVerticesMax = mesh->faceVerticesMaximumCount(); const size_t facesCount = mesh->facesCount(); const size_t faceOffsetsBufferLen = std::min( facesCount, maxBufferSize ); const size_t vertexIndicesBufferLen = faceOffsetsBufferLen * faceVerticesMax; std::vector faceOffsetsBuffer( faceOffsetsBufferLen ); std::vector vertexIndicesBuffer( vertexIndicesBufferLen ); size_t faceIndex = 0; while ( faceIndex < facesCount ) { size_t facesRead = faceIterator->next( faceOffsetsBufferLen, faceOffsetsBuffer.data(), vertexIndicesBufferLen, vertexIndicesBuffer.data() ); if ( facesRead == 0 ) break; for ( size_t i = 0; i < facesRead; i++ ) { std::vector verticesFaceData( faceVerticesMax, fillFace2DVertexValue ); int startIndex = 0; if ( i > 0 ) startIndex = faceOffsetsBuffer[ i - 1 ]; int endIndex = faceOffsetsBuffer[ i ]; size_t k = 0; for ( int j = startIndex; j < endIndex; ++j ) { int vertexIndex = vertexIndicesBuffer[ static_cast( j ) ]; verticesFaceData[k++] = vertexIndex; } mNcFile->putDataArrayInt( mesh2FaceNodesId, faceIndex + i, faceVerticesMax, verticesFaceData.data() ); } faceIndex += facesRead; } // Time values (not implemented) mNcFile->putDataDouble( timeId, 0, 0.0 ); // Turning on define mode nc_redef( mNcFile->handle() ); } void MDAL::DriverUgrid::writeGlobals() { mNcFile->putAttrStr( NC_GLOBAL, "source", "MDAL " + std::string( MDAL_Version() ) ); mNcFile->putAttrStr( NC_GLOBAL, "date_created", MDAL::getCurrentTimeStamp() ); mNcFile->putAttrStr( NC_GLOBAL, "Conventions", "CF-1.6 UGRID-1.0" ); } std::vector> MDAL::DriverUgrid::parseClassification( int varid ) const { std::vector> classes; std::string flagBoundVarName = mNcFile->getAttrStr( "flag_bounds", varid ); if ( !flagBoundVarName.empty() ) { try { int boundsVarId = mNcFile->getVarId( flagBoundVarName ); std::vector classDims; std::vector classDimIds; mNcFile->getDimensions( flagBoundVarName, classDims, classDimIds ); std::vector boundValues = mNcFile->readDoubleArr( boundsVarId, 0, 0, classDims[0], classDims[1] ); if ( classDims[1] != 2 || classDims[0] <= 0 ) throw MDAL::Error( MDAL_Status::Err_UnknownFormat, "Invalid classification dimension" ); std::pair classificationMeta; classificationMeta.first = "classification"; std::string classification; for ( size_t i = 0; i < classDims[0]; ++i ) { std::pair classBound; classBound.first = boundValues[i * 2]; classBound.second = boundValues[i * 2 + 1]; classes.push_back( classBound ); } } catch ( MDAL::Error &err ) { MDAL::Log::warning( err.status, err.driver, "Error when parsing class bounds: " + err.mssg + ", classification ignored" ); } } return classes; }