// 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; } } } }