// Copyright (C) Stichting Deltares 2019. All rights reserved.
//
// This file is part of the Dam Engine.
//
// The Dam Engine is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero 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.DamEngine.Data.Geometry;
namespace Deltares.DamEngine.Data.General
{
public class PolyLine where T : GeometryPoint, new()
{
protected IList points;
public PolyLine()
{
this.points = new List();
}
public virtual int Id { get; private set; }
public virtual string Name { get; set; }
public virtual IList Points { get { return this.points; } private set { this.points = value; } }
///
/// Check if lines are equal
///
///
///
public virtual bool Equals(PolyLine other)
{
bool isEqual = (other.Points.Count == this.Points.Count);
if (isEqual)
{
for (int pointIndex = 0; pointIndex < this.Points.Count; pointIndex++)
{
isEqual = isEqual && (this.Points[pointIndex].LocationEquals(other.Points[pointIndex]));
}
}
return isEqual;
}
///
/// Check if line has a minimum of two points
///
///
public bool Exists()
{
return (this.Points.Count > 1);
}
///
/// Check if all the points are in strict ascending X order
///
///
public bool IsXStrictAscending()
{
bool isStrictAscending = true;
for (int pointIndex = 0; pointIndex < this.Points.Count - 1; pointIndex++)
{
if (this.Points[pointIndex + 1].X <= this.Points[pointIndex].X)
{
isStrictAscending = false;
break;
}
}
return isStrictAscending;
}
///
/// Check if all the points are in ascending X order
///
///
public bool IsXAscending()
{
bool isAscending = true;
for (int pointIndex = 0; pointIndex < this.Points.Count - 1; pointIndex++)
{
if (this.Points[pointIndex + 1].X < this.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 this.points
where Math.Abs(point.X - X) < tolerance
select point).FirstOrDefault();
}
private T InsertPointAtX(double X)
{
T newPoint = new T();
newPoint.X = X;
try
{
T pointAfter = (from T point in this.points
where point.X > X
select point).First();
this.points.Insert(this.points.IndexOf(pointAfter), newPoint);
}
catch
{
this.points.Add(newPoint);
}
return newPoint;
}
///
/// 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;
}
public double ZFromX(double X)
{
if (!this.IsXAscending())
{
throw new PolyLineException("Interpolation only possible with ascending polyline");
}
double valueZ = 0.0;
// Less then first X
if (X <= this.Points[0].X)
{
valueZ = this.Points[0].Z;
}
// More then last X
else if (X >= this.Points[this.Points.Count - 1].X)
{
valueZ = this.Points[this.Points.Count - 1].Z;
}
else
{
// X is inside boundaries
for (int pointIndex = 0; pointIndex < this.Points.Count - 1; pointIndex++)
{
if ((X > this.Points[pointIndex].X) && (X <= this.Points[pointIndex + 1].X))
{
// interpolate in this section
double fractionX = (X - this.Points[pointIndex].X) / (this.Points[pointIndex + 1].X - this.Points[pointIndex].X);
valueZ = this.Points[pointIndex].Z + fractionX * (this.Points[pointIndex + 1].Z - this.Points[pointIndex].Z);
break;
}
}
}
return valueZ;
}
public override string ToString()
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append(this.Name);
stringBuilder.Append(" [");
foreach (T point in this.points)
{
if (this.points.IndexOf(point) > 0)
stringBuilder.Append(", ");
stringBuilder.Append(point.ToString());
}
stringBuilder.Append("]");
return stringBuilder.ToString();
}
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 (int 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 (this.Points.Count > 1)
{
double minZ = this.points.First().Z;
foreach (T point in this.points)
{
minZ = Math.Min(minZ, point.Z);
}
return minZ;
}
else
{
return 0.0;
}
}
public virtual double MaxZ()
{
if (this.Points.Count > 1)
{
double maxZ = this.points.First().Z;
foreach (T point in this.points)
{
maxZ = Math.Max(maxZ, point.Z);
}
return maxZ;
}
else
{
return 0.0;
}
}
}
}