// Copyright (C) Stichting Deltares 2017. All rights reserved. // // This file is part of Ringtoets. // // Ringtoets is free software: you can redistribute it and/or modify // it under the terms of the GNU 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 General Public License for more details. // // You should have received a copy of the GNU 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.Collections.ObjectModel; using System.Linq; using Core.Common.Base.Geometry; using Ringtoets.Common.IO.Exceptions; using Ringtoets.Common.IO.SoilProfile; using Ringtoets.Piping.IO.Properties; using Ringtoets.Piping.Primitives; using RingtoetsCommonIOResources = Ringtoets.Common.IO.Properties.Resources; namespace Ringtoets.Piping.IO.SoilProfiles { /// /// Transforms generic into . /// internal static class PipingSoilLayerTransformer { /// /// Transforms the generic into a . /// /// The soil layer to use in the transformation. /// A new based on the given data. /// Thrown when is null. /// Thrown when transformation would not result /// in a valid transformed instance. public static PipingSoilLayer Transform(SoilLayer1D soilLayer) { if (soilLayer == null) { throw new ArgumentNullException(nameof(soilLayer)); } ValidateStochasticParameters(soilLayer); var pipingSoilLayer = new PipingSoilLayer(soilLayer.Top) { IsAquifer = TransformIsAquifer(soilLayer.IsAquifer), MaterialName = soilLayer.MaterialName, Color = SoilLayerColorConverter.Convert(soilLayer.Color) }; SetStochasticParameters(pipingSoilLayer, soilLayer); return pipingSoilLayer; } /// /// Transforms the generic into one or more . /// /// The soil layer to use in the transformation. /// The 1D intersection of the profile. /// The bottom of the soil layer. /// A collection of based on the given data. /// Thrown when is null. /// Thrown when transformation would not result /// in a valid transformed instance. public static IEnumerable Transform(SoilLayer2D soilLayer, double atX, out double bottom) { bottom = double.MaxValue; var soilLayers = new Collection(); Transform(soilLayer, atX, soilLayers, ref bottom); return soilLayers; } /// /// Transforms the generic into one or more . /// /// The soil layer to use in the transformation. /// The 1D intersection of the profile. /// The collection of transformed piping soil layers to add the /// transformation to. /// The bottom of the soil layer. /// Thrown when is null. /// Thrown when transformation would not result /// in a valid transformed instance. private static void Transform(SoilLayer2D soilLayer, double atX, ICollection soilLayers, ref double bottom) { if (soilLayer == null) { throw new ArgumentNullException(nameof(soilLayer)); } ValidateStochasticParameters(soilLayer); if (soilLayer.OuterLoop == null) { return; } double[] outerLoopIntersectionHeights = GetLoopIntersectionHeights(soilLayer.OuterLoop.Segments, atX).ToArray(); if (!outerLoopIntersectionHeights.Any()) { return; } IEnumerable> innerLoopsIntersectionHeights = soilLayer.NestedLayers.Select(l => GetLoopIntersectionHeights(l.OuterLoop.Segments, atX)); IEnumerable> innerLoopIntersectionHeightPairs = GetOrderedStartAndEndPairsIn1D(innerLoopsIntersectionHeights).ToList(); IEnumerable> outerLoopIntersectionHeightPairs = GetOrderedStartAndEndPairsIn1D(outerLoopIntersectionHeights).ToList(); double currentBottom = outerLoopIntersectionHeightPairs.First().Item1; var heights = new List(); heights.AddRange(innerLoopIntersectionHeightPairs.Where(p => p.Item1 >= currentBottom).Select(p => p.Item1)); heights.AddRange(outerLoopIntersectionHeightPairs.Select(p => p.Item2)); foreach (double height in heights.Where(height => !innerLoopIntersectionHeightPairs.Any(tuple => HeightInInnerLoop(tuple, height)))) { var pipingSoilLayer = new PipingSoilLayer(height) { IsAquifer = TransformIsAquifer(soilLayer.IsAquifer), MaterialName = soilLayer.MaterialName, Color = SoilLayerColorConverter.Convert(soilLayer.Color) }; SetStochasticParameters(pipingSoilLayer, soilLayer); soilLayers.Add(pipingSoilLayer); } bottom = currentBottom < bottom ? currentBottom : bottom; foreach (SoilLayer2D nestedLayer in soilLayer.NestedLayers) { Transform(nestedLayer, atX, soilLayers, ref bottom); } } /// /// Validates whether the values of the distribution and shift for the stochastic parameters /// are correct for creating a . /// /// Thrown when any of the distributions of the /// stochastic parameters is not defined as lognormal or is shifted when it should not be. private static void ValidateStochasticParameters(SoilLayerBase soilLayer) { DistributionHelper.ValidateIsLogNormal( soilLayer.BelowPhreaticLevelDistributionType, Resources.SoilLayer_BelowPhreaticLevelDistribution_DisplayName); DistributionHelper.ValidateIsNonShiftedLogNormal( soilLayer.DiameterD70DistributionType, soilLayer.DiameterD70Shift, Resources.SoilLayer_DiameterD70Distribution_DisplayName); DistributionHelper.ValidateIsNonShiftedLogNormal( soilLayer.PermeabilityDistributionType, soilLayer.PermeabilityShift, Resources.SoilLayer_PermeabilityDistribution_DisplayName); } /// /// Sets the values of the stochastic parameters for the given . /// /// The to set the property values for. /// The to get the properties from. /// This method does not perform validation. Use to /// verify whether the distributions for the stochastic parameters are correctly defined. private static void SetStochasticParameters(PipingSoilLayer pipingSoilLayer, SoilLayerBase soilLayer1D) { pipingSoilLayer.BelowPhreaticLevelMean = soilLayer1D.BelowPhreaticLevelMean; pipingSoilLayer.BelowPhreaticLevelDeviation = soilLayer1D.BelowPhreaticLevelDeviation; pipingSoilLayer.BelowPhreaticLevelShift = soilLayer1D.BelowPhreaticLevelShift; pipingSoilLayer.DiameterD70Mean = soilLayer1D.DiameterD70Mean; pipingSoilLayer.DiameterD70CoefficientOfVariation = soilLayer1D.DiameterD70CoefficientOfVariation; pipingSoilLayer.PermeabilityMean = soilLayer1D.PermeabilityMean; pipingSoilLayer.PermeabilityCoefficientOfVariation = soilLayer1D.PermeabilityCoefficientOfVariation; } private static bool HeightInInnerLoop(Tuple tuple, double height) { return height <= tuple.Item2 && height > tuple.Item1; } private static IEnumerable> GetOrderedStartAndEndPairsIn1D(IEnumerable> innerLoopsIntersectionPoints) { var result = new Collection>(); foreach (IEnumerable innerLoopIntersectionPoints in innerLoopsIntersectionPoints) { foreach (Tuple tuple in GetOrderedStartAndEndPairsIn1D(innerLoopIntersectionPoints)) { result.Add(tuple); } } return result; } private static Collection> GetOrderedStartAndEndPairsIn1D(IEnumerable innerLoopIntersectionPoints) { var result = new Collection>(); List orderedHeights = innerLoopIntersectionPoints.OrderBy(v => v).ToList(); for (var i = 0; i < orderedHeights.Count; i = i + 2) { double first = orderedHeights[i]; double second = orderedHeights[i + 1]; result.Add(Tuple.Create(first, second)); } return result; } /// /// Gets a of heights where the intersects the /// vertical line at . /// /// The sequence of which together create a loop. /// The point on the x-axis where the vertical line is constructed do determine intersections with. /// A of , representing the height at which the /// intersects the vertical line at . /// Thrown when a segment is vertical at and thus /// no deterministic intersection points can be determined. private static IEnumerable GetLoopIntersectionHeights(IEnumerable loop, double atX) { Segment2D[] segment2Ds = loop.ToArray(); if (segment2Ds.Any(segment => IsVerticalAtX(segment, atX))) { string message = string.Format(Resources.Error_Can_not_determine_1D_profile_with_vertical_segments_at_X_0_, atX); throw new ImportedDataTransformException(message); } return Math2D.SegmentsIntersectionWithVerticalLine(segment2Ds, atX).Select(p => p.Y); } private static bool IsVerticalAtX(Segment2D segment, double atX) { return segment.FirstPoint.X.Equals(atX) && segment.IsVertical(); } /// /// Transforms a to a for the /// . /// /// The value to transform. /// A based on . /// Thrown when /// could not be transformed. private static bool TransformIsAquifer(double? isAquifer) { try { return SoilLayerIsAquiferConverter.Convert(isAquifer); } catch (NotSupportedException) { throw new ImportedDataTransformException(string.Format(RingtoetsCommonIOResources.Transform_Invalid_value_ParameterName_0, RingtoetsCommonIOResources.SoilLayerData_IsAquifer_DisplayName)); } } } }