// Copyright (C) Stichting Deltares 2016. All rights reserved. // // This file is part of the Macro Stability kernel. // // The Macro Stability kernel is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser 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 Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser 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.Geometry; using Deltares.Geotechnics.Soils; using Deltares.Geotechnics.SurfaceLines; using Deltares.Mathematics; using Deltares.Standard.EventPublisher; using Deltares.Standard.Extensions; using Deltares.Standard.Language; using Deltares.Standard.Logging; using Deltares.Standard.Validation; namespace Deltares.Geotechnics.WaternetCreator { /// /// Automatic creation of the waternet according to method Ringtoets WTI 2017. /// Refer to the memo of Alexander van Duinen (1209434-012-GEO-0002-m-Schematisering waterspanningen in WTI 2017 (Ringtoets).docx) for more information: /// https://repos.deltares.nl/repos/FailureMechanisms/FailureMechanisms/DikesMacroStability/trunk/doc/Work /// public class WaternetCreatorWTI : IWaternetCreator { private Soil bottomAquifer; private SoilLayer1D bottomAquiferLayer; private Soil inBetweenAquifer; private SoilLayer1D inBetweenAquiferLayerBottom; private SoilLayer1D inBetweenAquiferLayerTop; private Location location; private readonly PlLinesCreatorWTI plLinesCreator = new PlLinesCreatorWTI(); private SoilProfile1D profile1D; private SoilProfile2D profile2D; private Waternet waternet; bool isPl4Created = false; /// /// Create a waternet based on a soil profile 2D, a surface line and the output from PL-lines creator, for different scenario's: /// Sand dike on sand has only phreatic line. /// Clay dike on sand have only a PL-line 2 on the top of first aquifer. /// The 2 other scenario's may have an in-between aquifer (PL-line 4) and an in-between aquitard (PL-line 2) /// /// The actual waternet /// The input parameters for waternet creator (Location class) /// The updated waternet public void UpdateWaternet(Waternet waternet, Location location) { if (!HasWaterLevel(location)) { LogManager.Add(new LogMessage(LogMessageType.Warning, this, "Cannot update waternet if no water level is available.")); return; } this.waternet = waternet; Dictionary assignedHeadLines = new Dictionary(); // Send events, used for undo manager if (waternet.PhreaticLine != null) { DataEventPublisher.BeforeChange(waternet.PhreaticLine.Points); waternet.PhreaticLine.Points.Clear(); } DataEventPublisher.BeforeChange(this.waternet.WaternetLineList); DataEventPublisher.BeforeChange(this.waternet.HeadLineList); this.waternet = waternet; this.waternet.IsGenerated = location.WaternetCreationMode == WaternetCreationMode.CreateWaternet; if (location.WaternetCreationMode == WaternetCreationMode.CreateWaternet) { this.waternet.WaternetLineList.Clear(); } else { foreach (WaternetLine waternetLine in this.waternet.WaternetLineList) { if (waternetLine.HeadLine != null) { assignedHeadLines[waternetLine] = waternetLine.HeadLine.Name; } } } this.waternet.HeadLineList.Clear(); plLinesCreator.Inwards = location.Inwards; plLinesCreator.UnitWeightWater = waternet.UnitWeight; AssignLocationParameters(location); profile1D = null; double xUsedToCreate1DProfile; if (location.SoilProfile2D != null) { profile2D = location.SoilProfile2D; GeometryPoint dikeTopRiver = location.Surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeTopAtRiver); GeometryPoint dikeTopPolder = location.Surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeTopAtRiver); if (dikeTopRiver != null && dikeTopPolder != null) { xUsedToCreate1DProfile = (dikeTopRiver.X + dikeTopPolder.X) / 2; } else { xUsedToCreate1DProfile = location.Surfaceline.Geometry.GetMinX(); } profile1D = location.SoilProfile2D.GetSoilProfile1D(xUsedToCreate1DProfile); } else { profile1D = location.SoilProfile1D; } // Create PL1 = Phreatic Line and add the surface as a waternet line CreatePhreaticLineAndAssignItToSurfaceLine(); // For "Sand dike on sand" (2B) only the phreatic line is present, so the waternet creation is finished if (location.DikeSoilScenario == DikeSoilScenario.SandDikeOnSand) { if (DetermineAquitards().Any()) { LogManager.Add(new LogMessage(LogMessageType.Warning, location, LocalizationManager.GetTranslatedText(typeof(WaternetCreator), "SandDikeOnSandWarning"))); } } else { // If no pleistoceen is found, the waternet creation is finished DetermineAquifers(profile1D); if (bottomAquiferLayer != null) { // For scenarios's 1A, 2A and 1B, Head 3 is created and assigned to the Pleistoceen. // However, if no aquitards are present, Head 3 is not created var aquitard = DetermineAquitards(); if (aquitard.Any()) { // For "Clay dike on sand (1B)", only 1 penetration zone (related to Head 2) is created at the bottom of the toppest aquitard. // Nothing happens with the other aquitards below. For "Clay/Sand dike on clay (1A and 2A), 2 extra penetration zones (related to Head 2) // are created if an in-between aquifer is present. CreatePl2AndAssignItToPenetrationZones(); if (!double.IsNaN(location.HeadInPLLine3)) { CreatePl3AndAssignItToDeepestAquifer(); // Only for "Clay/Sand dike on clay (1A and 2A), the in-between aquifer (if present) is related to Head 4 if (inBetweenAquiferLayerTop != null && inBetweenAquiferLayerBottom != null && !double.IsNaN(location.HeadInPLLine4)) { if (location.DikeSoilScenario != DikeSoilScenario.ClayDikeOnSand) { CreatePl4AndAssignItToInBetweenAquifer(); isPl4Created = true; } } } } } EnsureHydrostaticPorePressureInLayersWhereThePhreaticLinePasses(); } // Remove head lines which are not used if (location.WaternetCreationMode == WaternetCreationMode.CreateWaternet) { foreach (HeadLine headLine in waternet.HeadLineList.ToArray()) { if (waternet.WaternetLineList.All(p => p.HeadLine == null || p.HeadLine.Name != headLine.Name)) { waternet.HeadLineList.Remove(headLine); } } } // Restore assigned head lines if (location.WaternetCreationMode == WaternetCreationMode.CreatePhreaticAndHeadLinesOnly) { foreach (WaternetLine waternetLine in assignedHeadLines.Keys) { if (!waternet.HeadLineList.Contains(waternetLine.HeadLine) && waternetLine.HeadLine != waternet.PhreaticLine) { waternetLine.HeadLine = waternet.HeadLineList.FirstOrDefault(p => p.Name == assignedHeadLines[waternetLine]); } } } DataEventPublisher.AfterChange(waternet, "PhreaticLine"); DataEventPublisher.DataListModified(waternet.PhreaticLine.Points); DataEventPublisher.DataListModified(this.waternet.WaternetLineList); DataEventPublisher.DataListModified(this.waternet.HeadLineList); } /// /// Determines whether the waternet can be generated at the specified location . /// /// The location. /// Returns true if the waternet can be generated at the specified location ; otherwise flase. public bool CanGenerateWaternet(Location location) { return location.Validate().Where(v => v.MessageType == ValidationResultType.Error).ToArray().Length == 0; } private bool HasWaterLevel(Location location) { return !double.IsNaN(location.WaterLevelRiver); } private void AssignLocationParameters(Location location) { this.location = location; this.location.Surfaceline.SortPoints(); } /// /// Creates the phreatic line (PL 1) and assigns it to the surface line. /// private void CreatePhreaticLineAndAssignItToSurfaceLine() { const string phreaticLineName = "Phreatic Line"; const string surfaceLineName = "Surface line"; GeometryPointString str = plLinesCreator.CreatePLLine(PLLineType.PL1, location); if (waternet.PhreaticLine == null) { waternet.PhreaticLine = new PhreaticLine { Name = phreaticLineName }; } waternet.PhreaticLine.Points.AddRange(str.Points); WaternetLine line = this.waternet.WaternetLineList.FirstOrDefault(p => p.Name == surfaceLineName); if (location.WaternetCreationMode == WaternetCreationMode.CreateWaternet) { line = new WaternetLine { Name = surfaceLineName }; // Clone points for surface line, because they don't have the same behaviour (enabled) for (int i = 0; i < location.Surfaceline.Geometry.Points.Count; i++) { var point = location.Surfaceline.Geometry.Points[i]; if (!double.IsNaN(point.X)) { line.Points.Add((GeometryPoint)point.Clone()); } } waternet.AddWaternetLine(line); } if (line != null) { line.HeadLine = waternet.PhreaticLine; } } /// /// Ensures an hydrostatic pore pressure distribution in the layers where the phreatic line passes. /// To do this, extra waternet lines are eventually created by: /// - First, creating a list of all layers where the phretic line passes; /// - Then, creating a waternet line at the bottom of each listed layer only if no waternet line already exists. /// private void EnsureHydrostaticPorePressureInLayersWhereThePhreaticLinePasses() { var listOfLayersWherePhreaticLinePasses = GetListOfLayersWherePhreaticLinePasses(); GeometryPointString alreadyExistingWaternetLine; if (isPl4Created) { alreadyExistingWaternetLine = GetContourOfInBetweenClusterOfAquifers(); } else { alreadyExistingWaternetLine = GetAquiferGeometrySurface(true, GeometryTopBottom.Top); } List listOfCreatedLines = new List(); if (listOfLayersWherePhreaticLinePasses.Count > 0) { foreach (var layer in listOfLayersWherePhreaticLinePasses) { GeometryPointString bottomSurfaceOfCurrentLayer = layer.GeometrySurface.DetermineBottomGeometrySurface(); bool coincide = BottomOfLayerCoincidesWithTheRelevantExistingWaternetLine(layer, alreadyExistingWaternetLine); if (!coincide) { listOfCreatedLines.Add(bottomSurfaceOfCurrentLayer); } } } // if no waternet line already exists, a waternet line is created for each line listed in listOfCreatedLines. if (listOfCreatedLines.Count != 0) { int number = 1; foreach (var line in listOfCreatedLines) { string bottomHydrostaticName = "Bottom hydrostatic zone"; if (listOfCreatedLines.Count > 1) { bottomHydrostaticName = "Bottom hydrostatic zone (" + number + ")"; } number++; WaternetLine waternetLine = this.waternet.WaternetLineList.FirstOrDefault(p => p.Name == bottomHydrostaticName); if (location.WaternetCreationMode == WaternetCreationMode.CreateWaternet) { waternetLine = new WaternetLine { Name = bottomHydrostaticName }; // Clone points for (int i = 0; i < line.Points.Count; i++) { var point = line.Points[i]; if (!double.IsNaN(point.X)) { waternetLine.Points.Add((GeometryPoint)point.Clone()); } } waternet.AddWaternetLine(waternetLine); } if (waternetLine != null) { waternetLine.HeadLine = waternet.PhreaticLine; } } } } private List GetListOfLayersWherePhreaticLinePasses() { List listOfLayersWherePhreaticLinePasses = new List(); // First if (waternet.PhreaticLine.Points.Count >= 2) { for (int i = 0; i < waternet.PhreaticLine.Points.Count - 1; i++) { Point2D phreaticLinePoint1 = new Point2D { X = waternet.PhreaticLine.Points[i].X, Y = waternet.PhreaticLine.Points[i].Z }; Point2D phreaticLinePoint2 = new Point2D { X = waternet.PhreaticLine.Points[i + 1].X, Y = waternet.PhreaticLine.Points[i + 1].Z }; foreach (var layer in profile2D.Surfaces) { for (int j = 0; j < layer.GeometrySurface.OuterLoop.Points.Count - 1; j++) { Point2D layerPoint1 = new Point2D { X = layer.GeometrySurface.OuterLoop.Points[j].X, Y = layer.GeometrySurface.OuterLoop.Points[j].Z }; Point2D layerPoint2 = new Point2D { X = layer.GeometrySurface.OuterLoop.Points[j + 1].X, Y = layer.GeometrySurface.OuterLoop.Points[j + 1].Z }; var ip = new Point2D(); var res = Routines2D.DetermineIf2DLinesIntersectStrickly(phreaticLinePoint1, phreaticLinePoint2, layerPoint1, layerPoint2, ref ip); bool linesIntersect = (res == LineIntersection.Intersects); if (linesIntersect && !listOfLayersWherePhreaticLinePasses.Contains(layer)) { listOfLayersWherePhreaticLinePasses.Add(layer); } } } } } return listOfLayersWherePhreaticLinePasses; } /// /// Determines wheter the bottom of layer coincides with the relevant existing waternet line . /// /// The layer. /// The already existing waternet line. /// true if the bottom of coincides with the relevant existing waternet line /// . Otherwise returns false. private bool BottomOfLayerCoincidesWithTheRelevantExistingWaternetLine(SoilLayer2D layer, GeometryPointString alreadyExistingWaternetLine) { GeometryPointString bottomSurfaceOfCurrentLayer = layer.GeometrySurface.DetermineBottomGeometrySurface(); var numberOfCommonPoints = 0; // Check if surface bottomSurfaceOfCurrentLayer as common points with the existingWaternelLine. if (alreadyExistingWaternetLine != null) { for (int j = 0; j < bottomSurfaceOfCurrentLayer.Count; j++) { var pointBottomSurface = bottomSurfaceOfCurrentLayer[j]; for (int k = 0; k < alreadyExistingWaternetLine.Count; k++) { var pointWaternetLine = alreadyExistingWaternetLine[k]; if ((Math.Abs(pointWaternetLine.X - pointBottomSurface.X) < GeometryConstants.Accuracy) && (Math.Abs(pointWaternetLine.Y - pointBottomSurface.Y) < GeometryConstants.Accuracy) && (Math.Abs(pointWaternetLine.Z - pointBottomSurface.Z) < GeometryConstants.Accuracy)) { numberOfCommonPoints++; } } } } if (numberOfCommonPoints >= 2) { return true; } else { return false; } } /// /// Create PL2 (i.e. Head 2) and: /// - For "Clay dike on clay (1A)" and "Sand dike on clay (2B)", create a penetration zone (with nr. 1) above the Pleistocene /// and assign headline Head 2 to this penetration zone. If an in-between cluster of aquifers is present, 2 extra penetration zones are created /// (nr. 2 and 3) around the in-between cluster of aquifers. /// - For "Clay dike on sand (1B)", create only 1 penetration zone (with nr. 1) above the toppest aquifer layer (aquifer layers situated /// in the dike are not relevant). /// private void CreatePl2AndAssignItToPenetrationZones() { const string penetrationZone1Name = "Penetration zone 1"; const string penetrationZone2Name = "Penetration zone 2"; const string penetrationZone3Name = "Penetration zone 3"; double penetrationLength = location.PenetrationLength; GeometryPointString pl2 = plLinesCreator.CreatePLLine(PLLineType.PL2, location); if (pl2.Points.Count > 0) { GeometryPointString aquiferGeometryTopBelowPenetrationZone1 = GetAquiferGeometrySurface(true, GeometryTopBottom.Top); WaternetLine line2 = this.waternet.WaternetLineList.FirstOrDefault(p => p.Name == penetrationZone1Name); if (location.WaternetCreationMode == WaternetCreationMode.CreateWaternet) { line2 = new WaternetLine { Name = penetrationZone1Name }; if (aquiferGeometryTopBelowPenetrationZone1 != null) { line2.Points.AddRange(aquiferGeometryTopBelowPenetrationZone1.Points); foreach (var geometryPoint in line2.Points) { geometryPoint.Z = geometryPoint.Z + penetrationLength; } RemovePlLinePointsAboveSurfaceLine(line2); } waternet.AddWaternetLine(line2); } HeadLine headLine2 = null; if (aquiferGeometryTopBelowPenetrationZone1 != null) { headLine2 = new HeadLine { Name = "Head 2" }; headLine2.Points.Clear(); headLine2.Points.AddRange(pl2.Points); waternet.HeadLineList.Add(headLine2); if (line2 != null) { line2.HeadLine = headLine2; } } // For "Clay dike on clay (1A)" and "Sand dike on clay (2B)", if an in-between cluster of aquifers is present, // 2 extra penetration zones are created (nr. 2 and 3) around the in-between cluster of aquifers. if (location.DikeSoilScenario != DikeSoilScenario.ClayDikeOnSand) { if (inBetweenAquiferLayerTop != null && inBetweenAquiferLayerBottom != null) { var inBetweenAquiferGeometryTop = GetAquiferGeometrySurface(false, GeometryTopBottom.Top); var inBetweenAquiferGeometryBottom = GetAquiferGeometrySurface(false, GeometryTopBottom.Bottom); WaternetLine line2B = this.waternet.WaternetLineList.FirstOrDefault(p => p.Name == penetrationZone3Name); if (location.WaternetCreationMode == WaternetCreationMode.CreateWaternet) { line2B = new WaternetLine { Name = penetrationZone3Name }; line2B.Points.AddRange(inBetweenAquiferGeometryTop.Points); foreach (var geometryPoint in line2B.Points) { geometryPoint.Z = geometryPoint.Z + penetrationLength; } RemovePlLinePointsAboveSurfaceLine(line2B); waternet.AddWaternetLine(line2B); } if (line2B != null) { line2B.HeadLine = headLine2; } WaternetLine line2C = this.waternet.WaternetLineList.FirstOrDefault(p => p.Name == penetrationZone2Name); if (location.WaternetCreationMode == WaternetCreationMode.CreateWaternet) { line2C = new WaternetLine { Name = penetrationZone2Name }; line2C.Points.AddRange(inBetweenAquiferGeometryBottom.Points); foreach (var geometryPoint in line2C.Points) { geometryPoint.Z = geometryPoint.Z - penetrationLength; } waternet.AddWaternetLine(line2C); } if (line2C != null) { line2C.HeadLine = headLine2; } } } } } /// /// Remove the points situated "in the air" (i.e. above surface line) for the current waternet line /// /// the current waternet line private void RemovePlLinePointsAboveSurfaceLine(WaternetLine waternetLine) { var intersectionPointsBetweenPenetrationZone1AndSurfaceLine = DetermineIntersectionPointsBetweenWaternetLineAndSurfaceLine(location.Surfaceline, waternetLine); if (intersectionPointsBetweenPenetrationZone1AndSurfaceLine != null) { foreach (var geometryPoint in intersectionPointsBetweenPenetrationZone1AndSurfaceLine) { waternetLine.Points.Add(geometryPoint); } waternetLine.Points.Sort(); for (int i = 0; i < waternetLine.Count; i++) { var zSurfaceLine = location.Surfaceline.Geometry.GetZAtX(waternetLine.Points[i].X); // if Z co-ordinate are very close, this means that it is an intersection point, so the point is not removed if ((Math.Abs(waternetLine.Points[i].Z - zSurfaceLine) > 0.00000001) && (waternetLine.Points[i].Z > zSurfaceLine)) { waternetLine.Points.Remove(waternetLine.Points[i]); i--; } } } } /// /// Create PL3 (i.e. Head 3) and assign it to the top surface of the deepest cluster of aquifers /// private void CreatePl3AndAssignItToDeepestAquifer() { const string pl3Name = "pl3"; GeometryPointString pl3 = plLinesCreator.CreatePLLine(PLLineType.PL3, location); if (pl3 != null) { WaternetLine line3 = this.waternet.WaternetLineList.FirstOrDefault(p => p.Name == pl3Name); if (location.WaternetCreationMode == WaternetCreationMode.CreateWaternet) { line3 = new WaternetLine { Name = pl3Name }; // The waternet corresonding to Head 3 is always the top surface of the deepest cluster of aquifers GeometryPointString bottomAquiferGeometryTop = GetAquiferGeometrySurface(true, GeometryTopBottom.Top); line3.Points.AddRange(bottomAquiferGeometryTop.Points); waternet.AddWaternetLine(line3); } HeadLine headLine3 = new HeadLine { Name = "Head 3" }; headLine3.Points.Clear(); headLine3.Points.AddRange(pl3.Points); waternet.HeadLineList.Add(headLine3); if (line3 != null) { line3.HeadLine = headLine3; } } } /// /// Create PL4 (i.e. Head 4) and assign it to the 'in between aquifer' when present. /// private void CreatePl4AndAssignItToInBetweenAquifer() { const string pl4Name = "In between aquifer"; const string pl4BName = "In between aquifer B"; GeometryPointString pl4 = plLinesCreator.CreatePLLine(PLLineType.PL4, location); if (pl4 != null) { if (pl4.Points.Count > 0) { WaternetLine line4 = this.waternet.WaternetLineList.FirstOrDefault(p => p.Name == pl4Name); if (location.WaternetCreationMode == WaternetCreationMode.CreateWaternet) { line4 = new WaternetLine { Name = pl4Name }; if (profile2D != null) { var points = new List(); var contour = GetContourOfInBetweenClusterOfAquifers(); points.AddRange(contour.Points); line4.Points.AddRange(points); } else { var zLevelTop = GetZLevelAquifer1DProfile(GeometryTopBottom.Top, false); var pointstring = CreateGeometryPointString(zLevelTop); line4.Points.AddRange(pointstring.Points); } waternet.AddWaternetLine(line4); } HeadLine headLine4 = new HeadLine { Name = "Head 4" }; headLine4.Points.Clear(); headLine4.Points.AddRange(pl4.Points); waternet.HeadLineList.Add(headLine4); if (line4 != null) { line4.HeadLine = headLine4; } if (profile2D == null) { WaternetLine line4B = this.waternet.WaternetLineList.FirstOrDefault(p => p.Name == pl4BName); if (location.WaternetCreationMode == WaternetCreationMode.CreateWaternet) { line4B = new WaternetLine { Name = pl4BName }; var zLevelBottom = GetZLevelAquifer1DProfile(GeometryTopBottom.Bottom, false); var pointstringB = CreateGeometryPointString(zLevelBottom); line4B.Points.AddRange(pointstringB.Points); waternet.AddWaternetLine(line4B); } if (line4B != null) { line4B.HeadLine = headLine4; } } } } } /// /// If IsBottomAquifer = true, get the top or bottom geometry surface of the toppest aquifer in the deep cluster of aquifers /// If IsBottomAquifer = false, get the top or bottom geometry surface of the toppest aquifer in the in-between cluster of aquifers /// /// Choose between true (the toppest aquifer in the deep cluster of aquifers) or false /// (toppest aquifer in the in-between cluster of aquifers) /// Choose between GeometryTopBottom.Top and GeometryTopBottom.Bottom /// The geometry surface (top or bottom) of the selected aquifer (bottom or in-between) private GeometryPointString GetAquiferGeometrySurface(bool isBottomAquifer, GeometryTopBottom topBottom) { if (profile2D != null) { GeometrySurface aquiferSurface; if (isBottomAquifer) { aquiferSurface = GeometrySurfaceAquifer(true, true); } else { if (topBottom == GeometryTopBottom.Top) { aquiferSurface = GeometrySurfaceAquifer(false, true); } else { aquiferSurface = GeometrySurfaceAquifer(false, false); } } if (aquiferSurface != null) { GeometryPointString result; if (topBottom == GeometryTopBottom.Top) { result = aquiferSurface.DetermineTopGeometrySurface(); } else { result = aquiferSurface.DetermineBottomGeometrySurface(); } return result; } else { return null; } } else { var zLevel = GetZLevelAquifer1DProfile(topBottom, isBottomAquifer); return CreateGeometryPointString(zLevel); } } /// /// Get the contour of the highest cluster of in-between aquifers, used to create the waternet line PL4 associated to Head 4. /// Note that an aquifer is always a complete layer (form left to right geometry boundary), /// not an isolated layer (see ValidateAquifers in Location.cs). /// /// List of geometry points (closed polygone) private GeometryPointString GetContourOfInBetweenClusterOfAquifers() { GeometryPointString result; if (profile2D != null) { GeometrySurface geometrySurfaceTopLayer = GeometrySurfaceAquifer(false, true); GeometrySurface geometrySurfaceBottomLayer = GeometrySurfaceAquifer(false, false); if (inBetweenAquiferLayerTop != null && inBetweenAquiferLayerBottom != null) { GeometryPointString pointsTop = geometrySurfaceTopLayer.DetermineTopGeometrySurface(); GeometryPointString pointsBottom = geometrySurfaceBottomLayer.DetermineBottomGeometrySurface(); pointsTop.SortPointsByXAscending(); pointsBottom.Points.Sort(); result = pointsTop; pointsBottom.Points.Sort(); for (int i = pointsBottom.Count - 1; i >= 0; i--) { result.Points.Add(pointsBottom[i]); } // add again the first point of the top surface to close the contour result.Points.Add(pointsTop[0]); return result; } else { return null; } } else { var zLevelTop = GetZLevelAquifer1DProfile(GeometryTopBottom.Top, false); var zLevelBottom = GetZLevelAquifer1DProfile(GeometryTopBottom.Bottom, false); GeometryPointString pointsTop = CreateGeometryPointString(zLevelTop); GeometryPointString pointsBottom = CreateGeometryPointString(zLevelBottom); result = pointsTop; foreach (var geometryPoint in pointsBottom.Points) { result.Points.Add(geometryPoint); } return result; } } /// /// Get the top surface of the highest cluster of in-between aquifers. /// /// List of geometry points (surface) private GeometryPointString GetTopSurfaceOfInBetweenClusterOfAquifers() { GeometryPointString result; if (profile2D != null) { GeometrySurface geometrySurfaceTopLayer = GeometrySurfaceAquifer(false, true); if (inBetweenAquiferLayerTop != null && inBetweenAquiferLayerBottom != null) { GeometryPointString pointsTop = geometrySurfaceTopLayer.DetermineTopGeometrySurface(); pointsTop.SortPointsByXAscending(); return pointsTop; } else { return null; } } else { var zLevelTop = GetZLevelAquifer1DProfile(GeometryTopBottom.Top, false); GeometryPointString pointsTop = CreateGeometryPointString(zLevelTop); result = pointsTop; return result; } } /// /// If isBottomAquifer = true, get the top or bottom level of the toppest aquifer in the deep cluster of aquifers /// If isBottomAquifer = false, get the top or bottom level of the toppest aquifer in the in-between cluster of aquifers /// /// Choose between GeometryTopBottom.Top and GeometryTopBottom.Bottom /// Choose between true (the toppest aquifer in the deep cluster of aquifers) or false /// (toppest aquifer in the in-between cluster of aquifers) /// The level of the selected aquifer (bottom or in-between) private double GetZLevelAquifer1DProfile(GeometryTopBottom topBottom, bool isBottomAquifer) { double zLevel; if (isBottomAquifer) { if (topBottom == GeometryTopBottom.Top) { zLevel = profile1D.Layers.Where(l => l.Soil == bottomAquifer) .Select(l => l.TopLevel) .FirstOrDefault(); } else { zLevel = profile1D.Layers.Where(l => l.Soil == bottomAquifer) .Select(l => l.BottomLevel) .FirstOrDefault(); } } else { if (topBottom == GeometryTopBottom.Top) { zLevel = profile1D.Layers.Where(l => l.Soil == inBetweenAquifer) .Select(l => l.TopLevel) .FirstOrDefault(); } else { zLevel = profile1D.Layers.Where(l => l.Soil == inBetweenAquifer) .Select(l => l.BottomLevel) .FirstOrDefault(); } } return zLevel; } private GeometryPointString CreateGeometryPointString(double zLevel) { var pointString = new GeometryPointString(); var maxX = location.Surfaceline.Geometry.Points.Max(p => p.X); var minX = location.Surfaceline.Geometry.Points.Min(p => p.X); pointString.Points.Add(new GeometryPoint(minX, 0, zLevel)); pointString.Points.Add(new GeometryPoint(maxX, 0, zLevel)); return pointString; } /// /// Get the geometry surface of: /// - If = true, the highest aquifer in the deep cluster of aquifers /// - If = false, the toppest aquifer in the in-between cluster of aquifers /// using the 1D profile generated at the middle of Dike Top. /// /// Choose between true (the toppest aquifer in the deep cluster of aquifers) or false /// (toppest aquifer in the in-between cluster of aquifers) /// if set to true [is top in between aquifer]. /// /// The geometry surface of the selected aquifer (bottom or in-between) /// /// No soil profile is present. private GeometrySurface GeometrySurfaceAquifer(bool isBottomAquifer, bool isTopInBetweenAquifer) { SoilLayer1D relevantAquiferLayerIn1DProfile = null; double xDikeTopAtPolder = location.Surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeTopAtPolder).X; double xDikeTopAtRiver = location.Surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeTopAtRiver).X; double xUsed = (xDikeTopAtRiver + xDikeTopAtPolder) / 2; SoilProfile1D soilProfile1DBelowDikeTop = location.SoilProfile2D != null ? location.SoilProfile2D.GetSoilProfile1D(xUsed) : location.SoilProfile1D; if (soilProfile1DBelowDikeTop == null) { throw new System.Exception("No soil profile is present."); } if (isBottomAquifer) { // Deepest cluster of sand (Pleistocene) relevantAquiferLayerIn1DProfile = soilProfile1DBelowDikeTop.BottomAquiferLayer; } else { // Intermediate cluster of sand layers if (isTopInBetweenAquifer) { relevantAquiferLayerIn1DProfile = soilProfile1DBelowDikeTop.InBetweenAquiferLayer; } else { relevantAquiferLayerIn1DProfile = soilProfile1DBelowDikeTop.BottomLayerOfInBetweenAquiferCluster; } } if (relevantAquiferLayerIn1DProfile != null) { var middlePoint = new GeometryPoint(); middlePoint.X = xDikeTopAtPolder; middlePoint.Z = (relevantAquiferLayerIn1DProfile.TopLevel + relevantAquiferLayerIn1DProfile.BottomLevel)/2; var relevantAquiferLayerIn2DProfile = profile2D.GetSoilLayer(middlePoint); return relevantAquiferLayerIn2DProfile.GeometrySurface; } else { return null; } } /// /// Determine present aquifers within soil profile 1D / 2D. /// If several in-between aquifer clusters are present, the highest cluster is used: InBetweenAquifer is the highest aquifer of the selected cluster and /// BottomLayerOfInBetweenAquiferCluster is the lowest aquifer of the selected cluster. /// If the deep cluster of aquifers contains several aquifers, the highest aquifer is used as BottomAquifer. /// /// The soil profile 1D used to determine the InBetweenAquifer and the BottomAquifer private void DetermineAquifers(SoilProfile1D currentProfile1D) { bottomAquiferLayer = currentProfile1D.BottomAquiferLayer; if (bottomAquiferLayer == null) { return; } inBetweenAquiferLayerTop = currentProfile1D.InBetweenAquiferLayer; inBetweenAquiferLayerBottom = currentProfile1D.BottomLayerOfInBetweenAquiferCluster; } /// /// determine present aquitards within soil profile 1D / 2D /// private List DetermineAquitards() { var aquitards = new List(); if (profile2D != null) { //IsAquifer is defined on SoilLayer now, not on Soil aquitards.AddRange(profile2D.Surfaces.Where(s => s.IsAquifer == false).Select(s => s.Soil)); // aquitards = profile2D.Surfaces.Where(s => s.IsAquifer == false).Select(s => s.Soil); } else { aquitards.AddRange(profile1D.Layers.Where(s => s.IsAquifer == false).Select(s => s.Soil)); } return aquitards; } /// /// Finds the intersection point of a waternet line and a surface line. /// /// The evaluated surfaceline. /// The waternet line. /// The list of the intersection points, or null in case no intersection was found. private static List DetermineIntersectionPointsBetweenWaternetLineAndSurfaceLine(SurfaceLine2 surfaceLine, WaternetLine waternetLine) { var listSurfaceLinePoints = surfaceLine.Geometry.Points; var listOfIntersectionPointsFound = new List(); for (int i = 1; i < listSurfaceLinePoints.Count; i++) { var surfaceLineSegment = new Line(); surfaceLineSegment.SetBeginAndEndPoints( new GeometryPoint(listSurfaceLinePoints[i - 1].X, 0, listSurfaceLinePoints[i - 1].Z), new GeometryPoint(listSurfaceLinePoints[i].X, 0, listSurfaceLinePoints[i].Z)); for (int j = 1; j < waternetLine.Count; j++) { var waternetLineSegment = new Line(); waternetLineSegment.SetBeginAndEndPoints( new GeometryPoint(waternetLine[j - 1].X, 0, waternetLine[j - 1].Z), new GeometryPoint(waternetLine[j].X, 0, waternetLine[j].Z)); var intersectPoint = surfaceLineSegment.GetIntersectPointXZ(waternetLineSegment); if (intersectPoint != null) { listOfIntersectionPointsFound.Add(intersectPoint); } } } if (listOfIntersectionPointsFound.Count != 0) { return listOfIntersectionPointsFound; } return null; } } }