// 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.Standard.Extensions;
namespace Deltares.Geotechnics.WaternetCreator
{
///
/// This class creates 4 PL-lines (PL1, PL2, PL3 and PL4) for 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 PlLinesCreatorWTI
{
private const double cUpliftFactorEquilibrium = 1.0;
private const double cEpsilon = 1.0e-3;
private const double cOffsetPhreaticLineBelowSurface = 0.01;
private const int cRoundDecimals = 6;
private double HeadAtDikeToePolderPlus20M;
private double HeadAtEndOfGeometry;
private GeometryPointString phreaticLine;
private GeometryPointString Pl2;
///
/// Initializes a new instance of the class.
///
public PlLinesCreatorWTI()
{
InitPLLinesCreator();
}
///
/// Gets or sets a value indicating whether oven dry unit weight is used.
///
///
/// true if oven dry unit weight is used; otherwise, false.
///
public bool IsUseOvenDryUnitWeight { get; set; }
///
/// Gets or sets the type of the soil geometry.
///
///
/// The type of the soil geometry.
///
public SoilGeometryType SoilGeometryType { get; set; }
///
/// Gets or sets the dike embankment material.
///
///
/// The dike embankment material.
///
public Soil DikeEmbankmentMaterial { get; set; }
///
/// Gets or sets the head in PL-line 2.
///
///
/// The head in PL-line 2.
///
public double? HeadInPLLine2 { get; set; }
///
/// Gets or sets the unit weight water.
///
///
/// The unit weight water.
///
public double UnitWeightWater { get; set; }
///
/// Gets or sets a value indicating whether this is inwards.
///
///
/// true if inwards; otherwise, false.
///
public bool Inwards { get; set; }
///
/// Create 4 PL-lines for the waternet creator of Ringtoets WTI 2017.
///
/// Enter the PL type you want to create.
/// Class containing all hydraulic data needed for the creation of PL-lines.
/// All the points of the desired PL-line.
public GeometryPointString CreatePLLine(PLLineType plLineType, Location location)
{
GeometryPointString plLine = null;
switch (plLineType)
{
case PLLineType.PL1:
plLine = CreatePLLine1(location);
break;
case PLLineType.PL2:
plLine = CreatePLLine2(location);
break;
case PLLineType.PL3:
plLine = CreatePLLine3(location);
break;
case PLLineType.PL4:
plLine = CreatePLLine4(location);
break;
}
if (plLine != null)
{
LineHelper.DeleteCoinsidingPoints(plLine.Points, 0.0001);
}
return plLine;
}
///
/// Create PL2 (i.e. PL-line at the penetration zone level in clay)
///
/// PL-line 2 (also called Head 2)
public GeometryPointString CreatePLLine2(Location location)
{
GeometryPointString plLine = null;
if ((location.PenetrationLength.AlmostEquals(0.0)) || Double.IsNaN(location.HeadInPLLine2Outwards))
{
// No penetration, or no Head Pl2 defined, so empty pl-line will be returned
plLine = new GeometryPointString();
}
else
{
plLine = CreatePLLine2For1DGeometry(location);
}
return plLine;
}
///
/// Create PL-line 3 (= head in the Pleistocene)
///
/// PL-line 3
public GeometryPointString CreatePLLine3(Location location)
{
switch (location.DikeSoilScenario)
{
case DikeSoilScenario.ClayDikeOnClay:
case DikeSoilScenario.SandDikeOnClay:
return CreatePLLine3Or4ForDikesOnClay(location.HeadInPLLine3, PLLineType.PL3, location);
case DikeSoilScenario.ClayDikeOnSand:
return createPLLine3ForClayDikeOnSand(location);
}
return null;
}
///
/// Create PL-line 4 (= head in the in-between aquifer)
///
/// PL-line 4
public GeometryPointString CreatePLLine4(Location location)
{
switch (location.DikeSoilScenario)
{
case DikeSoilScenario.ClayDikeOnClay:
case DikeSoilScenario.SandDikeOnClay:
return CreatePLLine3Or4ForDikesOnClay(location.HeadInPLLine4, PLLineType.PL4, location);
}
return null;
}
///
/// Determines the intersection between two line segments:
/// - Horizontal water level
/// - Surface line (until DikeTopAtRiver)
///
/// Water Level at River side
/// The surface line
/// Intersection point between horizontal water level and surface line
public GeometryPoint DetermineIntersectionBetweenWaterLevelAndDike(double waterLevelRiver,
SurfaceLine2 surfaceline)
{
GeometryPoint pointAtLevel = DetermineIntersectionBetweenTaludRiverSideAndWaterLevel(waterLevelRiver,
surfaceline);
if (pointAtLevel != null)
{
return new GeometryPoint(pointAtLevel.X, 0, pointAtLevel.Z);
}
return null;
}
///
/// Determines the intersection between talud river side and water level.
/// Search starts from DikeTopAtRiver to SurfaceLevelOutside
///
/// The water level or z value of the horizontal line
///
/// An intersection point if any null otherswise
public GeometryPoint DetermineIntersectionBetweenTaludRiverSideAndWaterLevel(double level, SurfaceLine2 surfaceline)
{
var waterlevelLine = new Line();
double xStart = surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.SurfaceLevelOutside).X;
double xEnd = surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.SurfaceLevelInside).X;
waterlevelLine.CreateHorizontalZLine(xStart, xEnd, level);
ThrowWhenWaterLevelAboveDike(level, surfaceline);
var relevantPart = new GeometryPointString();
relevantPart.Points.AddRange(surfaceline.Geometry.Points.Where(p => !double.IsNaN(p.X)));
double x = relevantPart.GetXAtZ(level);
if (x <= surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeTopAtRiver).X + cEpsilon)
{
return new GeometryPoint(x, 0, level);
}
else
{
return null;
}
}
///
/// Determines the intersection between PL2 or PL1 and the final slope (1:100) of PL3.
///
/// The first point of oblique line.
/// The slope of oblique line.
/// The location.
/// The intersection point.
public GeometryPoint DetermineIntersectionBetweenPl2Or1AndFinalSlope100OfPl3(GeometryPoint firstPointOfObliqueLine, double slopeOfObliqueLine, Location location)
{
// Creation of the oblique line with a slope of slopeOfObliqueLine (1:100)
var obliqueLineWithSlope100 = new Line();
const double deltaX = 10000;
double signSlope;
if (Pl2 != null)
{
if (CalculateHeadPl2AtX(location, firstPointOfObliqueLine.X) < firstPointOfObliqueLine.Z)
{
signSlope = -1;
}
else
{
signSlope = 1;
}
}
else
{
if (phreaticLine.GetZAtX(firstPointOfObliqueLine.X) < firstPointOfObliqueLine.Z)
{
signSlope = -1;
}
else
{
signSlope = 1;
}
}
double xFirstPointOfObliqueLine = firstPointOfObliqueLine.X - deltaX;
double zFirstPointOfObliqueLine = firstPointOfObliqueLine.Z + signSlope * (xFirstPointOfObliqueLine - firstPointOfObliqueLine.X) / slopeOfObliqueLine;
double xLastPointOfObliqueLine = firstPointOfObliqueLine.X + deltaX;
double zLastPointOfObliqueLine = firstPointOfObliqueLine.Z + signSlope * (xLastPointOfObliqueLine - firstPointOfObliqueLine.X) / slopeOfObliqueLine;
obliqueLineWithSlope100.SetBeginAndEndPoints(new GeometryPoint(xFirstPointOfObliqueLine, 0, zFirstPointOfObliqueLine),
new GeometryPoint(xLastPointOfObliqueLine, 0, zLastPointOfObliqueLine));
// Creation of the relevant part of PL2 (or PL1 if PL2 is not present)
var segmentPl2OrPl1 = new Line();
if (Pl2 != null)
{
double x1 = firstPointOfObliqueLine.X - deltaX;
double x2 = firstPointOfObliqueLine.X + deltaX;
double z1 = CalculateHeadPl2AtX(location, x1);
double z2 = CalculateHeadPl2AtX(location, x2);
segmentPl2OrPl1.SetBeginAndEndPoints(new GeometryPoint(x1, 0, z1), new GeometryPoint(x2, 0, z2));
GeometryPoint intersectPoint = LineHelper.GetIntersectPoint(segmentPl2OrPl1, obliqueLineWithSlope100, PointType.XZ);
if (!double.IsNaN(intersectPoint.X))
{
return new GeometryPoint(intersectPoint.X, 0, intersectPoint.Z);
}
}
else
{
GeometryPointString relevantPartOfPl1 = phreaticLine.GetPart(firstPointOfObliqueLine.X, firstPointOfObliqueLine.X + deltaX);
for (int index = 0; index < relevantPartOfPl1.Points.Count - 1; index++)
{
segmentPl2OrPl1 = new Line(relevantPartOfPl1[index], relevantPartOfPl1[index + 1]);
GeometryPoint intersectPoint = LineHelper.GetIntersectPoint(segmentPl2OrPl1, obliqueLineWithSlope100, PointType.XZ);
if (!double.IsNaN(intersectPoint.X))
{
return new GeometryPoint(intersectPoint.X, 0, intersectPoint.Z);
}
}
}
return null;
}
///
/// Determines if values and are equal, with a tolerance of .
///
/// a value1.
/// a value2.
/// true if values and are equal; otherwise false.
public static bool Equal(double aValue1, double aValue2)
{
if (Math.Abs(aValue1 - aValue2) < cEpsilon)
{
return true;
}
return false;
}
///
/// Create PL1 (= phreatic level)
///
/// PL1 (= phreatic level)
public GeometryPointString CreatePLLine1(Location location)
{
GeometryPoint dikeTopAtRiver = location.Surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeTopAtRiver);
GeometryPoint dikeTopAtPolder = location.Surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeTopAtPolder);
// Create Phreatic line and add polder water level
double startXCoordinate =
location.Surfaceline.Geometry.Points.Where(p => !double.IsNaN(p.X)).OrderBy(p => p.X).First().X;
var line = new GeometryPointString();
double waterLevel;
if (Inwards)
{
waterLevel = location.WaterLevelRiver;
}
else
{
waterLevel = location.WaterLevelRiverLow;
}
// Water level at river side is supposed to be at level of SurfaceLevelOutside when waterlevel is below SurfaceLevelOutside (is the bottom of the river)
if (!double.IsNaN(EnsureWaterLevelIsAboveRiverBottom(waterLevel, location.Surfaceline)))
{
waterLevel = EnsureWaterLevelIsAboveRiverBottom(waterLevel, location.Surfaceline);
//location.WaterLevelRiver = waterLevelRiver;
}
line.Points.Add(new GeometryPoint(startXCoordinate, 0, waterLevel));
GeometryPoint intersectionPhreaticDike = null;
// Determine intersection between the Phreatic Level and the berm (or dike) at river side
intersectionPhreaticDike = DetermineIntersectionBetweenWaterLevelAndDike(waterLevel, location.Surfaceline);
if (intersectionPhreaticDike == null)
{
string message = "DetermineIntersectionBetweenWaterLevelAndDike failed";
throw new System.Exception(message);
}
// Add intersection point (i.e. point A of figure 19 handleiding DAM 1.0 - part C) to phreatic line list if not already in list.
if (!line.Points.Contains(intersectionPhreaticDike))
{
line.Points.Add(intersectionPhreaticDike);
}
// Complete phreatic line with points below dike crest (i.e. dike top at river and dike top at polder)
CreatePhreaticLineSegmentsInsideDikeForRingtoetsWti2017(line, location);
CreatePhreaticLineSegmentsInShoulderAndPolderForRingtoetsWti2017(line, location);
// Check if phreatic line is above
line.CondensePoints();
ValidatePhreaticAboveWaterLevelPolder(line, location);
// Check if phreatic line is below the surface line
ValidatePhreaticLineBelowDike(line, location);
// For Ringtoets WTI 2017 method, both points at dike top have a minimum value
line.CondensePoints();
line.SortPointsByXAscending();
// First dike top at river is created for all scenario's
double interpolatedLevelDikeTopRiver = line.GetZAtX(dikeTopAtRiver.X);
double waterLevelBelowDikeTopAtRiver = Math.Max(interpolatedLevelDikeTopRiver, location.MinimumLevelPhreaticLineAtDikeTopRiver);
line.Points.Add(new GeometryPoint(dikeTopAtRiver.X, 0, waterLevelBelowDikeTopAtRiver));
line.SortPointsByXAscending();
// Then dike top at polder is created only for sand dikes (for clay dikes it was already created
// in procedure CreatePhreaticLineSegmentsInsideDikeForRingtoetsWti2017)
if ((location.DikeSoilScenario == DikeSoilScenario.SandDikeOnClay || location.DikeSoilScenario == DikeSoilScenario.SandDikeOnSand)
&& location.UseDefaultOffsets)
{
double interpolatedLevelDikeTopPolder = line.GetZAtX(dikeTopAtPolder.X);
double waterLevelBelowDikeTopAtPolder = Math.Max(interpolatedLevelDikeTopPolder, location.MinimumLevelPhreaticLineAtDikeTopPolder);
line.Points.Add(new GeometryPoint(dikeTopAtPolder.X, 0, waterLevelBelowDikeTopAtPolder));
line.SortPointsByXAscending();
}
ValidatePhreaticLineBelowDike(line, location);
// remove all double points
line.CondensePoints();
phreaticLine = line;
return line;
}
///
/// Make sure that river level is above the bottom of the river
///
///
///
/// bottom of the river if it is above riverLevel, else riverLevel
public double EnsureWaterLevelIsAboveRiverBottom(double riverLevel, SurfaceLine2 surfaceline)
{
// Waterlevel is supposed to be at level of SurfaceLevelOutside when waterlevel
// is below SurfaceLevelOutside (is the bottom of the river)
if (!double.IsNaN(riverLevel))
{
double z = surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.SurfaceLevelOutside).Z;
riverLevel = Math.Max(z, riverLevel);
}
return riverLevel;
}
///
/// If shoulder is present then the toe of the shoulder is returned
/// Else the toe of the dike (ShoulderSaseInside) is returned
///
///
/// toe of the dike
public GeometryPoint GetDikeToeInward(SurfaceLine2 surfaceline)
{
if (surfaceline.IsDefined(CharacteristicPointType.ShoulderBaseInside))
{
return surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.ShoulderBaseInside);
}
return surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeToeAtPolder);
}
///
/// Initialize
///
private void InitPLLinesCreator()
{
IsUseOvenDryUnitWeight = false;
}
///
/// Get soil profile 1D below using .
///
/// Soil profile 2D
/// X coordinate
/// The created soil profile 1D
private SoilProfile1D GetSoilProfileBelowPoint(SoilProfile2D soilProfile2D, double xCoordinate)
{
return soilProfile2D.GetSoilProfile1D(xCoordinate);
}
///
/// Create PL2 (is PL-line for penetration) for 1D-geometry: the soil geometry at Dike Top at Polder is used and PL2 is created
/// only if an aquitard above an aquifer is found. If created, PL-line 2 is a straight line from left to right.
///
/// Class containing all the hydraulic data's
/// PL-line 2
private GeometryPointString CreatePLLine2For1DGeometry(Location location)
{
SoilProfile1D actualSoilProfile = location.SoilProfile2D != null
? GetSoilProfileBelowPoint(location.SoilProfile2D,
location.Surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeTopAtPolder).X)
: location.SoilProfile1D;
var plLine = new GeometryPointString();
if (actualSoilProfile != null)
{
IList aquiferLayers = actualSoilProfile.GetAquiferLayers();
// ROB: Add validation method to check this
if (aquiferLayers.Count == 0)
{
string message = "Soil profile contains no aquifer layers.";
throw new System.Exception(message);
}
if (location.PenetrationLength > 0 && aquiferLayers.Count > 0)
{
IList infiltrationLayers = (from SoilLayer1D layer in actualSoilProfile.Layers
where
(actualSoilProfile.InBetweenAquiferLayer == null ||
layer.TopLevel <
actualSoilProfile.InBetweenAquiferLayer.TopLevel) &&
layer.TopLevel >
actualSoilProfile.BottomAquiferLayer.TopLevel
select layer).ToList();
if (infiltrationLayers.Count > 0)
{
double separationLevel = actualSoilProfile.BottomAquiferLayer.TopLevel +
location.PenetrationLength;
if (separationLevel <= infiltrationLayers.First().TopLevel)
{
plLine.Points.Add(
new GeometryPoint(location.Surfaceline.Geometry.Points.First(p => !double.IsNaN(p.X)).X, 0,
location.CorrectedHeadInPLLine2Outwards));
plLine.Points.Add(
new GeometryPoint(location.Surfaceline.Geometry.Points.Last(p => !double.IsNaN(p.X)).X, 0,
location.CorrectedHeadInPLLine2Inwards));
}
}
}
}
Pl2 = plLine;
return plLine;
}
private GeometryPointString createPLLine3ForClayDikeOnSand(Location location)
{
var plLine = new GeometryPointString();
if (!location.Surfaceline.HasAnnotation(CharacteristicPointType.DikeToeAtRiver))
{
throw new System.Exception("Characteristic point \"dike toe at river\" is not defined.");
}
var outerBoundary = new GeometryPoint();
outerBoundary.X = location.Surfaceline.Geometry.Points.First(p => !double.IsNaN(p.X)).X;
outerBoundary.Z = location.WaterLevelRiver;
plLine.Points.Add(outerBoundary);
var outertoplevel = new GeometryPoint();
outertoplevel.X = location.Surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeToeAtRiver).X;
outertoplevel.Z = location.WaterLevelRiver;
plLine.Points.Add(outertoplevel);
var innerToe = new GeometryPoint();
innerToe.X = location.Surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeToeAtPolder).X;
innerToe.Z = location.Surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeToeAtPolder).Z;
plLine.Points.Add(innerToe);
var innerBoundary = new GeometryPoint();
innerBoundary.X = location.Surfaceline.Geometry.Points.Last(p => !double.IsNaN(p.X)).X;
innerBoundary.Z = location.Surfaceline.Geometry.Points.Last(p => !double.IsNaN(p.X)).Z;
plLine.Points.Add(innerBoundary);
plLine.CondensePoints();
return plLine;
}
private GeometryPointString CreatePLLine3Or4ForDikesOnClay(double headOutside, PLLineType plLineType, Location location)
{
var plLine = new GeometryPointString();
// short-cuts
double firstX = location.Surfaceline.Geometry.Points.First(p => !double.IsNaN(p.X)).X;
double lastX = location.Surfaceline.Geometry.Points.Last(p => !double.IsNaN(p.X)).X;
double headInPlLine2Outwards = location.CorrectedHeadInPLLine2Outwards;
GeometryPoint dikeTopAtRiver = location.Surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeTopAtRiver);
GeometryPoint dikeToeAtRiver = location.Surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeToeAtRiver);
GeometryPoint dikeTopAtPolder = location.Surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeTopAtPolder);
GeometryPoint dikeToeAtPolder = location.Surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeToeAtPolder);
// Soil profile is used to check if there is really an aquifer below toe of dike at river.
// The assumption is made that if there is an aquifer at the riverside and at the polderside,
// that there is a connection between these aquifers.
// In the uplift calculation there will also be a check on the existence of an aquifer.
if (!location.Surfaceline.HasAnnotation(CharacteristicPointType.DikeToeAtRiver))
{
throw new System.Exception("Characteristic point \"dike toe at river\" is not defined.");
}
if (!location.Surfaceline.HasAnnotation(CharacteristicPointType.DikeTopAtRiver))
{
throw new System.Exception("Characteristic point \"dike top at river\" is not defined.");
}
if (!location.Surfaceline.HasAnnotation(CharacteristicPointType.DikeTopAtPolder))
{
throw new System.Exception("Characteristic point \"dike top at polder\" is not defined.");
}
if (!location.Surfaceline.HasAnnotation(CharacteristicPointType.DikeToeAtPolder))
{
throw new System.Exception("Characteristic point \"dike toe at polder\" is not defined.");
}
SoilProfile1D actualSoilProfile = location.SoilProfile2D != null
? GetSoilProfileBelowPoint(location.SoilProfile2D, dikeTopAtPolder.X) : location.SoilProfile1D;
if (actualSoilProfile == null)
{
throw new System.Exception("No soil profile is present.");
}
SoilLayer1D relevantAquiferLayer = GetRelevantAquiferLayer(plLineType, actualSoilProfile, location);
if (relevantAquiferLayer != null && !double.IsNaN(location.LeakageLengthInwardsPl3))
{
if (double.IsNaN(headInPlLine2Outwards))
{
headInPlLine2Outwards = location.WaterLevelPolder;
}
// First, horizontal line between outside boundary and Dike Toe At River
plLine.Points.Add(new GeometryPoint(firstX, 0, headOutside));
plLine.Points.Add(new GeometryPoint(dikeToeAtRiver.X, 0, headOutside));
// Then, add head at Dike Top At River (formule 1 in memo)
double headPlDikeTopRiver = CalculateHeadPL3OrPL4AtTopDikeOutside(location, plLineType);
plLine.Points.Add(new GeometryPoint(dikeTopAtRiver.X, 0, headPlDikeTopRiver));
// Then, add head at Dike Top at Polder (formule 2)
double headPlDikeTopPolder = CalculateHeadPL3OrPL4AtX(location, plLineType, dikeTopAtPolder.X);
plLine.Points.Add(new GeometryPoint(dikeTopAtPolder.X, 0, headPlDikeTopPolder));
// Then, add head at Dike Toe at Polder (formule 2)
double headPlDikeToePolder = CalculateHeadPL3OrPL4AtX(location, plLineType, dikeToeAtPolder.X);
plLine.Points.Add(new GeometryPoint(dikeToeAtPolder.X, 0, headPlDikeToePolder));
// Calculate head at 20 m after dike toe (this is needed if no uplift occurs)
double xDikeToeAtPolderPlus20M = Math.Min(dikeToeAtPolder.X + 20, lastX);
HeadAtDikeToePolderPlus20M = CalculateHeadPL3OrPL4AtX(location, plLineType, xDikeToeAtPolderPlus20M);
// This head will be added in procedure AdjustLineDueToUpliftForRingtoetsWti2017Method
// Calculate head at the end of the geometry (this is needed if no uplift occurs)
HeadAtEndOfGeometry = CalculateHeadPL3OrPL4AtX(location, plLineType, lastX);
// This head will be added in procedure AdjustLineDueToUpliftForRingtoetsWti2017Method
// Now calculate the tail of the PL-line, from DikeToeAtPolder, using formule 2.
// If uplift occurs, the head is adjusted to a maximum head corresponding to an uplift potential of 1.
AdjustLineDueToUpliftForRingtoetsWti2017Method(plLine, plLineType, location);
}
plLine.CondensePoints();
return plLine;
}
///
/// Calculate head 2 at X-coordinate, according to the memo of A. van Duinen, see:
/// https://repos.deltares.nl/repos/delftgeosystems/delftgeosystems/trunk/documents/Geotechnics
///
/// Location class containing all waternet data's
/// X-coordinate of the considered point
/// Head 2 at x
private double CalculateHeadPl2AtX(Location location, double x)
{
double firstX = location.Surfaceline.Geometry.Points.First(p => !double.IsNaN(p.X)).X;
double lastX = location.Surfaceline.Geometry.Points.Last(p => !double.IsNaN(p.X)).X;
double pl2Gradient = (location.CorrectedHeadInPLLine2Outwards - location.CorrectedHeadInPLLine2Inwards) / (firstX - lastX);
double pl2Level = location.CorrectedHeadInPLLine2Outwards + pl2Gradient * (x - firstX);
return pl2Level;
}
///
/// Calculate head 3 or head 4 at X-coordinate, according to the memo of A. van Duinen (formule 2), see:
/// https://repos.deltares.nl/repos/delftgeosystems/delftgeosystems/trunk/documents/Geotechnics
///
/// Location class containing all waternet data's
/// Type of PL-line: either PL3 or PL4
/// X-coordinate of the considered point
/// Head 3 or head 4 at X
private double CalculateHeadPL3OrPL4AtX(Location location, PLLineType plLineType, double x)
{
// Determine the leakage length
double leakageLengthInward;
if (plLineType == PLLineType.PL3)
{
leakageLengthInward = location.LeakageLengthInwardsPl3;
}
else
{
leakageLengthInward = location.LeakageLengthInwardsPl4;
}
// Head PL3/PL4 at Dike Top River (formule 1 in memo)
double headPlDikeTopRiver = CalculateHeadPL3OrPL4AtTopDikeOutside(location, plLineType);
// Head PL3/PL4 at x (formule 2 in memo)
GeometryPoint dikeTopAtRiver = location.Surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeTopAtRiver);
double headPl2AtDikeTopAtRiver = CalculateHeadPl2AtX(location, dikeTopAtRiver.X);
double headPl2AtX = CalculateHeadPl2AtX(location, x);
double deltaX = x - dikeTopAtRiver.X;
double head = (headPlDikeTopRiver - headPl2AtDikeTopAtRiver) * Math.Pow(Math.E, (-deltaX / leakageLengthInward)) + headPl2AtX;
return head;
}
///
/// Calculate head 3 or head 4 at Dike Tope Outside, according to the memo of A. van Duinen (formule 1), see:
/// https://repos.deltares.nl/repos/delftgeosystems/delftgeosystems/trunk/documents/Geotechnics
///
///
///
///
private double CalculateHeadPL3OrPL4AtTopDikeOutside(Location location, PLLineType plLineType)
{
// Determine the leakage length
double leakageLengthInward;
double leakageLengthOutward;
if (plLineType == PLLineType.PL3)
{
leakageLengthInward = location.LeakageLengthInwardsPl3;
leakageLengthOutward = location.LeakageLengthOutwardsPl3;
}
else
{
leakageLengthInward = location.LeakageLengthInwardsPl4;
leakageLengthOutward = location.LeakageLengthOutwardsPl4;
}
double waterLevelRiverAverage = !double.IsNaN(location.WaterLevelRiverAverage)
? location.WaterLevelRiverAverage
: location.CorrectedHeadInPLLine2Outwards;
// Head PL3/PL4 at Dike Top River (formule 1 in memo)
GeometryPoint dikeTopAtRiver = location.Surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeTopAtRiver);
double headPl2DikeTopAtRiver = CalculateHeadPl2AtX(location, dikeTopAtRiver.X);
double headPlDikeTopRiver = (location.WaterLevelRiver - waterLevelRiverAverage)
/ (1 + (leakageLengthOutward / leakageLengthInward)) + headPl2DikeTopAtRiver;
return headPlDikeTopRiver;
}
///
/// Calculate the maximum head (of PL3 or PL4) for which no uplift will occur
///
///
///
///
/// maximum head for which no uplift will occur
private double DetermineMaxHead(double xCoor, PLLineType plLineType, Location location)
{
SoilProfile1D actualSoilProfile = location.SoilProfile2D != null
? GetSoilProfileBelowPoint(location.SoilProfile2D, xCoor)
: location.SoilProfile1D;
SoilLayer1D relevantSandLayer = null;
try
{
relevantSandLayer = GetRelevantAquiferLayer(plLineType, actualSoilProfile, location);
}
catch
{
return Double.MaxValue;
}
UpliftCalculatorWTI upliftCalculator = new UpliftCalculatorWTI();
upliftCalculator.PhreaticLevel = phreaticLine.GetZAtX(xCoor);
upliftCalculator.SoilProfile = actualSoilProfile;
upliftCalculator.SurfaceLevel = actualSoilProfile.TopLevel;
upliftCalculator.TopOfLayerToBeEvaluated = relevantSandLayer.TopLevel;
upliftCalculator.UpLiftTopLevel = relevantSandLayer.TopLevel;
upliftCalculator.VolumicWeightOfWater = UnitWeightWater;
return upliftCalculator.CalculateHeadOfPLLine(cUpliftFactorEquilibrium);
}
///
/// Adjust PL3 or 4 if uplift occurs.
///
///
///
///
private void AdjustLineDueToUpliftForRingtoetsWti2017Method(GeometryPointString plLine, PLLineType plLineType, Location location)
{
GeometryPoint ditchDikeSide = location.Surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DitchDikeSide);
GeometryPoint bottomDitchDikeSide = location.Surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.BottomDitchDikeSide);
double head = 0;
double maxHeadFirstPoint = 0;
double xFirstPointPlus20M = 0;
double upliftLength = 20;
const double searchUpliftIncrement = 0.5;
double dikeToePolderX = location.Surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeToeAtPolder).X;
double lastX = location.Surfaceline.Geometry.Points.Last(p => !double.IsNaN(p.X)).X;
bool isUplift = false;
bool isDitchPresent = (ditchDikeSide != null && !double.IsNaN(ditchDikeSide.X)) && (bottomDitchDikeSide != null && !double.IsNaN(bottomDitchDikeSide.X));
// First, create the list of X-coordinates used for the search area (= points of the surface line + intermediaire points every 50 cm)
var listXCoord = new List();
for (int i = 0; i < location.SoilProfile2D.Geometry.Points.Count; i++)
{
if (location.SoilProfile2D.Geometry.Points[i].X > dikeToePolderX)
{
listXCoord.Add(location.SoilProfile2D.Geometry.Points[i].X);
}
}
for (int i = 0; i < (lastX - dikeToePolderX) / searchUpliftIncrement - 1; i++)
{
listXCoord.Add(dikeToePolderX + (i + 1) * searchUpliftIncrement);
}
listXCoord.Sort();
// Search first X-coordinate at which uplift occurs (only if no ditch is present)
double xFirstPointUplift;
xFirstPointUplift = dikeToePolderX - searchUpliftIncrement;
while ((!isUplift) && ((xFirstPointUplift + searchUpliftIncrement) < lastX))
{
xFirstPointUplift += searchUpliftIncrement;
maxHeadFirstPoint = DetermineMaxHead(xFirstPointUplift, plLineType, location);
head = CalculateHeadPL3OrPL4AtX(location, plLineType, xFirstPointUplift);
isUplift = (head > maxHeadFirstPoint);
}
// If there is uplift and a Ditch and the upliftpoint is beyond the ditch (as seen from the diketoe), then reset upliftpoint (and head) to
// ditch dike side.
if (isUplift && isDitchPresent)
{
if (xFirstPointUplift > ditchDikeSide.X)
{
xFirstPointUplift = ditchDikeSide.X;
maxHeadFirstPoint = DetermineMaxHead(xFirstPointUplift, plLineType, location);
}
}
if (isUplift && location.AdjustPl3And4ForUplift)
{
// In situation with an uplift zone of 20 m, the points situated at DikeTopAtPolder and DikeToeAtPolder are deleted
// (i.e. PL-line is a straight line between DikeTopAtRiver and the first point at which uplift occurs)
xFirstPointPlus20M = xFirstPointUplift + upliftLength;
if (isDitchPresent)
{
xFirstPointPlus20M = ditchDikeSide.X + upliftLength;
}
// uplift zone can not exceed geometry boundary inside
if (xFirstPointPlus20M > lastX)
{
xFirstPointPlus20M = lastX;
}
double maxHeadFirstPointPlus20M = DetermineMaxHead(xFirstPointPlus20M, plLineType, location);
double head20M = CalculateHeadPL3OrPL4AtX(location, plLineType, xFirstPointPlus20M);
bool isUpliftAfter20M = (head20M > maxHeadFirstPointPlus20M);
if (isUpliftAfter20M)
{
plLine.Points.Remove(plLine.Points.Last());
plLine.Points.Remove(plLine.Points.Last());
}
// Add the first point at which uplift occurs
double headFirstPointUplift = CalculateHeadPL3OrPL4AtX(location, plLineType, xFirstPointUplift);
headFirstPointUplift = Math.Min(headFirstPointUplift, maxHeadFirstPoint);
// When the first location where uplift occurs coincides with Dike Toe at Polder, this point replaces the current point of the PL
if (Math.Abs(xFirstPointUplift - dikeToePolderX) < 0.0001)
{
plLine.Points.Remove(plLine.Points.Last());
}
plLine.Points.Add(new GeometryPoint(xFirstPointUplift, 0, headFirstPointUplift));
// Add the first point at which uplift occurs + 20 m
double headFirstPointPlus20M = CalculateHeadPL3OrPL4AtX(location, plLineType, xFirstPointPlus20M);
headFirstPointPlus20M = Math.Min(headFirstPointPlus20M, maxHeadFirstPointPlus20M);
plLine.Points.Add(new GeometryPoint(xFirstPointPlus20M, 0, headFirstPointPlus20M));
// Fill list with all relevant points in uplift area
List xValues = new List();
IList points = location.SoilProfile2D != null ? location.SoilProfile2D.Geometry.Points : location.Surfaceline.Geometry.Points;
foreach (var point in points)
{
if (!double.IsNaN(point.X) && point.X > xFirstPointUplift && point.X < xFirstPointPlus20M && !xValues.Contains(Math.Round(point.X, 3)))
{
xValues.Add(Math.Round(point.X, 3));
}
}
xValues.Add(xFirstPointUplift);
xValues.Add(xFirstPointPlus20M);
xValues.Sort();
// Fill large gaps between points
points.Sort();
int pointIndex = 0;
while (pointIndex < xValues.Count - 1)
{
if (xValues[pointIndex + 1] - xValues[pointIndex] > searchUpliftIncrement)
{
xValues.Insert(pointIndex + 1, Math.Min(xValues[pointIndex] + searchUpliftIncrement, (xValues[pointIndex] + xValues[pointIndex + 1]) / 2));
}
pointIndex++;
}
// Add all the points of the surface line part of the uplift zone (i.e. from first point at which uplift occurs until 20 m)
foreach (double x in xValues)
{
double xCoor = x;
double headAtX = CalculateHeadPL3OrPL4AtX(location, plLineType, xCoor);
double maxHeadAtX = DetermineMaxHead(xCoor, plLineType, location);
headAtX = Math.Min(headAtX, maxHeadAtX);
plLine.Points.Add(new GeometryPoint(xCoor, 0, headAtX));
}
// Now continue PL-line to PL2 (or PL1 if PL2 is not present), with a slope of 1:100.
plLine.Points.Sort();
plLine.RemoveUnnecessaryPoints();
GeometryPoint lastCurrentPointPlLine = plLine.Points.Last();
const double slopeEnd = 100;
if (Math.Abs(plLine.Points.Last().X - lastX) > 0.0001)
{
double head2Or1AtLastX;
if (Pl2 != null)
{
head2Or1AtLastX = CalculateHeadPl2AtX(location, lastX);
}
else
{
head2Or1AtLastX = phreaticLine.GetZAtX(lastX);
}
GeometryPoint pointE = DetermineIntersectionBetweenPl2Or1AndFinalSlope100OfPl3(lastCurrentPointPlLine, slopeEnd, location);
if (pointE.X > lastX)
{
// 1 point is added (on the slope 1:100) at end of the geometry
double headOnSlopeAtLastX;
bool slopeGoesDown = (CalculateHeadPl2AtX(location, lastCurrentPointPlLine.X) < lastCurrentPointPlLine.Z);
if (slopeGoesDown)
{
headOnSlopeAtLastX = lastCurrentPointPlLine.Z - (lastX - lastCurrentPointPlLine.X) / 100;
}
else
{
headOnSlopeAtLastX = lastCurrentPointPlLine.Z + (lastX - lastCurrentPointPlLine.X) / 100;
}
plLine.Points.Add(new GeometryPoint(lastX, 0, headOnSlopeAtLastX));
}
else
{
// 2 points are added: 1 at the end of the slope 1:100 (point E in figure 7 of the memo of A. van Duinen) and 1 at end of geometry (point G)
if (pointE != null)
{
plLine.Points.Add(pointE);
}
plLine.Points.Add(new GeometryPoint(lastX, 0, head2Or1AtLastX));
}
}
}
else
{
// if no uplift occurs, add point at 20 m after dike toe at polder
xFirstPointPlus20M = Math.Min(dikeToePolderX + 20, lastX);
plLine.Points.Add(new GeometryPoint(xFirstPointPlus20M, 0, HeadAtDikeToePolderPlus20M));
// and add point at the end of geometry
plLine.Points.Add(new GeometryPoint(lastX, 0, HeadAtEndOfGeometry));
}
plLine.Points.Sort();
}
private static SoilLayer1D GetRelevantAquiferLayer(PLLineType type, SoilProfile1D actualSoilProfile, Location location)
{
IList aquiferLayers = actualSoilProfile.GetAquiferLayers();
SoilLayer1D relevantAquiferLayer = null;
switch (type)
{
case PLLineType.PL3:
if (location.DikeSoilScenario == DikeSoilScenario.ClayDikeOnSand)
{
relevantAquiferLayer = actualSoilProfile.TopAquiferLayer; // Highest aquifer
}
else
{
relevantAquiferLayer = actualSoilProfile.BottomAquiferLayer; // Pleistocene
}
break;
case PLLineType.PL4:
relevantAquiferLayer = actualSoilProfile.InBetweenAquiferLayer; // Intermediate sand layer
break;
default:
string message = String.Format("Invalid PL line type:{0} for creation of PL Line 3 or 4", type);
throw new System.Exception(message);
}
if (relevantAquiferLayer == null)
{
string message = "Soil profile (" + actualSoilProfile.Name + ") contains no relevant aquifer layer.";
throw new System.Exception(message);
}
return relevantAquiferLayer;
}
///
/// Create the phreatic line segments inside the dike for Phreatic line generated for Ringtoets WTI 2017
///
/// The current phreatic line, in construction
/// The hydraulic conditions of the waternet creator
private void CreatePhreaticLineSegmentsInsideDikeForRingtoetsWti2017(GeometryPointString phreaticLine, Location location)
{
bool isFinished = false;
// Shortcuts
GeometryPoint intersectionPolderLevelWithDike = DetermineIntersectionBetweenPolderLevelAndDike(location.WaterLevelPolder, location.Surfaceline);
double offsetBelowPointB = 0.0;
double offsetBelowDikeTopAtPolder = 0.0;
GeometryPoint dikeTopAtRiver = location.Surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeTopAtRiver);
GeometryPoint dikeTopAtPolder = location.Surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeTopAtPolder);
GeometryPoint dikeToeAtPolder = location.Surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeToeAtPolder);
GeometryPoint pointA = phreaticLine.Points.Last();
double maxXCoordinateSurface = location.Surfaceline.Geometry.Points.Max(x => x.X);
// Determine the offset values
if (!location.UseDefaultOffsets)
{
offsetBelowPointB = location.PlLineOffsetBelowPointBRingtoetsWti2017;
offsetBelowDikeTopAtPolder = location.PlLineOffsetBelowDikeTopAtPolder;
}
else
{
if (location.DikeSoilScenario == DikeSoilScenario.ClayDikeOnClay || location.DikeSoilScenario == DikeSoilScenario.ClayDikeOnSand)
{
offsetBelowPointB = 1.0;
offsetBelowDikeTopAtPolder = 1.5;
}
if (location.DikeSoilScenario == DikeSoilScenario.SandDikeOnClay)
{
offsetBelowPointB = 0.5 * (pointA.Z - dikeToeAtPolder.Z);
}
}
// Determine X coordinate of point B
double xPointB;
if (location.DikeSoilScenario == DikeSoilScenario.SandDikeOnClay)
{
xPointB = Math.Min(phreaticLine.Points.Last().X + 0.001, dikeTopAtPolder.X);
}
else
{
xPointB = Math.Min(phreaticLine.Points.Last().X + 1.0, dikeTopAtPolder.X);
}
// Determine the minimum value for point B (linear interpolation between both minimum values at Dike Top)
double minValueForPointB = (location.MinimumLevelPhreaticLineAtDikeTopPolder - location.MinimumLevelPhreaticLineAtDikeTopRiver)
/ (dikeTopAtPolder.X - dikeTopAtRiver.X) * (xPointB - dikeTopAtRiver.X) + location.MinimumLevelPhreaticLineAtDikeTopRiver;
// Determine the phreatic levels at points B and DikeTopPolder
double zPointB = Math.Round(Math.Min(location.Surfaceline.Geometry.GetZAtX(xPointB) - cOffsetPhreaticLineBelowSurface,
Math.Max(location.WaterLevelRiver - offsetBelowPointB, minValueForPointB)), cRoundDecimals);
double waterLevelBelowDikeTopAtPolder = Math.Max(Math.Round(location.WaterLevelRiver - offsetBelowDikeTopAtPolder, cRoundDecimals),
location.MinimumLevelPhreaticLineAtDikeTopPolder);
// Both created points are now added to the phreatic line. However:
// - For Sand dike on sand (with default offset), no points are added below both dike top yet;
// - For Sand dike on clay (with default offset), only 1 point below dike top at river side is added.
// This is done afterwards, in method CreatePLLine1ByExpertKnowledge, because the position of point E has to be known.
if (location.Surfaceline.IsDefined(CharacteristicPointType.DikeTopAtRiver))
{
// If the water level at polder if above the phreatic line point at the top of the dike at river side (point B), then the rest of the phreatic line
// is an horizontal line at water polder, and starting at intersection polder level/dike
if (zPointB < location.WaterLevelPolder)
{
if (intersectionPolderLevelWithDike != null)
{
phreaticLine.Points.Add(intersectionPolderLevelWithDike);
phreaticLine.Points.Add(new GeometryPoint(maxXCoordinateSurface, 0, location.WaterLevelPolder));
isFinished = true;
}
else
{
if (!(location.DikeSoilScenario == DikeSoilScenario.SandDikeOnSand && location.UseDefaultOffsets))
{
phreaticLine.Points.Add(new GeometryPoint(xPointB, 0, location.WaterLevelPolder));
}
}
}
else
{
if (!(location.DikeSoilScenario == DikeSoilScenario.SandDikeOnSand && location.UseDefaultOffsets))
{
phreaticLine.Points.Add(new GeometryPoint(xPointB, 0, zPointB));
}
}
}
if (location.Surfaceline.IsDefined(CharacteristicPointType.DikeTopAtPolder) && !isFinished)
{
// If the water level at polder if above the phreatic line point at the top of the dike at river side (point B), then the rest of the phreatic line
// is an horizontal line at water polder, and starting at intersection polder level/dike
if (waterLevelBelowDikeTopAtPolder < location.WaterLevelPolder)
{
if (intersectionPolderLevelWithDike != null)
{
phreaticLine.Points.Add(intersectionPolderLevelWithDike);
phreaticLine.Points.Add(new GeometryPoint(maxXCoordinateSurface, 0, location.WaterLevelPolder));
}
else
{
phreaticLine.Points.Add(new GeometryPoint(dikeTopAtPolder.X, 0, location.WaterLevelPolder));
}
}
else
{
if (!(location.DikeSoilScenario == DikeSoilScenario.SandDikeOnSand && location.UseDefaultOffsets))
{
if (!(location.DikeSoilScenario == DikeSoilScenario.SandDikeOnClay && location.UseDefaultOffsets))
{
phreaticLine.Points.Add(new GeometryPoint(dikeTopAtPolder.X, 0, waterLevelBelowDikeTopAtPolder));
}
}
}
}
}
///
/// Create the phreatic line segments inside the dike for Phreatic line generated for Ringtoets WTI 2017
///
///
///
private void CreatePhreaticLineSegmentsInShoulderAndPolderForRingtoetsWti2017(GeometryPointString phreaticLine, Location location)
{
bool isFinished = false;
// Shortcuts
GeometryPoint intersectionPolderLevelWithDike = DetermineIntersectionBetweenPolderLevelAndDike(location.WaterLevelPolder, location.Surfaceline);
GeometryPoint shoulderBaseInsidePoint = location.Surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.ShoulderBaseInside);
GeometryPoint dikeToeAtPolderPoint = location.Surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeToeAtPolder);
GeometryPoint ditchDikeSidePoint = location.Surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DitchDikeSide);
bool isDitchPresent = (ditchDikeSidePoint != null && !double.IsNaN(ditchDikeSidePoint.X));
double maxXCoordinateSurface = location.Surfaceline.Geometry.Points.Max(x => x.X);
GeometryPoint intersectionDitchWithWaterPolder = DetermineIntersectionBetweenPolderLevelAndDitch(location, isDitchPresent);
// If the water level at polder if above the phreatic line point at the top of the dike (point C), then stop
if (Math.Abs(phreaticLine.Points.Last().X - maxXCoordinateSurface) < 0.00001)
{
isFinished = true;
}
// If a drainage construction is present, the phreatic line goes directly to the middle of it (shoulder offset not used)
if (!isFinished)
{
if ((location.DikeSoilScenario == DikeSoilScenario.SandDikeOnClay ||
location.DikeSoilScenario == DikeSoilScenario.SandDikeOnSand) && location.DrainageConstructionPresent)
{
ThrowWhenDrainageConstructionNotInsideDike(location, location.Surfaceline);
phreaticLine.Points.Add(new GeometryPoint(location.XCoordMiddleDrainageConstruction, 0,
location.ZCoordMiddleDrainageConstruction));
// If a ditch is present, go directly to intersection point with water polder level
if (isDitchPresent && !double.IsNaN(intersectionDitchWithWaterPolder.X))
{
phreaticLine.Points.Add(intersectionDitchWithWaterPolder);
phreaticLine.Points.Add(new GeometryPoint(maxXCoordinateSurface, 0, location.WaterLevelPolder));
isFinished = true;
}
}
else
{
// A point is created below shoulder point only for user-defined offset, and only if a shoulder is present
if (location.Surfaceline.IsDefined(CharacteristicPointType.ShoulderBaseInside) && !location.UseDefaultOffsets)
{
// determine offset value below the shoulder: a negative offset is not allowed, in this case the method uses 1 cm
double offsetAtShouder = Math.Max(cOffsetPhreaticLineBelowSurface, location.PlLineOffsetBelowShoulderBaseInside);
double zLevelAtShoulder = shoulderBaseInsidePoint.Z - offsetAtShouder;
// automatic correction of the phreatic line to avoid that it goes up
zLevelAtShoulder = Math.Min(phreaticLine.Points.Last().Z, zLevelAtShoulder);
// If the water level at polder if above the phreatic line point at shoulder, then the rest of the phreatic line
// is an horizontal line at water polder, and starting at intersection polder level/dike
if (zLevelAtShoulder < location.WaterLevelPolder)
{
phreaticLine.Points.Add(intersectionPolderLevelWithDike);
phreaticLine.Points.Add(new GeometryPoint(maxXCoordinateSurface, 0, location.WaterLevelPolder));
isFinished = true;
}
else
{
phreaticLine.Points.Add(new GeometryPoint(shoulderBaseInsidePoint.X, 0, zLevelAtShoulder));
}
}
// For sand dikes, an extra point (D1) is created 0.25*h above dike toe, where h is the distance between
// point A (water level at river side) and point E (dike toe at polder side)
bool negativeUserDefinedOffset = (!location.UseDefaultOffsets && (location.PlLineOffsetBelowDikeToeAtPolder < 0));
bool sandDikesWithDefaultOffsets = (location.DikeSoilScenario == DikeSoilScenario.SandDikeOnClay || location.DikeSoilScenario == DikeSoilScenario.SandDikeOnSand)
&& location.UseDefaultOffsets;
if ((sandDikesWithDefaultOffsets || negativeUserDefinedOffset) && !isFinished)
{
double levelPointA = phreaticLine.Points.First().Z;
double levelPointD1;
if (location.UseDefaultOffsets)
{
levelPointD1 = dikeToeAtPolderPoint.Z + 0.25 * (levelPointA - dikeToeAtPolderPoint.Z);
}
else
{
levelPointD1 = dikeToeAtPolderPoint.Z - location.PlLineOffsetBelowDikeToeAtPolder;
}
GeometryPoint pointD1 = DetermineIntersectionBetweenPolderLevelAndDike(levelPointD1, location.Surfaceline);
if (levelPointD1 < location.WaterLevelPolder)
{
phreaticLine.Points.Add(intersectionPolderLevelWithDike);
phreaticLine.Points.Add(new GeometryPoint(maxXCoordinateSurface, 0, location.WaterLevelPolder));
isFinished = true;
}
else
{
if (pointD1 != null)
{
phreaticLine.Points.Add(new GeometryPoint(pointD1.X, 0, pointD1.Z - cOffsetPhreaticLineBelowSurface));
}
}
}
}
}
// If the water level at polder if above the phreatic line point at shoulder, then the rest of the phreatic line
// is an horizontal line at water polder, and starting at intersection polder level/dike
if (dikeToeAtPolderPoint.Z < location.WaterLevelPolder && !isFinished)
{
phreaticLine.Points.Add(intersectionPolderLevelWithDike);
phreaticLine.Points.Add(new GeometryPoint(maxXCoordinateSurface, 0, location.WaterLevelPolder));
isFinished = true;
}
// Add point E (dike toe at polder side)
if (!Double.IsNaN(dikeToeAtPolderPoint.X) && !isFinished)
{
// determine offset value below the dike toe polder: if a negative offset is used (i.e. point D1 is created),
// point E is situated 1 cm below dike toe
double offsetBelowDikeToe = 0.0;
if (!location.UseDefaultOffsets)
{
offsetBelowDikeToe = Math.Max(cOffsetPhreaticLineBelowSurface, location.PlLineOffsetBelowDikeToeAtPolder);
}
double zLevelAtDikeToe = dikeToeAtPolderPoint.Z - offsetBelowDikeToe;
// automatic correction of the phreatic line to avoid that it goes up between point E and F, if point F (intersection with ditch) exists
if (isDitchPresent && !double.IsNaN(intersectionDitchWithWaterPolder.X))
{
zLevelAtDikeToe = Math.Max(intersectionDitchWithWaterPolder.Z, zLevelAtDikeToe);
}
else
{
// if no ditch present, or no intersection with ditch
offsetBelowDikeToe = Math.Max(cOffsetPhreaticLineBelowSurface, offsetBelowDikeToe);
zLevelAtDikeToe = Math.Max(location.WaterLevelPolder, dikeToeAtPolderPoint.Z - offsetBelowDikeToe);
}
phreaticLine.Points.Add(new GeometryPoint(dikeToeAtPolderPoint.X, 0, zLevelAtDikeToe));
}
// If point F (intersection point between the ditch slope at dike side and the level of the water polder) exists, add it to phreatic line
if (isDitchPresent && !double.IsNaN(intersectionDitchWithWaterPolder.X) && !isFinished)
{
//Todo #Bka see MAC-242: This point should not just be added. First you should see if there are other points
//on the surfaceline between the last added phreatic point and this intersectionDitchWithWaterPolder. If there
// are such points, see if the intended line does not fall above these points. If so, then add point(s) to the
// phreatic line at that X with the give offset below the point(s).
// It is in fact easier to just add a phreatic point for all points that fall between the last added phreatic
// point and this intersectionDitchWithWaterPolder (with offset of course) but I do not know if that is intended.
// For that check with Alexander van Duinen.
phreaticLine.Points.Add(intersectionDitchWithWaterPolder);
}
// If no ditch is present and if the phreatic level at dike toe (i.e. point E = dike toe - offset)
// is above the water level at polder, then the Phreatic Line follows the surface line minus offset (= 1 cm).
else
{
if (!isFinished && (phreaticLine.Points[phreaticLine.Points.Count - 1].Z > location.WaterLevelPolder))
{
AddPhreaticLineAlongSurfaceLevel(phreaticLine, location);
}
}
// Validate if end point surface is reached
if (Math.Abs(phreaticLine.Points.Last().X - maxXCoordinateSurface) > 0.0001 && !isFinished)
{
var endPoint = new GeometryPoint(maxXCoordinateSurface, 0, phreaticLine.Points.Last().Z);
phreaticLine.Points.Add(endPoint);
}
}
///
/// If a ditch is present, determine point F = the intersection point between the ditch slope at dike side and the level of the water polder.
/// If no point is found, return NaN.
///
/// Class containing hydraulic data's over the waternet creator
/// is a ditch present ?
/// The intersection point between the ditch slope at dike side and the level of the water polder. If no point is found, return NaN.
private static GeometryPoint DetermineIntersectionBetweenPolderLevelAndDitch(Location location, bool isDitchPresent)
{
GeometryPoint ditchDikeSidePoint = location.Surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DitchDikeSide);
GeometryPoint bottomDitchPolderSidePoint = location.Surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.BottomDitchPolderSide);
double minXCoordinateSurface = location.Surfaceline.Geometry.Points.Min(x => x.X);
double maxXCoordinateSurface = location.Surfaceline.Geometry.Points.Max(x => x.X);
var intersectionDitchWithWaterPolder = new GeometryPoint(double.NaN, double.NaN, double.NaN);
if (isDitchPresent)
{
var waterLevelPolderLine = new Line();
var firstPoint = new GeometryPoint(minXCoordinateSurface, 0, location.WaterLevelPolder);
var lastPoint = new GeometryPoint(maxXCoordinateSurface, 0, location.WaterLevelPolder);
waterLevelPolderLine.SetBeginAndEndPoints(firstPoint, lastPoint);
GeometryPointString ditchPart = location.Surfaceline.Geometry.GetPart(ditchDikeSidePoint.X, bottomDitchPolderSidePoint.X);
for (int i = 0; i < ditchPart.Count - 1; i++)
{
var lineDitchDikeSide = new Line(ditchPart[i], ditchPart[i + 1]);
intersectionDitchWithWaterPolder = LineHelper.GetIntersectPoint(lineDitchDikeSide, waterLevelPolderLine, PointType.XZ);
if (!double.IsNaN(intersectionDitchWithWaterPolder.X))
{
break;
}
}
}
return intersectionDitchWithWaterPolder;
}
///
/// Add phreatic point at offset (i.e. point E = DikeToeAtPolder - offset) below every surface line point as long as depth > polder level. Else determine the
/// proper position of the point at polder level (intersection) and stop.
///
///
///
private void AddPhreaticLineAlongSurfaceLevel(GeometryPointString phreaticLine, Location location)
{
GeometryPointString relevantPart =
location.Surfaceline.Geometry.GetPart(
location.Surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeToeAtPolder).X, location.Surfaceline.Geometry.GetMaxX());
bool intersected = false;
double offset;
if (location.UseDefaultOffsets)
{
offset = 0;
}
else
{
offset = location.PlLineOffsetBelowDikeToeAtPolder;
}
offset = Math.Max(cOffsetPhreaticLineBelowSurface, offset);
for (int i = 0; i < relevantPart.Count; i++)
{
double z = Math.Max(location.WaterLevelPolder, relevantPart.Points[i].Z - offset);
double x = relevantPart.Points[i].X;
if (location.WaterLevelPolder > relevantPart[i].Z - offset)
{
// Determine intersection between would be phreatic segment and polderlevel. Add that as next point.
var waterLevelPolderLine = new Line
{
BeginPoint = new GeometryPoint(location.Surfaceline.Geometry.GetMinX(), 0, location.WaterLevelPolder),
EndPoint = new GeometryPoint(location.Surfaceline.Geometry.GetMaxX(), 0, location.WaterLevelPolder)
};
var slopeLine = new Line
{
BeginPoint = phreaticLine.Points[phreaticLine.Points.Count - 1],
EndPoint = new GeometryPoint(relevantPart.Points[i].X, 0, relevantPart.Points[i].Z - offset)
};
GeometryPoint intersectionPoint = LineHelper.GetIntersectPoint(waterLevelPolderLine, slopeLine, PointType.XZ);
if (!double.IsNaN(intersectionPoint.X))
{
x = intersectionPoint.X;
}
intersected = true;
}
var point = new GeometryPoint(x, 0, z);
phreaticLine.Points.Add(point);
if (intersected)
{
break;
}
}
}
///
/// Determine if polder level intersects the dike at polder side (between dike top at river and dike toe at polder)
///
///
///
///
private GeometryPoint DetermineIntersectionBetweenPolderLevelAndDike(double polderLevel, SurfaceLine2 surfaceline)
{
var polderlevelLine = new Line();
var localSurfaceline = new GeometryPointString();
localSurfaceline.Points.AddRange(surfaceline.Geometry.Points.Where(p => !double.IsNaN(p.X)));
double startXCoordinate = localSurfaceline.Points.OrderBy(p => p.X).First().X;
double endxCoordinate = localSurfaceline.Points.OrderBy(p => p.X).Last().X;
polderlevelLine.SetBeginAndEndPoints(new GeometryPoint(startXCoordinate, 0, polderLevel),
new GeometryPoint(endxCoordinate, 0, polderLevel));
ThrowWhenWaterLevelPolderAboveDikeTopAtPolder(polderLevel, surfaceline);
GeometryPointString relevantPart =
surfaceline.Geometry.GetPart(
surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeTopAtRiver).X,
surfaceline.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeToeAtPolder).X);
for (int index = 0; index < relevantPart.Points.Count - 1; index++)
{
var surfaceLineSegment = new Line(relevantPart[index], relevantPart[index + 1]);
GeometryPoint intersectPoint = LineHelper.GetIntersectPoint(surfaceLineSegment, polderlevelLine, PointType.XZ);
if (!double.IsNaN(intersectPoint.X))
{
return new GeometryPoint(intersectPoint.X, 0, intersectPoint.Z);
}
}
return null;
}
///
/// Detects whether the phreatic line is above the surface line between entry point in dike and polder.
/// If the phreatic line is above the surface, then the phreatic line should follow the surface
///
/// The phreatic line.
/// The location.
///
private void ValidatePhreaticLineBelowDike(GeometryPointString phreaticLine, Location location)
{
GeometryPointString relevantPart =
location.Surfaceline.Geometry.GetPart(phreaticLine[1].X + 0.0001, location.Surfaceline.Geometry.GetMaxX());
bool stopIteration = false;
// This code can go into an endless loop
// Enter failsave to raise System.Exception when this occurs
int cMaxIterations = Math.Max(100, phreaticLine.Points.Count * location.Surfaceline.Geometry.Count);
int iterationCount = 0;
while (stopIteration == false)
{
iterationCount++;
bool foundIntersect = false;
for (int index = 0; index < phreaticLine.Points.Count - 1; index++)
{
var phreaticLineSegment = new Line(phreaticLine.Points[index], phreaticLine.Points[index + 1]);
for (int surfacePointIndex = 0; surfacePointIndex < relevantPart.Count - 1; surfacePointIndex++)
{
var surfaceLineSegment = new Line(relevantPart.Points[surfacePointIndex], relevantPart.Points[surfacePointIndex + 1]);
GeometryPoint intersectPoint = LineHelper.GetIntersectPoint(phreaticLineSegment, surfaceLineSegment, PointType.XZ);
if (!double.IsNaN(intersectPoint.X))
{
// Only add intersectPoint if it is higher than waterlevelPolder, because the intersection could be caused
// by the fact that the waterlevel is higher than DikeToeAtPolder
if (intersectPoint.Z > location.WaterLevelPolder + cOffsetPhreaticLineBelowSurface)
{
var newPLLinePoint = new GeometryPoint(intersectPoint.X, 0,
intersectPoint.Z -
cOffsetPhreaticLineBelowSurface);
// Only add new point if it is more to the right than the first point of the phreatic line segment when
if (newPLLinePoint.X > phreaticLineSegment.BeginPoint.X)
{
phreaticLine.Points.Insert(index + 1, newPLLinePoint);
if (Equal(relevantPart.Points[surfacePointIndex + 1].X, intersectPoint.X))
{
// phreatic point and surface line point are positioned above each other, so replace the phreatic point with the intersect point
phreaticLine.Points[index + 2] =
new GeometryPoint(relevantPart[surfacePointIndex + 1].X, 0,
relevantPart.Points[surfacePointIndex + 1].Z -
cOffsetPhreaticLineBelowSurface);
}
if (Equal(relevantPart.Points[surfacePointIndex].X, intersectPoint.X))
{
// phreatic point and surface line point are positioned above each other, so replace the phreatic point with the intersect point
phreaticLine.Points[index + 2] =
new GeometryPoint(relevantPart[surfacePointIndex].X, 0,
relevantPart.Points[surfacePointIndex].Z -
cOffsetPhreaticLineBelowSurface);
}
else
{
var newNextPLLinePoint =
new GeometryPoint(relevantPart.Points[surfacePointIndex + 1].X, 0,
relevantPart.Points[surfacePointIndex + 1].Z -
cOffsetPhreaticLineBelowSurface);
if (newNextPLLinePoint.X <= phreaticLine.Points[index + 2].X)
{
if (Equal(newNextPLLinePoint.X,
phreaticLine.Points[index + 2].X))
{
// If phreatic point already exist on this x-coordinate just replace it with the new point
phreaticLine.Points[index + 2] = newNextPLLinePoint;
}
else
{
phreaticLine.Points.Insert(index + 2, newNextPLLinePoint);
}
}
}
foundIntersect = true;
}
break;
}
}
}
}
if (foundIntersect == false)
{
stopIteration = true;
}
if (iterationCount > cMaxIterations)
{
string message = "PLLinesCreator.ValidatePhreaticLineBelowDike() cannot succeed";
throw new System.Exception(message);
}
}
}
///
/// Check if the phreatic line is above the water level at polder. If it is below, correct it.
///
///
///
private void ValidatePhreaticAboveWaterLevelPolder(GeometryPointString phreaticLine, Location location)
{
foreach (var phreaticPoint in phreaticLine.Points)
{
if (phreaticPoint.Z < location.WaterLevelPolder)
{
phreaticPoint.Z = location.WaterLevelPolder;
}
}
}
///
/// Check if water level is below dike
///
///
///
private void ThrowWhenWaterLevelAboveDike(double waterLevelRiver, SurfaceLine2 surfaceLine)
{
if (waterLevelRiver > surfaceLine.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeTopAtRiver).Z)
{
string message =
String.Format(
"Phreatic water level should have an intersection with the dike at least once (phreatic line higher ({0:F2} m) than surface line ({1:F2}))",
waterLevelRiver, surfaceLine.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeTopAtRiver).Z);
throw new System.Exception(message);
}
}
///
/// Check if water level at polder is above dike top at polder
///
private void ThrowWhenWaterLevelPolderAboveDikeTopAtPolder(double polderLevel, SurfaceLine2 surfaceLine)
{
if (polderLevel > surfaceLine.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeTopAtPolder).Z)
{
string message =
String.Format(
"Waterlevel ({0:0.00}) in polder higher than dike top at polder ({1:0.00}) in surface line {2}",
polderLevel, GetDikeToeInward(surfaceLine).Z, surfaceLine.Name);
throw new System.Exception(message);
}
}
///
/// Check if the middle of the drainage construction is situated between DikeTopAtPolder and DikeToeAtPolder.
/// Check also that the middle of the drainage construction is inside the dike, not in the air.
///
///
///
private void ThrowWhenDrainageConstructionNotInsideDike(Location location, SurfaceLine2 surfaceLine)
{
double xCoordDrain = location.XCoordMiddleDrainageConstruction;
double zCoordDrain = location.ZCoordMiddleDrainageConstruction;
double zTopDike = surfaceLine.Geometry.GetZAtX(xCoordDrain);
double xDikeToeAtPolder = surfaceLine.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeToeAtPolder).X;
double xDikeTopAtPolder = surfaceLine.CharacteristicPoints.GetGeometryPoint(CharacteristicPointType.DikeTopAtPolder).X;
if ((xCoordDrain > xDikeToeAtPolder) || (xCoordDrain < xDikeTopAtPolder))
{
string message =
String.Format(
"The middle of the drainage construction ({0:0.00}) must be situated between the dike top ({1:0.00}) and the dike toe at polder side ({2:0.00}) " +
"in surface line {3}", xCoordDrain, xDikeTopAtPolder, xDikeToeAtPolder, surfaceLine.Name);
throw new System.Exception(message);
}
if (zCoordDrain > zTopDike)
{
string message =
String.Format(
"The drainage construction ({0:0.00}) must be situated below surface line {1}", zCoordDrain, surfaceLine.Name);
throw new System.Exception(message);
}
}
}
}