// 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 RingtoetsCommonResources = 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 = soilLayer.IsAquifer, MaterialName = soilLayer.MaterialName, Color = soilLayer.Color }; SetStochasticParameters(pipingSoilLayer, soilLayer); return pipingSoilLayer; } public static IEnumerable Transform(SoilLayer2D soilLayer, double atX, out double bottom) { if (soilLayer == null) { throw new ArgumentNullException(nameof(soilLayer)); } ValidateStochasticParameters(soilLayer); bottom = double.MaxValue; if (soilLayer.OuterLoop == null) { return Enumerable.Empty(); } double[] outerLoopIntersectionHeights = GetLoopIntersectionHeights(soilLayer.OuterLoop, atX).ToArray(); if (!outerLoopIntersectionHeights.Any()) { return Enumerable.Empty(); } var soilLayers = new Collection(); IEnumerable> innerLoopsIntersectionHeights = soilLayer.InnerLoops.Select(loop => GetLoopIntersectionHeights(loop, 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 = soilLayer.IsAquifer, MaterialName = soilLayer.MaterialName, Color = soilLayer.Color }; SetStochasticParameters(pipingSoilLayer, soilLayer); soilLayers.Add(pipingSoilLayer); } bottom = EnsureBottomOutsideInnerLoop(innerLoopIntersectionHeightPairs, currentBottom); return soilLayers; } /// /// 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) { ValidateIsLogNormal( soilLayer.BelowPhreaticLevelDistribution, Resources.SoilLayer_BelowPhreaticLevelDistribution_Description); ValidateIsNonShiftedLogNormal( soilLayer.DiameterD70Distribution, soilLayer.DiameterD70Shift, Resources.SoilLayer_DiameterD70Distribution_Description); ValidateIsNonShiftedLogNormal( soilLayer.PermeabilityDistribution, soilLayer.PermeabilityShift, Resources.SoilLayer_PermeabilityDistribution_Description); } /// /// 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 void ValidateIsNonShiftedLogNormal(long? distribution, double shift, string incorrectDistibutionParameter) { if (distribution.HasValue && (distribution.Value != SoilLayerConstants.LogNormalDistributionValue || Math.Abs(shift) > 1e-6)) { throw new ImportedDataTransformException(string.Format( RingtoetsCommonResources.SoilLayer_Stochastic_parameter_0_has_no_lognormal_distribution, incorrectDistibutionParameter)); } } private static void ValidateIsLogNormal(long? distribution, string incorrectDistibutionParameter) { if (distribution.HasValue && distribution != SoilLayerConstants.LogNormalDistributionValue) { throw new ImportedDataTransformException(string.Format( RingtoetsCommonResources.SoilLayer_Stochastic_parameter_0_has_no_shifted_lognormal_distribution, incorrectDistibutionParameter)); } } private static bool HeightInInnerLoop(Tuple tuple, double height) { return height <= tuple.Item2 && height > tuple.Item1; } private static bool BottomInInnerLoop(Tuple tuple, double height) { return height < tuple.Item2 && height >= tuple.Item1; } private static double EnsureBottomOutsideInnerLoop(IEnumerable> innerLoopIntersectionHeightPairs, double bottom) { double newBottom = bottom; List> heightPairArray = innerLoopIntersectionHeightPairs.ToList(); Tuple overlappingInnerLoop = heightPairArray.FirstOrDefault(t => BottomInInnerLoop(t, newBottom)); while (overlappingInnerLoop != null) { newBottom = overlappingInnerLoop.Item2; overlappingInnerLoop = heightPairArray.FirstOrDefault(t => BottomInInnerLoop(t, newBottom)); } return newBottom; } 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(); } } }