// Copyright (C) Stichting Deltares 2024. All rights reserved. // // This file is part of the Dam Engine. // // The Dam Engine is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . // // All names, logos, and references to "Deltares" are registered trademarks of // Stichting Deltares and remain full property of Stichting Deltares at all times. // All rights reserved. using System; using System.Collections.Generic; using System.Linq; using Deltares.DamEngine.Data.Geometry; using Deltares.DamEngine.Data.Geotechnics; namespace Deltares.DamEngine.Calculators.KernelWrappers.Common; public static class SoilProfile2DHelper { public enum LayerType { BottomAquiferCluster, InBetweenAquiferCluster, HighestAquiferCluster, LowestLayer } private enum BoundaryType { Top, Bottom } private const double deviationX = 1e-06; private const double toleranceAlmostEqual = 1e-09; internal static bool AreInBetweenAquiferClustersPresent(SoilProfile2D soilProfile, out int count) { double[] xCoordinates = DetermineAllXCoordinatesOfSoilProfile(soilProfile); var currentCount = 0; var previousCount = 0; for (var i = 0; i < xCoordinates.Length; i++) { SoilProfile1D crossSection = soilProfile.GetSoilProfile1D(xCoordinates[i]); currentCount = crossSection.GetInBetweenAquiferClusters?.Count ?? 0; if (i > 0 && currentCount != previousCount) { count = 0; return false; } previousCount = currentCount; } count = currentCount; return count > 0; } public static bool IsAtLeastOneIsolatedInBetweenAquiferPresent(SoilProfile2D soilProfile) { double[] xCoordinates = DetermineAllXCoordinatesOfSoilProfile(soilProfile); var previousCount = 0; SoilProfile1D previousCrossSection = null; for (var i = 0; i < xCoordinates.Length; i++) { SoilProfile1D crossSection = soilProfile.GetSoilProfile1D(xCoordinates[i]); int currentCount = crossSection.GetInBetweenAquiferClusters?.Count ?? 0; if (i > 0 && currentCount != previousCount) { if (IsAtLeastOneIsolatedInBetweenAquiferPresent(currentCount, previousCount, crossSection, previousCrossSection)) { return true; } } previousCount = currentCount; previousCrossSection = crossSection; } return false; } private static bool IsAtLeastOneIsolatedInBetweenAquiferPresent(int currentCount, int previousCount, SoilProfile1D currentCrossSection, SoilProfile1D previousCrossSection) { // If more than one new or one less in-between aquifers are found, at least one is an isolated aquifer. if (Math.Abs(currentCount - previousCount) > 1) { return true; } if (currentCount <= 0 || currentCrossSection.GetInBetweenAquiferClusters == null) { return false; } // If one new in-between aquifer is found, the top layer in the previous cross-section must be an aquifer. // If not, it is an isolated aquifer. if (currentCount - previousCount == 1 && !previousCrossSection.Layers[0].IsAquifer) { return true; } // If one less in-between aquifer is found, the top layer in the current section must be an aquifer. // If not, it is an isolated aquifer. return previousCount - currentCount == 1 && !currentCrossSection.Layers[0].IsAquifer; } /// /// Determines all the points of the layer boundary for the specified layer type: /// - For LayerType = BottomAquiferCluster, layerBoundaryTop is the top boundary of the bottom aquifer (= waternet line for PL3) /// - For LayerType = InBetweenAquiferCluster, layerBoundaryTop and layerBoundaryBottom are both boundaries of the in-between aquifer (= waternet line for PL4) /// - For LayerType = HighestAquiferCluster, layerBoundaryTop is the top of the highest aquifer (= waternet line for PL1 for "Hydrostatic" waternet) /// - For LayerType = LowestLayer, layerBoundaryBottom is the bottom boundary of the lowest layer (= waternet line for PL1 for "Full hydrostatic" waternet) /// When the aquifer is not continuous, null top and bottom boundaries are returned. /// /// The type of layer boundary. /// The soil profile 2D. /// All the points of the layer top boundary. /// All the points of the layer bottom boundary. /// In case of several in-between aquifers, the index of the in-between aquifer must be specified. /// All the points of the layer boundary. internal static void DetermineAquiferLayerBoundaryPoints(LayerType layerType, SoilProfile2D soilProfile, out Point2D[] layerBoundaryTop, out Point2D[] layerBoundaryBottom, int indexInBetweenAquifer = -1) { double[] xCoordinates = DetermineAllXCoordinatesOfSoilProfile(soilProfile); var xCoordinatesAll = new List(); foreach (double xCoordinate in xCoordinates) { if (HasAquiferAVerticalPartAtGivenX(layerType, xCoordinate, soilProfile, indexInBetweenAquifer)) { xCoordinatesAll.Add(xCoordinate - deviationX); xCoordinatesAll.Add(xCoordinate + deviationX); } else { xCoordinatesAll.Add(xCoordinate); } } var layerBoundaryTopPoints = new List(); var layerBoundaryBottomPoints = new List(); double previousAquiferTop = double.NaN; double previousAquiferBottom = double.NaN; foreach (double xCoordinate in xCoordinatesAll) { SoilProfile1D crossSection = soilProfile.GetSoilProfile1D(xCoordinate); // Determine if the cluster of layers is in range of the previous cluster of layers. // If not, return empty coordinates, because the cluster of layers is interrupted. double currentAquiferTop = GetLevel(layerType, BoundaryType.Top, crossSection, indexInBetweenAquifer); double currentAquiferBottom = GetLevel(layerType, BoundaryType.Bottom, crossSection, indexInBetweenAquifer); if (!double.IsNaN(previousAquiferTop) && !double.IsNaN(previousAquiferBottom) && !double.IsNaN(currentAquiferTop) && !double.IsNaN(currentAquiferBottom) && !AreHorizontallyConnected(previousAquiferTop, previousAquiferBottom, currentAquiferTop, currentAquiferBottom)) { layerBoundaryTop = null; layerBoundaryBottom = null; return; } if (!double.IsNaN(currentAquiferTop) && !double.IsNaN(currentAquiferBottom)) { previousAquiferTop = currentAquiferTop; previousAquiferBottom = currentAquiferBottom; layerBoundaryTopPoints.Add(new Point2D(xCoordinate, currentAquiferTop)); layerBoundaryBottomPoints.Add(new Point2D(xCoordinate, currentAquiferBottom)); } } // Perform a short validation that the coordinates are fully defined from the beginning to the end // of the profile. If not, the layer is not continuous. if (!IsLayerBoundaryContinuous(layerBoundaryTopPoints, xCoordinates.First(), xCoordinates.Last()) || !IsLayerBoundaryContinuous(layerBoundaryBottomPoints, xCoordinates.First(), xCoordinates.Last())) { layerBoundaryTop = null; layerBoundaryBottom = null; return; } layerBoundaryTop = layerBoundaryTopPoints.ToArray(); layerBoundaryBottom = layerBoundaryBottomPoints.ToArray(); } /// /// Determine all the xCoordinates to make cross-sections. /// /// The soil profile 2D. /// All the xCoordinates of the soil profile 2D. private static double[] DetermineAllXCoordinatesOfSoilProfile(SoilProfile2D soilProfile) { IEnumerable points = soilProfile.Surfaces.SelectMany(surf => surf.GeometrySurface.OuterLoop.CalcPoints); double[] xCoordinates = points.Select(point => point.X).OrderBy(x => x).Distinct().ToArray(); return xCoordinates; } private static bool IsLayerBoundaryContinuous(IEnumerable boundaryPoints, double leftGeometryBoundary, double rightGeometryBoundary) { IEnumerable point2Ds = boundaryPoints.ToList(); return point2Ds.Any() && (Math.Abs(point2Ds.First().X - leftGeometryBoundary) < toleranceAlmostEqual) && Math.Abs(point2Ds.Last().X - rightGeometryBoundary) < toleranceAlmostEqual; } private static double GetLevel(LayerType layerType, BoundaryType boundaryType, SoilProfile1D soilProfile1D, int indexInBetweenAquifer) { SoilLayer1D layer = GetLayer(layerType, boundaryType, soilProfile1D, indexInBetweenAquifer); if (layer != null) { return boundaryType == BoundaryType.Top ? layer.TopLevel : layer.BottomLevel; } return double.NaN; } private static SoilLayer1D GetLayer(LayerType layerType, BoundaryType boundaryType, SoilProfile1D soilProfile1D, int indexInBetweenAquifer) { switch (layerType) { case LayerType.BottomAquiferCluster: return boundaryType == BoundaryType.Top ? soilProfile1D.BottomAquiferLayer : soilProfile1D.DeepestAquiferLayer; case LayerType.InBetweenAquiferCluster: return boundaryType == BoundaryType.Top ? soilProfile1D.GetInBetweenAquiferClusters[indexInBetweenAquifer].Item1 : soilProfile1D.GetInBetweenAquiferClusters[indexInBetweenAquifer].Item2; case LayerType.HighestAquiferCluster: return boundaryType == BoundaryType.Top ? soilProfile1D.GetHighestAquifer() : soilProfile1D.GetLowestLayerOfHighestAquiferCluster(); case LayerType.LowestLayer: return soilProfile1D.Layers.Last(); } return null; } private static bool HasAquiferAVerticalPartAtGivenX(LayerType layerType, double xCoordinate, SoilProfile2D soilProfile, int indexInBetweenAquifer) { SoilProfile1D crossSectionLeft = soilProfile.GetSoilProfile1D(xCoordinate - deviationX); SoilProfile1D crossSectionRight = soilProfile.GetSoilProfile1D(xCoordinate + deviationX); if (crossSectionLeft == null || crossSectionRight == null) { return false; } double aquiferLeftTop = GetLevel(layerType, BoundaryType.Top, crossSectionLeft, indexInBetweenAquifer); double aquiferLefBottom = GetLevel(layerType, BoundaryType.Bottom, crossSectionLeft, indexInBetweenAquifer); double aquiferRightTop = GetLevel(layerType, BoundaryType.Top, crossSectionRight, indexInBetweenAquifer); double aquiferRightBottom = GetLevel(layerType, BoundaryType.Bottom, crossSectionRight, indexInBetweenAquifer); if (!double.IsNaN(aquiferLeftTop) && !double.IsNaN(aquiferLefBottom) && !double.IsNaN(aquiferRightTop) && !double.IsNaN(aquiferRightBottom)) { return Math.Abs(aquiferLeftTop - aquiferRightTop) > GeometryConstants.Accuracy || Math.Abs(aquiferLefBottom - aquiferRightBottom) > GeometryConstants.Accuracy; } return false; } private static bool AreHorizontallyConnected(double leftLayerTop, double leftLayerBottom, double rightLayerTop, double rightLayerBottom) { // Left soil layer envelopes whole right soil layer if (leftLayerBottom <= rightLayerBottom && leftLayerTop >= rightLayerTop) { return true; } // Right soil layer envelopes whole left soil layer if (rightLayerBottom <= leftLayerBottom && rightLayerTop >= leftLayerTop) { return true; } return (rightLayerTop <= leftLayerTop && rightLayerTop >= leftLayerBottom) // Top level lies in-between the left soil layer || (rightLayerBottom >= leftLayerBottom && rightLayerBottom <= leftLayerTop); // Bottom level lies in-between the left soil layer } }