using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Xml.Serialization; using Deltares.Geographic; using Deltares.Geotechnics; using Deltares.Geotechnics.GeotechnicalGeometry; using Deltares.Standard; using Deltares.Standard.Attributes; using Deltares.Standard.Data; using Deltares.Standard.EventPublisher; using Deltares.Standard.Extensions; using Deltares.Standard.Units; using Deltares.Standard.Validation; namespace Deltares.DeltaModel { /// /// Block revetment Cross Section Object /// public class BlockRevetmentCrossSection : IGeographicString, IComparable, IHasSelectedSubObject, IDisposable { private readonly List points = new List(); private bool applyAboveToetsPeilCriteria; private bool applyLowerPeriodCriteria; private double averageHighWaterLevel = double.NaN; private double averageLowWaterLevel = double.NaN; private DikeLinePoint beginPoint; private double betaDijk; private YesNoQuestionMark breedteKering; private DikeLinePoint endPoint; private string name = ""; private object selectedSubObject; private LocalizedGeometryPointString surfaceLine; private double tanaBottom; private double zbodem; /// /// Initializes a new instance of the class. /// When a is supplied, the of this provider is used to project /// the segments upon. /// public BlockRevetmentCrossSection(IHasSurfaceLine surfaceLineProvider) { Segments = new DelegatedList { AddMethod = AddSegment, DeleteMethod = RemoveSegment }; surfaceLine = null != surfaceLineProvider ? surfaceLineProvider.SurfaceLine : new LocalizedGeometryPointString(); } /// /// Initializes a new instance of the class based /// on an already existing instance. When a is supplied, the of this provider is used to project /// the segments upon. /// public BlockRevetmentCrossSection(BlockRevetmentCrossSection blockRevetmentCrossSection, IHasSurfaceLine surfaceLineProvider) : this(surfaceLineProvider) { blockRevetmentCrossSection.CopyTo(this); } [Obsolete("Only use this for deserialization")] public BlockRevetmentCrossSection() : this(null) {} /// /// List of all the segments that make up this cross section. /// public DelegatedList Segments { get; private set; } /// /// Gets or sets the name. /// /// /// The name. /// [Data] // Used for visualization in map [PropertyOrder(0, 1)] public string Name { get { return name; } set { this.SetAndNotify2(out name, value, x => x.Name); } } /// /// Gets or sets the dike line. /// /// /// The dike line. /// [ReadOnly(true)] [PropertyOrder(1, 1)] public DikeLine DikeLine { get; set; } /// /// Gets or sets the begin point. /// /// /// The begin point. /// [Browsable(false)] public DikeLinePoint BeginPoint { get { return beginPoint; } set { if (value != beginPoint) { this.SetAndNotify2(out beginPoint, value, x => x.BeginPoint); } } } /// /// Gets or sets the end point. /// /// /// The end point. /// [Browsable(false)] public DikeLinePoint EndPoint { get { return endPoint; } set { if (value != endPoint) { this.SetAndNotify2(out endPoint, value, x => x.EndPoint); } } } /// /// Gets the begin offset. /// /// /// The begin offset. /// [XmlIgnore] [Unit(UnitType.Length)] [PropertyOrder(1, 2)] [Format("F2")] public double BeginOffset { get { return BeginPoint != null ? BeginPoint.Offset : double.NaN; } } /// /// Gets the end offset. /// /// /// The end offset. /// [XmlIgnore] [Unit(UnitType.Length)] [PropertyOrder(1, 3)] [Format("F2")] public double EndOffset { get { return EndPoint != null ? EndPoint.Offset : double.NaN; } } /// /// Value indicating whether to apply lower period criteria. /// [Category("Properties")] [PropertyOrder(2, 0)] public bool ApplyLowerPeriodCriteria { get { return applyLowerPeriodCriteria; } set { this.SetAndNotify2(out applyLowerPeriodCriteria, value, x => x.ApplyLowerPeriodCriteria); } } /// /// Value indicating whether to apply above toetspeil criteria. /// [Category("Properties")] [PropertyOrder(2, 1)] public bool ApplyAboveToetsPeilCriteria { get { return applyAboveToetsPeilCriteria; } set { this.SetAndNotify2(out applyAboveToetsPeilCriteria, value, x => x.ApplyAboveToetsPeilCriteria); } } /// /// Direction of the normal on the dike (Richting normaal op dijk (gr tov N)) /// [Unit(UnitType.Angle, GeometryAngleUnit.deg)] // gr tov N [Format("F3")] [Clearable] [Category("Properties")] [PropertyOrder(2, 2)] public double BetaDijk { get { return betaDijk; } set { this.SetAndNotify2(out betaDijk, value, x => x.BetaDijk); } } /// /// Average slope of the foreland (vert. : hor.) /// for a few tens of meters to the toe of the dike or jetty /// TODO: Comment /// [Unit(UnitType.None, GeometryAngleUnit.tan)] [Format("F3")] [Clearable] [Category("Properties")] [PropertyOrder(2, 3)] [ReadOnly(true)] public double TanaBottom { get { return tanaBottom; } set { this.SetAndNotify2(out tanaBottom, value, x => x.TanaBottom); } } /// /// If the dike on 2.5m above the average outer water level is wider than 150 m /// (including the area lying behind it), then there is a risico of slide /// in case there is a layer of clay present. /// Not filling in any value means the same as 'n'. /// TODO: Comment // // Dutch original text: // "Als de dijk op 2,5m boven de gemiddelde // buitenwaterstand breder is dan 150 m (incl. erachter gelegen terrein), // is er een risico op afschuiving indien er een kleilaag aanwezig is. // Niets invullen staat gelijk aan 'n'. " /// [Category("Properties")] [PropertyOrder(2, 4)] public YesNoQuestionMark BreedteKering { get { return breedteKering; } set { this.SetAndNotify2(out breedteKering, value, x => x.BreedteKering); } } /// /// Height of the foreland at the toe of the dike or outside /// of the mole, relative to NAP. /// TODO: Comment /// [Format("F3")] [Clearable] [Category("Properties")] [Unit(UnitType.Depth)] [PropertyOrder(2, 5)] [ReadOnly(true)] public double Zbodem { get { return zbodem; } set { this.SetAndNotify2(out zbodem, value, x => x.Zbodem); } } /// /// Gets or sets the average low water level. /// [Unit(UnitType.Depth)] [NotClear] [Clearable] [Format("F2")] public double AverageLowWaterLevel { get { return averageLowWaterLevel; } set { this.SetAndNotify2(out averageLowWaterLevel, value, x => x.AverageLowWaterLevel); } } /// /// Gets or sets the average high water level. /// [Unit(UnitType.Depth)] [NotClear] [Clearable] [Format("F2")] public double AverageHighWaterLevel { get { return averageHighWaterLevel; } set { this.SetAndNotify2(out averageHighWaterLevel, value, x => x.AverageHighWaterLevel); } } /// /// The surface line along all segments. This collection is kept synchronized with the segment definition /// [Browsable(false)] public LocalizedGeometryPointString SurfaceLine { get { return surfaceLine; } private set { this.SetAndNotify2(out surfaceLine, value, s => s.SurfaceLine); } } #region IComparable Members /// /// Compares the current object with another object of the same type. /// /// An object to compare with this object. /// /// Result of comparing the BeginOffset values /// public int CompareTo(BlockRevetmentCrossSection other) { return BeginOffset.CompareTo(other.BeginOffset); } #endregion /// /// Creates a list of points starting with BeginPoint and ending with EndPoints and returns that. /// /// /// The points. /// [Browsable(false)] [XmlIgnore] public IList Points { get { if (points.Count == 0 && BeginPoint != null && EndPoint != null && DikeLine != null) { points.Add(BeginPoint); for (var i = 0; i < DikeLine.Points.Count; i++) { IGeographicPoint point = DikeLine.Points[i]; double offset = GeographicHelper.Instance.GetOffsetOnGeographicLineString(point, DikeLine); if (offset >= EndPoint.Offset) { break; } if (offset > BeginPoint.Offset) { points.Add(point); } } points.Add(EndPoint); } return points; } } /// /// Selected segment is just a property for the GUI /// TODO: Comment /// [Browsable(false)] [XmlIgnore] public object SelectedSubObject { get { return selectedSubObject; } set { this.SetAndNotify2(out selectedSubObject, value, x => x.SelectedSubObject); } } /// /// Shifts the surface line points according to the provided . /// /// The shift to apply. public void ShiftSurfaceLinePoints(double shift) { var orderedSurfaceLinePoints = shift > 0 ? surfaceLine.Points.OrderByDescending(slp => slp.X) // Order by descending when shifting to the right, otherwise the shifting action might be "cut off" : surfaceLine.Points.OrderBy(slp => slp.X); // Order by ascending when shifting to the left, otherwise the shifting action might be "cut off" foreach (var point in orderedSurfaceLinePoints) { point.X += shift; } } public override string ToString() { return Name; } public void Dispose() { Segments.AddMethod = null; Segments.DeleteMethod = null; } private void CopyTo(BlockRevetmentCrossSection target) { target.name = Name; target.DikeLine = DikeLine; target.applyLowerPeriodCriteria = applyLowerPeriodCriteria; target.applyAboveToetsPeilCriteria = applyAboveToetsPeilCriteria; target.betaDijk = betaDijk; target.tanaBottom = tanaBottom; target.breedteKering = breedteKering; target.zbodem = zbodem; target.averageLowWaterLevel = averageLowWaterLevel; target.averageHighWaterLevel = averageHighWaterLevel; if (null != BeginPoint) { target.BeginPoint = BeginPoint.Clone() as DikeLinePoint; } if (null != EndPoint) { target.EndPoint = EndPoint.Clone() as DikeLinePoint; } } private void AddSegment(BlockRevetmentSegment newSegment) { newSegment.Owner = this; // Take action when a segment is added without start and end point if (Double.IsNaN(newSegment.StartX) && Double.IsNaN(newSegment.EndX)) { var index = Segments.IndexOf(newSegment); var previousSegment = GetSegmentAt(index - 1); var nextSegment = GetSegmentAt(index + 1); var isPreviousSet = previousSegment != null; var isNextSet = nextSegment != null; if (isPreviousSet && isNextSet) // Handle segments that were added somewhere in the middle { previousSegment.DisconnectFrom(nextSegment); previousSegment.EndX = (previousSegment.StartX + previousSegment.EndX)/2.0; previousSegment.EndZ = (previousSegment.StartZ + previousSegment.EndZ)/2.0; } if (isPreviousSet) { previousSegment.CopyTo(newSegment); newSegment.StartX = previousSegment.EndX; newSegment.StartZ = previousSegment.EndZ; previousSegment.ConnectTo(newSegment); } else { newSegment.StartX = 0; newSegment.StartZ = 0; } if (isNextSet) { if (null == newSegment.BlockRevetmentProfile) { nextSegment.CopyTo(newSegment); } newSegment.EndX = nextSegment.StartX; newSegment.EndZ = nextSegment.StartZ; newSegment.ConnectTo(nextSegment); } else { newSegment.EndX = newSegment.StartX + 5; newSegment.EndZ = 0; } } else { var existingStartPoint = surfaceLine.GetPointAt(newSegment.StartX, newSegment.StartZ); var existingEndPoint = surfaceLine.GetPointAt(newSegment.EndX, newSegment.EndZ); var previousSegment = null == existingStartPoint ? null : Segments.FirstOrDefault(s => s.EndX.AlmostEquals(existingStartPoint.X)); var nextSegment = null == existingEndPoint ? null : Segments.FirstOrDefault(s => s.StartX.AlmostEquals(existingEndPoint.X)); if (null != previousSegment) { previousSegment.ConnectTo(newSegment); } if (null != nextSegment) { newSegment.ConnectTo(nextSegment); } } UpdateSurfaceLine(); } private BlockRevetmentSegment GetSegmentAt(int index) { if (index >= 0 && index < Segments.Count) { return Segments[index]; } return null; } /// /// This method offers functionality that is only used in the interface. If possible, try to remove it, because there /// is no such thing a a surfaceline for block revetment cross sections, just a set of segments which bind points which /// can be used to draw a surfaceline. /// private void UpdateSurfaceLine() { SurfaceLine.Points.Clear(); foreach (var segment in Segments) { segment.AddTo(SurfaceLine); } DataEventPublisher.DataListModified(SurfaceLine.Points); } private void RemoveSegment(BlockRevetmentSegment removedSegment) { var previousSegment = Segments.FirstOrDefault(s => s.IsConnectedTo(removedSegment)); var nextSegment = Segments.FirstOrDefault(s => removedSegment.IsConnectedTo(s)); if (previousSegment != null && nextSegment != null) { previousSegment.ConnectTo(nextSegment); } SelectedSubObject = previousSegment ?? nextSegment; UpdateSurfaceLine(); } } }