// Copyright (C) Stichting Deltares 2026. All rights reserved.
//
// This file is part of the application DAM - UI.
//
// DAM - UI 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.Linq;
using System.Text;
using Deltares.Geometry;
namespace Deltares.Dam.Data;
public class PolyLineException : Exception
{
public PolyLineException(string message)
: base(message) {}
}
public class PolyLine where T : GeometryPoint, new()
{
protected IList points;
public PolyLine()
{
points = new List();
}
public virtual int Id { get; }
public virtual string Name { get; set; }
public virtual IList Points
{
get
{
return points;
}
private set
{
points = value;
}
}
///
/// Check if lines are equal
///
///
///
public virtual bool Equals(PolyLine other)
{
bool isEqual = other.Points.Count == Points.Count;
if (isEqual)
{
for (var pointIndex = 0; pointIndex < Points.Count; pointIndex++)
{
isEqual = isEqual && Points[pointIndex].LocationEquals(other.Points[pointIndex]);
}
}
return isEqual;
}
///
/// Check if line has a minimum of two points
///
///
public bool Exists()
{
return Points.Count > 1;
}
///
/// Check if all the points are in strict ascending X order
///
///
public bool IsXStrictAscending()
{
var isStrictAscending = true;
for (var pointIndex = 0; pointIndex < Points.Count - 1; pointIndex++)
{
if (Points[pointIndex + 1].X <= Points[pointIndex].X)
{
isStrictAscending = false;
break;
}
}
return isStrictAscending;
}
///
/// Check if all the points are in ascending X order
///
///
public bool IsXAscending()
{
var isAscending = true;
for (var pointIndex = 0; pointIndex < Points.Count - 1; pointIndex++)
{
if (Points[pointIndex + 1].X < Points[pointIndex].X)
{
isAscending = false;
break;
}
}
return isAscending;
}
///
/// Gets point at X if present within tolerance or null if not present.
///
///
public T GetPointAtX(double X, double tolerance)
{
return (from T point in points
where Math.Abs(point.X - X) < tolerance
select point).FirstOrDefault();
}
///
/// Gets point at X if present within tolerance; creates one there if not present.
///
///
public T EnsurePointAtX(double X, double tolerance)
{
T point = GetPointAtX(X, tolerance);
if (point == null)
{
point = InsertPointAtX(X);
}
return point;
}
///
/// If IsXStrictAscending then return the interpolated value for any X-value
///
///
///
public double YFromX(double X)
{
return YZFromX(X, PointType.XY);
}
public double ZFromX(double X)
{
return YZFromX(X, PointType.XZ);
}
public IList IntersectionsXAtZ(double z)
{
var intersectionsX = new List();
if (points.Count >= 2)
{
//#bka: Tom is dit geen circular reference? Line staat PLLinesCreator! Die dit weer roept!?
var lineAtZ = new Line
{
BeginPoint = new GeometryPoint(points.First().X, 0, z),
EndPoint = new GeometryPoint(points.Last().X, 0, z)
};
for (var pointIndex = 0; pointIndex < points.Count - 1; pointIndex++)
{
var line = new Line
{
BeginPoint = points[pointIndex],
EndPoint = points[pointIndex + 1]
};
var intersectionPoint = new GeometryPoint();
if (LineHelper.GetStrictIntersectionPoint(line, lineAtZ, ref intersectionPoint))
{
intersectionsX.Add(intersectionPoint.X);
}
}
}
return intersectionsX;
}
public IList IntersectionPointsXzWithLineXz(Line line)
{
var intersectionPointsWithLine = new List();
if (points.Count >= 2)
{
for (var pointIndex = 0; pointIndex < points.Count - 1; pointIndex++)
{
var lineInPoly = new Line
{
BeginPoint = points[pointIndex],
EndPoint = points[pointIndex + 1]
};
var intersectionPoint = new GeometryPoint();
if (LineHelper.GetStrictIntersectionPoint(lineInPoly, line, ref intersectionPoint))
{
intersectionPointsWithLine.Add(intersectionPoint);
}
}
}
return intersectionPointsWithLine;
}
public virtual void CopyPoints(PolyLine polyLine)
{
foreach (T point in polyLine.Points)
{
points.Add(point);
}
}
///
/// Deletes the coinsiding points.
///
/// The tolerance.
public virtual void DeleteCoinsidingPoints(double tolerance)
{
// First build a list of indices of points that have to be removed (from end of list to start)
var indicesToDelete = new List();
for (int pointIndex = Points.Count - 1; pointIndex > 0; pointIndex--)
{
for (int i = pointIndex - 1; i >= 0; i--)
{
if (Points[pointIndex].LocationEquals(Points[i], tolerance))
{
indicesToDelete.Add(i);
}
}
}
// Remove duplicate points beginning from the end
for (var index = 0; index < indicesToDelete.Count; index++)
{
Points.RemoveAt(indicesToDelete[index]);
}
}
public virtual void DeleteCoinsidingPoints()
{
const double defaultTolerance = 0.001;
DeleteCoinsidingPoints(defaultTolerance);
}
public virtual double MinZ()
{
if (Points.Count > 1)
{
double minZ = points.First().Z;
foreach (T point in points)
{
minZ = Math.Min(minZ, point.Z);
}
return minZ;
}
return 0.0;
}
public virtual double MaxZ()
{
if (Points.Count > 1)
{
double maxZ = points.First().Z;
foreach (T point in points)
{
maxZ = Math.Max(maxZ, point.Z);
}
return maxZ;
}
return 0.0;
}
public override string ToString()
{
var stringBuilder = new StringBuilder();
stringBuilder.Append(Name);
stringBuilder.Append(" [");
foreach (T point in points)
{
if (points.IndexOf(point) > 0)
{
stringBuilder.Append(", ");
}
stringBuilder.Append(point);
}
stringBuilder.Append("]");
return stringBuilder.ToString();
}
private T InsertPointAtX(double X)
{
var newPoint = new T();
newPoint.X = X;
try
{
T pointAfter = (from T point in points
where point.X > X
select point).First();
points.Insert(points.IndexOf(pointAfter), newPoint);
}
catch
{
points.Add(newPoint);
}
return newPoint;
}
private double YZFromX(double X, PointType pointType)
{
if (!IsXAscending())
{
throw new PolyLineException("Interpolation only possible with ascending polyline");
}
var valueYZ = 0.0;
// Less then first X
if (X <= Points[0].X)
{
valueYZ = pointType == PointType.XZ ? Points[0].Z : Points[0].Y;
}
// More then last X
else if (X >= Points[Points.Count - 1].X)
{
valueYZ = pointType == PointType.XZ ? Points[Points.Count - 1].Z : Points[Points.Count - 1].Y;
}
else
{
// X is inside boundaries
for (var pointIndex = 0; pointIndex < Points.Count - 1; pointIndex++)
{
if ((X > Points[pointIndex].X) && (X <= Points[pointIndex + 1].X))
{
// interpolate in this section
double fractionX = (X - Points[pointIndex].X) / (Points[pointIndex + 1].X - Points[pointIndex].X);
if (pointType == PointType.XZ)
{
valueYZ = Points[pointIndex].Z + fractionX * (Points[pointIndex + 1].Z - Points[pointIndex].Z);
}
else
{
valueYZ = Points[pointIndex].Y + fractionX * (Points[pointIndex + 1].Y - Points[pointIndex].Y);
}
break;
}
}
}
return valueYZ;
}
}