From e2c3ff759553173597e2401e8b92208ca3d634e6 Mon Sep 17 00:00:00 2001 From: Luca Carniato Date: Mon, 2 Sep 2024 17:08:58 +0200 Subject: [PATCH] Add an API to retrieve the polygons of selected faces, filtered by property (#367| GRIDEDIT-1410) --- libs/MeshKernel/include/MeshKernel/Mesh2D.hpp | 19 ++- libs/MeshKernel/src/Mesh2D.cpp | 52 ++++++++- .../include/MeshKernelApi/MeshKernel.hpp | 38 ++++++ .../include/MeshKernelApi/Utils.hpp | 34 ++++++ libs/MeshKernelApi/src/MeshKernel.cpp | 108 ++++++++++++++---- libs/MeshKernelApi/tests/src/Mesh2DTests.cpp | 73 +++++++++++- 6 files changed, 297 insertions(+), 27 deletions(-) diff --git a/libs/MeshKernel/include/MeshKernel/Mesh2D.hpp b/libs/MeshKernel/include/MeshKernel/Mesh2D.hpp index e3b30fc80..b30e6528f 100644 --- a/libs/MeshKernel/include/MeshKernel/Mesh2D.hpp +++ b/libs/MeshKernel/include/MeshKernel/Mesh2D.hpp @@ -76,6 +76,12 @@ namespace meshkernel other }; + /// Enumerator for different filtering properties on a 2D mesh + enum class Property + { + Orthogonality = 0 + }; + /// @brief Default destructor ~Mesh2D() override = default; @@ -224,11 +230,11 @@ namespace meshkernel /// @brief Get the orthogonality values, the inner product of edges and segments connecting the face circumcenters /// @return The edge orthogonality - [[nodiscard]] std::vector GetOrthogonality(); + [[nodiscard]] std::vector GetOrthogonality() const; /// @brief Gets the smoothness values, ratios of the face areas /// @return The smoothness at the edges - [[nodiscard]] std::vector GetSmoothness(); + [[nodiscard]] std::vector GetSmoothness() const; /// @brief Gets the aspect ratios (the ratios edges lengths to flow edges lengths) /// @param[in,out] aspectRatios The aspect ratios (passed as reference to avoid re-allocation) @@ -288,6 +294,15 @@ namespace meshkernel /// @param[in] invertDeletion Inverts the selected node to delete (instead of outside the polygon, inside the polygon) [[nodiscard]] std::unique_ptr DeleteMesh(const Polygons& polygon, DeleteMeshOptions deletionOption, bool invertDeletion); + /// @brief This method generates a mask indicating which locations are within the specified range of the given metric. + /// + /// @param[in] location The location representing the location where to filter the object. + /// @param[in] property The property by which to filter locations. + /// @param[in] minValue The minimum value of the metric for filtering. + /// @param[in] maxValue The maximum value of the metric for filtering. + /// @ return A vector of boolean values. Each element corresponds to a location and is `true` if the location's metric is within the specified range, and `false` otherwise. + [[nodiscard]] std::vector FilterBasedOnMetric(Location location, Property property, double minValue, double maxValue) const; + /// @brief Inquire if a segment is crossing a face /// @param[in] firstPoint The first point of the segment /// @param[in] secondPoint The second point of the segment diff --git a/libs/MeshKernel/src/Mesh2D.cpp b/libs/MeshKernel/src/Mesh2D.cpp index 6eda77622..147794ce3 100644 --- a/libs/MeshKernel/src/Mesh2D.cpp +++ b/libs/MeshKernel/src/Mesh2D.cpp @@ -1158,7 +1158,7 @@ void Mesh2D::ComputeNodeNeighbours() } } -std::vector Mesh2D::GetOrthogonality() +std::vector Mesh2D::GetOrthogonality() const { std::vector result(GetNumEdges()); const auto numEdges = GetNumEdges(); @@ -1191,7 +1191,7 @@ std::vector Mesh2D::GetOrthogonality() return result; } -std::vector Mesh2D::GetSmoothness() +std::vector Mesh2D::GetSmoothness() const { std::vector result(GetNumEdges()); const auto numEdges = GetNumEdges(); @@ -1634,6 +1634,54 @@ std::vector Mesh2D::GetHangingEdges() const return result; } +std::vector Mesh2D::FilterBasedOnMetric(Location location, + Property property, + double minValue, + double maxValue) const +{ + if (location != Location::Faces) + { + throw ConstraintError("Unsupported location. Only location faces is supported"); + } + if (property != Property::Orthogonality) + { + throw ConstraintError("Unsupported metric. Only orthogonality metric is supported"); + } + + const auto numFaces = GetNumFaces(); + + // Pre-allocate memory for result vector and set all elements to false + std::vector result(numFaces, false); + + // Retrieve orthogonality values + const std::vector metricValues = GetOrthogonality(); + + // Loop through faces and compute how many edges have the metric within the range + for (UInt f = 0; f < numFaces; ++f) + { + const UInt numFaceEdges = GetNumFaceEdges(f); + UInt numEdgesFiltered = 0; + for (UInt e = 0; e < numFaceEdges; ++e) + { + const auto edge = m_facesEdges[f][e]; + const double metricValue = metricValues[edge]; + if (metricValue < minValue || metricValue > maxValue) + { + break; + } + numEdgesFiltered = numEdgesFiltered + 1; + } + + // If all edges have the metric within the range, the face is masked + if (numEdgesFiltered == numFaceEdges) + { + result[f] = true; + } + } + + return result; +} + std::unique_ptr Mesh2D::DeleteMesh(const Polygons& polygon, DeleteMeshOptions deletionOption, bool invertDeletion) { if (deletionOption == FacesWithIncludedCircumcenters) diff --git a/libs/MeshKernelApi/include/MeshKernelApi/MeshKernel.hpp b/libs/MeshKernelApi/include/MeshKernelApi/MeshKernel.hpp index 434005d78..c705d05f5 100644 --- a/libs/MeshKernelApi/include/MeshKernelApi/MeshKernel.hpp +++ b/libs/MeshKernelApi/include/MeshKernelApi/MeshKernel.hpp @@ -1182,6 +1182,11 @@ namespace meshkernelapi /// @returns Error code MKERNEL_API int mkernel_mesh2d_get_data(int meshKernelId, Mesh2D& mesh2d); + /// @brief Gets an int indicating the orthogonality property type for mesh2d + /// @param[out] type The int indicating the orthogonality property type + /// @returns Error code + MKERNEL_API int mkernel_mesh2d_get_orthogonality_property_type(int& type); + /// @brief Gets only the node and edge Mesh2D data /// /// This function ought to be called after `mkernel_mesh2d_get_dimensions` has been called @@ -1243,6 +1248,39 @@ namespace meshkernelapi /// @returns Error code MKERNEL_API int mkernel_mesh2d_get_face_polygons_dimension(int meshKernelId, int numEdges, int& geometryListDimension); + /// @brief Retrieves the dimension of the geometry list containing the face polygons within the filtering range. + /// + /// This function filters face polygons within a mesh based on a specific property and location, + /// applying a range filter (minimum and maximum values). It then returns the dimension of the + /// filtered geometry list. + /// + /// @param[in] meshKernelId The identifier of the mesh kernel or mesh state. + /// @param[in] propertyValue The property used to filter the locations. + /// @param[in] minValue The minimum value of the property. + /// @param[in] maxValue The maximum value of the property. + /// @param[out] geometryListDimension The output parameter that will store the dimension (size) of the geometry list + /// containing the polygons that match the filtering criteria. + /// @returns An error code indicating the success or failure of the operation. + MKERNEL_API int mkernel_mesh2d_get_filtered_face_polygons_dimension(int meshKernelId, + int propertyValue, + double minValue, + double maxValue, + int& geometryListDimension); + + /// @brief Gets the geometry list containing the face polygons within the filtering range + /// + /// @param[in] meshKernelId The identifier of the mesh kernel or mesh state. + /// @param[in] propertyValue The property used to filter the locations. + /// @param[in] minValue The minimum value of the property. + /// @param[in] maxValue The maximum value of the property. + /// @param[out] facePolygons The geometry list containing the filtered locations. + /// @returns Error code + MKERNEL_API int mkernel_mesh2d_get_filtered_face_polygons(int meshKernelId, + int propertyValue, + double minValue, + double maxValue, + const GeometryList& facePolygons); + /// @brief Gets the mesh location closet to a specific coordinate. /// @param[in] meshKernelId The id of the mesh state /// @param[in] xCoordinate The input xCoordinate diff --git a/libs/MeshKernelApi/include/MeshKernelApi/Utils.hpp b/libs/MeshKernelApi/include/MeshKernelApi/Utils.hpp index 647c3dbfb..e42acc13c 100644 --- a/libs/MeshKernelApi/include/MeshKernelApi/Utils.hpp +++ b/libs/MeshKernelApi/include/MeshKernelApi/Utils.hpp @@ -583,4 +583,38 @@ namespace meshkernelapi } } + static void FillFacePolygons(std::shared_ptr mesh2d, + const std::vector& facesInPolygon, + const GeometryList& facePolygons) + { + meshkernel::UInt count = 0; + for (meshkernel::UInt f = 0; f < mesh2d->GetNumFaces(); ++f) + { + if (!facesInPolygon[f]) + { + continue; + } + + const auto& faceNodes = mesh2d->m_facesNodes[f]; + if (count != 0) + { + facePolygons.coordinates_x[count] = missing::doubleValue; + facePolygons.coordinates_y[count] = missing::doubleValue; + count++; + } + + for (meshkernel::UInt n = 0u; n < faceNodes.size(); ++n) + { + const auto& currentNode = mesh2d->Node(faceNodes[n]); + facePolygons.coordinates_x[count] = currentNode.x; + facePolygons.coordinates_y[count] = currentNode.y; + count++; + } + const auto& currentNode = mesh2d->Node(faceNodes[0]); + facePolygons.coordinates_x[count] = currentNode.x; + facePolygons.coordinates_y[count] = currentNode.y; + count++; + } + } + } // namespace meshkernelapi diff --git a/libs/MeshKernelApi/src/MeshKernel.cpp b/libs/MeshKernelApi/src/MeshKernel.cpp index b8bdc9d45..04b9ad03e 100644 --- a/libs/MeshKernelApi/src/MeshKernel.cpp +++ b/libs/MeshKernelApi/src/MeshKernel.cpp @@ -801,6 +801,13 @@ namespace meshkernelapi return lastExitCode; } + MKERNEL_API int mkernel_mesh2d_get_orthogonality_property_type(int& type) + { + lastExitCode = meshkernel::ExitCode::Success; + type = static_cast(meshkernel::Mesh2D::Property::Orthogonality); + return lastExitCode; + } + MKERNEL_API int mkernel_mesh1d_get_dimensions(int meshKernelId, Mesh1D& mesh1d) { lastExitCode = meshkernel::ExitCode::Success; @@ -2320,34 +2327,17 @@ namespace meshkernelapi } meshKernelState[meshKernelId].m_mesh2d->Administrate(); const auto numFaces = meshKernelState[meshKernelId].m_mesh2d->GetNumFaces(); - meshkernel::UInt count = 0; + std::vector validFace(numFaces, false); for (meshkernel::UInt f = 0; f < numFaces; ++f) { const auto& faceNodes = meshKernelState[meshKernelId].m_mesh2d->m_facesNodes[f]; const auto faceNumEdges = static_cast(faceNodes.size()); - if (faceNumEdges != numEdges) - { - continue; - } - if (count != 0) - { - facePolygons.coordinates_x[count] = missing::doubleValue; - facePolygons.coordinates_y[count] = missing::doubleValue; - count++; - } - - for (meshkernel::UInt n = 0u; n < faceNodes.size(); ++n) + if (faceNumEdges == numEdges) { - const auto& currentNode = meshKernelState[meshKernelId].m_mesh2d->Node(faceNodes[n]); - facePolygons.coordinates_x[count] = currentNode.x; - facePolygons.coordinates_y[count] = currentNode.y; - count++; + validFace[f] = true; } - const auto& currentNode = meshKernelState[meshKernelId].m_mesh2d->Node(faceNodes[0]); - facePolygons.coordinates_x[count] = currentNode.x; - facePolygons.coordinates_y[count] = currentNode.y; - count++; } + FillFacePolygons(meshKernelState[meshKernelId].m_mesh2d, validFace, facePolygons); } catch (...) { @@ -2389,6 +2379,82 @@ namespace meshkernelapi return lastExitCode; } + MKERNEL_API int mkernel_mesh2d_get_filtered_face_polygons_dimension(int meshKernelId, + int propertyValue, + double minValue, + double maxValue, + int& geometryListDimension) + { + lastExitCode = meshkernel::ExitCode::Success; + try + { + if (!meshKernelState.contains(meshKernelId)) + { + throw meshkernel::MeshKernelError("The selected mesh kernel id does not exist."); + } + if (meshKernelState[meshKernelId].m_mesh2d->GetNumNodes() <= 0) + { + throw meshkernel::ConstraintError("The 2d mesh contains no nodes."); + } + const auto filterEnum = static_cast(propertyValue); + const auto filterMask = meshKernelState[meshKernelId].m_mesh2d->FilterBasedOnMetric(meshkernel::Location::Faces, + filterEnum, + minValue, + maxValue); + geometryListDimension = 0; + for (meshkernel::UInt f = 0; f < filterMask.size(); ++f) + { + if (!filterMask[f]) + { + continue; + } + const auto faceNumEdges = static_cast(meshKernelState[meshKernelId].m_mesh2d->m_facesNodes[f].size()); + geometryListDimension += faceNumEdges + 2; + } + if (geometryListDimension > 0) + { + geometryListDimension -= 1; + } + } + catch (...) + { + lastExitCode = HandleException(); + } + return lastExitCode; + } + + MKERNEL_API int mkernel_mesh2d_get_filtered_face_polygons(int meshKernelId, + int propertyValue, + double minValue, + double maxValue, + const GeometryList& facePolygons) + { + lastExitCode = meshkernel::ExitCode::Success; + try + { + if (!meshKernelState.contains(meshKernelId)) + { + throw meshkernel::MeshKernelError("The selected mesh kernel id does not exist."); + } + if (meshKernelState[meshKernelId].m_mesh2d->GetNumNodes() <= 0) + { + throw meshkernel::ConstraintError("The 2d mesh contains no nodes."); + } + + const auto filterEnum = static_cast(propertyValue); + const auto filterMask = meshKernelState[meshKernelId].m_mesh2d->FilterBasedOnMetric(meshkernel::Location::Faces, + filterEnum, + minValue, + maxValue); + FillFacePolygons(meshKernelState[meshKernelId].m_mesh2d, filterMask, facePolygons); + } + catch (...) + { + lastExitCode = HandleException(); + } + return lastExitCode; + } + MKERNEL_API int mkernel_polygon_get_offset(int meshKernelId, const GeometryList& geometryListIn, int inWard, double distance, GeometryList& geometryListOut) { lastExitCode = meshkernel::ExitCode::Success; diff --git a/libs/MeshKernelApi/tests/src/Mesh2DTests.cpp b/libs/MeshKernelApi/tests/src/Mesh2DTests.cpp index 844b5d094..f84e30df1 100644 --- a/libs/MeshKernelApi/tests/src/Mesh2DTests.cpp +++ b/libs/MeshKernelApi/tests/src/Mesh2DTests.cpp @@ -3,11 +3,9 @@ #include #include "MeshKernel/Parameters.hpp" - #include "MeshKernelApi/BoundingBox.hpp" #include "MeshKernelApi/Mesh2D.hpp" #include "MeshKernelApi/MeshKernel.hpp" - #include "TestUtils/MakeMeshes.hpp" // namespace aliases @@ -134,3 +132,74 @@ TEST(Mesh2DTests, Mesh2dApiNodeEdgeDataTest) EXPECT_EQ(edges[i], expectedEdges[i]); } } + +TEST(Mesh2DTests, GetPolygonsOfDeletedFaces_WithPolygon_ShouldGetPolygonOfDeletedFaces) +{ + // Prepare: set a mesh with two faces sharing an high orthogonality edge. 2 polygon faces should be return + std::vector nodesX{57.0, 49.1, 58.9, 66.7, 48.8, 65.9, 67.0, 49.1}; + std::vector nodesY{23.6, 14.0, 6.9, 16.2, 23.4, 24.0, 7.2, 6.7}; + + std::vector edges{ + 0, 1, + 1, 2, + 2, 3, + 0, 3, + 1, 4, + 0, 4, + 0, 5, + 3, 5, + 3, 6, + 2, 6, + 2, 7, + 1, 7}; + + meshkernelapi::Mesh2D mesh2d; + mesh2d.edge_nodes = edges.data(); + mesh2d.node_x = nodesX.data(); + mesh2d.node_y = nodesY.data(); + mesh2d.num_nodes = static_cast(nodesX.size()); + mesh2d.num_edges = static_cast(edges.size() * 0.5); + + int meshKernelId = -1; + auto errorCode = meshkernelapi::mkernel_allocate_state(0, meshKernelId); + ASSERT_EQ(meshkernel::ExitCode::Success, errorCode); + errorCode = mkernel_mesh2d_set(meshKernelId, mesh2d); + ASSERT_EQ(meshkernel::ExitCode::Success, errorCode); + + int propertyType = -1; + errorCode = meshkernelapi::mkernel_mesh2d_get_orthogonality_property_type(propertyType); + ASSERT_EQ(meshkernel::ExitCode::Success, errorCode); + + // Execute + int geometryListDimension = -1; + errorCode = meshkernelapi::mkernel_mesh2d_get_filtered_face_polygons_dimension(meshKernelId, + propertyType, + 0.04, + 1.0, + geometryListDimension); + ASSERT_EQ(meshkernel::ExitCode::Success, errorCode); + ASSERT_EQ(5, geometryListDimension); + + meshkernelapi::GeometryList facePolygons{}; + facePolygons.num_coordinates = geometryListDimension; + facePolygons.geometry_separator = meshkernel::constants::missing::doubleValue; + std::vector xfacePolygons(geometryListDimension); + std::vector yfacePolygons(geometryListDimension); + facePolygons.coordinates_x = xfacePolygons.data(); + facePolygons.coordinates_y = yfacePolygons.data(); + errorCode = mkernel_mesh2d_get_filtered_face_polygons(meshKernelId, + propertyType, + 0.04, + 1.0, + facePolygons); + + // Assert + ASSERT_EQ(meshkernel::ExitCode::Success, errorCode); + std::vector expectedFacePolygonsX{57.0, 49.1, 58.9, 66.7, 57.0}; + std::vector expectedFacePolygonsY{23.6, 14.0, 6.9, 16.2, 23.6}; + for (size_t i = 0u; i < xfacePolygons.size(); ++i) + { + ASSERT_NEAR(expectedFacePolygonsX[i], xfacePolygons[i], 1e-6); + ASSERT_NEAR(expectedFacePolygonsY[i], yfacePolygons[i], 1e-6); + } +}