// Copyright (C) Stichting Deltares 2024. 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;
using System.Collections.Generic;
using System.Linq;
using Deltares.DamEngine.Data.Geometry;
namespace Deltares.DamEngine.Data.Geotechnics;
///
/// Represents a meta-data set of instances that describe
/// special point-locations of a geometric surfaceline.
///
public class CharacteristicPointSet : IList, IList
{
private readonly List annotations;
private readonly Dictionary typeCache;
private GeometryPointString geometry;
private bool geometryMustContainPoint;
///
/// Creates a characteristic point set whose points act as meta data for a geometric
/// description ( false) of a surfaceline.
///
public CharacteristicPointSet()
{
typeCache = new Dictionary();
annotations = new List();
geometryMustContainPoint = false;
}
///
/// The observed localized geometry point string, describing the dike surfaceline
/// shape.
///
/// References by aggregation.
public GeometryPointString Geometry
{
get
{
return geometry;
}
set
{
geometry = value;
FullSyncWithGeometry();
}
}
///
/// When true, requires all instances part of
/// this set to be contained in and cannot have instances
/// not part of .
/// When false, all instances are required NOT
/// to be part of . Their height will then be determined by
/// the height profile defined by .
///
public bool GeometryMustContainPoint
{
get
{
return geometryMustContainPoint;
}
set
{
geometryMustContainPoint = value;
FullSyncWithGeometry();
}
}
///
/// Retrieves the annotated with the given type.
///
/// Annotation to look for.
/// The instances annotated with the given type;
/// Null if no definition can be found.
/// This method relies on this class observing changes to its
/// to work correctly.
public Point2D GetPoint2D(CharacteristicPointType characteristicPointType)
{
return typeCache.ContainsKey(characteristicPointType)
? typeCache[characteristicPointType]
: null;
}
///
/// Sorts this characteristic point set items on
/// ascending. If is true,
/// shall also be sorted.
///
public void Sort()
{
if (GeometryMustContainPoint && Geometry != null)
{
Geometry.SortPointsByXAscending();
}
annotations.Sort();
}
///
/// Annotates the characteristic point at the specified index.
///
/// The index.
/// The new type of the characteristic point.
/// When is
/// less then 0 or greater or equal to .
/// will be returning the correct points.
public void Annotate(int index, CharacteristicPointType characteristicPointType)
{
annotations[index].CharacteristicPointType = characteristicPointType;
UpdateTypeCache(annotations[index]);
}
///
/// Snaps the characteristic point height to if it's not part
/// of it, or set it to in case no
/// is available.
///
/// The characteristic point whose height is to be updated.
private void UpdateCharacteristicPointHeight(CharacteristicPoint characteristicPoint)
{
if (geometry == null)
{
characteristicPoint.Point.Z = double.NaN;
}
else if (!geometry.Points.Contains(characteristicPoint.Point))
{
if (double.IsNaN(characteristicPoint.Point.X))
{
characteristicPoint.Point.Z = double.NaN;
}
else
{
// Note: Cannot use GeometryPointString.GetZAtX due to its requirement
// that Geometry.Points is sorted on X.
characteristicPoint.Point.Z = geometry.GetZAtUnsortedX(characteristicPoint.Point.X);
}
}
// Else: Keep Z the same as in Geometry
}
private void FullSyncWithGeometry()
{
if (GeometryMustContainPoint)
{
// Resync with geometry; Do not keep previous data:
ClearCharacteristicPointSet();
if (geometry == null)
{
return;
}
foreach (Point2D point in geometry.Points)
{
Add(new CharacteristicPoint
{
Point = point,
CharacteristicPointType = CharacteristicPointType.None
});
}
}
else
{
// Remove all characteristic points whose GeometryPoint are part of Geometry
// as per required by the value of GeometryMustContainPoint. All other
// characteristic points should have their height updated:
foreach (CharacteristicPoint characteristicPoint in annotations.ToArray())
{
if (geometry != null && geometry.Points.Contains(characteristicPoint.Point))
{
Remove(characteristicPoint);
}
else
{
UpdateCharacteristicPointHeight(characteristicPoint);
}
}
}
}
private void ClearCharacteristicPointSet()
{
ClearAnnotations();
typeCache.Clear();
}
private void ClearAnnotations()
{
annotations.Clear();
}
private void UpdateTypeCache(CharacteristicPoint item)
{
if (item.CharacteristicPointType != CharacteristicPointType.None)
{
// Prevent duplication:
CharacteristicPoint alreadyDefined = annotations.FirstOrDefault(cp => cp.CharacteristicPointType == item.CharacteristicPointType &&
!ReferenceEquals(cp.Point, item.Point));
if (alreadyDefined != null)
{
alreadyDefined.CharacteristicPointType = CharacteristicPointType.None;
}
// Set new annotation definition:
typeCache[item.CharacteristicPointType] = item.Point;
}
}
#region Implementation: IList, IList
///
/// Gets an object that can be used to synchronize access to the .
///
public object SyncRoot { get; } = new object();
///
/// Gets a value indicating whether access to the is synchronized (thread safe).
///
public bool IsSynchronized
{
get
{
return false;
}
}
///
/// Gets a value indicating whether the has a fixed size.
///
public bool IsFixedSize
{
get
{
return false;
}
}
///
/// Gets or sets the element at the specified index.
///
/// The index.
///
object IList.this[int index]
{
get
{
return this[index];
}
set
{
this[index] = (CharacteristicPoint) value;
}
}
///
/// Gets the number of elements contained in the .
///
public int Count
{
get
{
return annotations.Count;
}
}
///
/// Gets a value indicating whether the is read-only.
///
public bool IsReadOnly
{
get
{
return false;
}
}
///
/// Gets or sets the element at the specified index.
///
/// The index.
///
public CharacteristicPoint this[int index]
{
get
{
return annotations[index];
}
set
{
RemoveAt(index);
Insert(index, value);
}
}
///
/// Adds an item to the .
///
/// The object to add to the .
///
/// The position into which the new element was inserted, or -1 to indicate that the item was not inserted into the collection,
///
public int Add(object value)
{
Add((CharacteristicPoint) value);
return Count - 1;
}
///
/// Determines whether the contains a specific value.
///
/// The object to locate in the .
///
/// true if the is found in the ; otherwise, false.
///
public bool Contains(object value)
{
var cp = value as CharacteristicPoint;
if (cp == null)
{
return false;
}
return Contains(cp);
}
///
/// Copies the elements of the to an , starting at a particular index.
///
/// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing.
/// The zero-based index in at which copying begins.
public void CopyTo(Array array, int index)
{
CopyTo((CharacteristicPoint[]) array, index);
}
///
/// Determines the index of a specific item in the .
///
/// The object to locate in the .
///
/// The index of if found in the list; otherwise, -1.
///
public int IndexOf(object value)
{
return IndexOf((CharacteristicPoint) value);
}
///
/// Inserts an item to the at the specified index.
///
/// The zero-based index at which should be inserted.
/// The object to insert into the .
public void Insert(int index, object value)
{
Insert(index, (CharacteristicPoint) value);
}
///
/// Removes the first occurrence of a specific object from the .
///
/// The object to remove from the .
public void Remove(object value)
{
Remove((CharacteristicPoint) value);
}
///
/// Removes the first occurrence of a specific object from the .
///
/// The object to remove from the .
///
/// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original .
///
public bool Remove(CharacteristicPoint item)
{
bool removed = PerformCollectionRemoveWithEvents(annotations, item);
if (removed)
{
typeCache.Remove(item.CharacteristicPointType);
bool removeGeometryPoint = GeometryMustContainPoint && Geometry != null &&
Geometry.Points.Contains(item.Point) &&
!annotations.Any(cp => ReferenceEquals(cp.Point, item.Point));
if (removeGeometryPoint)
{
PerformCollectionRemoveWithEvents(Geometry.Points, item.Point);
}
}
return removed;
}
///
/// Adds an item to the .
///
/// The object to add to the .
public void Add(CharacteristicPoint item)
{
Insert(Count, item);
}
///
/// Removes all items from the .
///
public void Clear()
{
ClearCharacteristicPointSet();
if (GeometryMustContainPoint && Geometry != null)
{
Geometry.Points.Clear();
}
}
///
/// Determines whether the contains a specific value.
///
/// The object to locate in the .
///
/// true if is found in the ; otherwise, false.
///
public bool Contains(CharacteristicPoint item)
{
return annotations.Contains(item);
}
///
/// Copies to.
///
/// The array.
/// Index of the array.
public void CopyTo(CharacteristicPoint[] array, int arrayIndex)
{
annotations.CopyTo(array, arrayIndex);
}
///
/// Determines the index of a specific item in the .
///
/// The object to locate in the .
///
/// The index of if found in the list; otherwise, -1.
///
public int IndexOf(CharacteristicPoint item)
{
return annotations.IndexOf(item);
}
///
/// Inserts an item to the at the specified index.
///
/// The zero-based index at which should be inserted.
/// The object to insert into the .
public void Insert(int index, CharacteristicPoint item)
{
if (!ReferenceEquals(item.PointSet, this))
{
item.PointSet = this;
}
Point2D itemAtIndex = index < annotations.Count ? annotations[index].Point : null;
PerformListInsertWithEvents(annotations, item, index);
if (GeometryMustContainPoint && Geometry != null && !Geometry.Points.Contains(item.Point))
{
int geometryIndex = Geometry.Points.Count;
if (null != itemAtIndex)
{
for (var i = 0; i < Geometry.Points.Count; i++)
{
if (ReferenceEquals(Geometry.Points[i], itemAtIndex))
{
geometryIndex = i;
break;
}
}
}
// Check if point at same position already exists and set that point to the existing point
// Do this to avoid points in Surfaceline.Geometry with the same location
var IsPointExist = false;
for (var i = 0; i < Geometry.Points.Count; i++)
{
if (Geometry.Points[i].LocationEquals(item.Point))
{
item.Point = Geometry.Points[i];
IsPointExist = true;
break;
}
}
if (!IsPointExist)
{
// Only add new point if no point on same location was found
PerformListInsertWithEvents(Geometry.Points, item.Point, geometryIndex);
}
}
if (geometry != null)
{
UpdateCharacteristicPointHeight(item);
}
UpdateTypeCache(item);
}
///
/// Removes the item at the specified index.
///
/// The zero-based index of the item to remove.
public void RemoveAt(int index)
{
Remove(annotations[index]);
}
///
/// Returns an enumerator that iterates through a collection.
///
///
/// An object that can be used to iterate through the collection.
///
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
///
/// Returns an enumerator that iterates through the collection.
///
///
/// A that can be used to iterate through the collection.
///
public IEnumerator GetEnumerator()
{
return annotations.GetEnumerator();
}
#endregion
#region Events
///
///
/// Type of the list elements.
/// The list where the item is inserted into.
/// The item inserted into the list.
/// Index where the item is inserted.
private static void PerformListInsertWithEvents(IList list, T item, int index)
{
list.Insert(index, item);
}
private static bool PerformCollectionRemoveWithEvents(ICollection list, T item)
{
bool removed = list.Remove(item);
return removed;
}
#endregion
}