// Copyright (C) Stichting Deltares 2016. 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.Piping.IO.Properties; using Ringtoets.Piping.Primitives; namespace Ringtoets.Piping.IO.Builders { /// /// This class represents objects which were imported from a DSoilModel database. /// Instances of this class are transient and are not to be used once the DSoilModel /// database has been imported. /// internal class SoilLayer2D { private readonly Collection innerLoops; private Segment2D[] outerLoop; /// /// Creates a new instance of . /// public SoilLayer2D() { innerLoops = new Collection(); } /// /// Gets or sets a value representing /// whether the is an aquifer. /// public double? IsAquifer { get; set; } /// /// Gets or sets the above phreatic level for the . /// public double? AbovePhreaticLevel { get; set; } /// /// Gets or sets the below phreatic level for the . /// public double? BelowPhreaticLevel { get; set; } /// /// Gets or sets the dry unit weight for the . /// public double? DryUnitWeight { get; set; } /// /// Gets the outer loop of the as a of , /// for which each of the segments are connected to the next. /// /// Thrown when the in /// do not form a loop. public IEnumerable OuterLoop { get { return outerLoop; } internal set { var loop = value.ToArray(); CheckValidLoop(loop); outerLoop = loop; } } /// /// Gets the of inner loops (as of , /// for which each of the segments are connected to the next) of the . /// public IEnumerable InnerLoops { get { return innerLoops; } } /// /// Adds an inner loop to the geometry. /// /// The innerloop to add. /// Thrown when the in /// do not form a loop. internal void AddInnerLoop(IEnumerable innerLoop) { var loop = innerLoop.ToArray(); CheckValidLoop(loop); innerLoops.Add(loop); } /// /// Constructs a (1D) based on the and set for the . /// /// The point from which to take a 1D profile. /// The bottom level of the . /// A of . /// Thrown when any of the or /// contain a vertical line at . internal IEnumerable AsPipingSoilLayers(double atX, out double bottom) { bottom = Double.MaxValue; var result = new Collection(); if (OuterLoop != null) { IEnumerable outerLoopIntersectionHeights = GetLoopIntersectionHeights(outerLoop, atX); if (outerLoopIntersectionHeights.Any()) { IEnumerable> innerLoopsIntersectionHeights = InnerLoops.Select(loop => GetLoopIntersectionHeights(loop, atX)); IEnumerable> innerLoopIntersectionHeightPairs = GetOrderedStartAndEndPairsIn1D(innerLoopsIntersectionHeights).ToList(); IEnumerable> outerLoopIntersectionHeightPairs = GetOrderedStartAndEndPairsIn1D(outerLoopIntersectionHeights).ToList(); var 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 (var height in heights.Where(height => !innerLoopIntersectionHeightPairs.Any(tuple => HeightInInnerLoop(tuple, height)))) { result.Add(new PipingSoilLayer(height) { IsAquifer = IsAquifer.HasValue && IsAquifer.Value.Equals(1.0), BelowPhreaticLevel = BelowPhreaticLevel, AbovePhreaticLevel = AbovePhreaticLevel, DryUnitWeight = DryUnitWeight }); } bottom = EnsureBottomOutsideInnerLoop(innerLoopIntersectionHeightPairs, currentBottom); } } return result; } private static void CheckValidLoop(Segment2D[] innerLoop) { if (innerLoop.Length == 1 || !IsLoopConnected(innerLoop)) { throw new ArgumentException(Resources.SoilLayer2D_Error_Loop_contains_disconnected_segments); } } private static bool IsLoopConnected(Segment2D[] segments) { int segmentCount = segments.Length; if (segmentCount == 2) { return segments[0].Equals(segments[1]); } for (int i = 0; i < segmentCount; i++) { var segmentA = segments[i]; var segmentB = segments[(i + 1)%segmentCount]; if (!segmentA.IsConnected(segmentB)) { return false; } } return true; } private double EnsureBottomOutsideInnerLoop(IEnumerable> innerLoopIntersectionHeightPairs, double bottom) { var newBottom = bottom; var heigthPairArray = innerLoopIntersectionHeightPairs.ToList(); var overlappingInnerLoop = heigthPairArray.FirstOrDefault(t => BottomInInnerLoop(t, newBottom)); while (overlappingInnerLoop != null) { newBottom = overlappingInnerLoop.Item2; overlappingInnerLoop = heigthPairArray.FirstOrDefault(t => BottomInInnerLoop(t, newBottom)); } return newBottom; } private bool HeightInInnerLoop(Tuple tuple, double height) { return height <= tuple.Item2 && height > tuple.Item1; } private bool BottomInInnerLoop(Tuple tuple, double height) { return height < tuple.Item2 && height >= tuple.Item1; } private IEnumerable> GetOrderedStartAndEndPairsIn1D(IEnumerable> innerLoopsIntersectionPoints) { Collection> result = new Collection>(); foreach (var innerLoopIntersectionPoints in innerLoopsIntersectionPoints) { foreach (var tuple in GetOrderedStartAndEndPairsIn1D(innerLoopIntersectionPoints)) { result.Add(tuple); } } return result; } private static Collection> GetOrderedStartAndEndPairsIn1D(IEnumerable innerLoopIntersectionPoints) { var result = new Collection>(); var orderedHeights = innerLoopIntersectionPoints.OrderBy(v => v).ToList(); for (int i = 0; i < orderedHeights.Count; i = i + 2) { var first = orderedHeights[i]; var 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 IEnumerable GetLoopIntersectionHeights(IEnumerable loop, double atX) { var segment2Ds = loop.ToArray(); if (segment2Ds.Any(segment => IsVerticalAtX(segment, atX))) { var message = string.Format(Resources.Error_Can_not_determine_1D_profile_with_vertical_segments_at_X_0_, atX); throw new SoilLayer2DConversionException(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(); } } }