// 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.ComponentModel; using System.Linq; using System.Xml.Serialization; using Deltares.Standard; using Deltares.Standard.Attributes; using Deltares.Standard.EventPublisher; using Deltares.Standard.IO.Xml; using Deltares.Standard.Specifications; using NetTopologySuite.Geometries; using XmlSerializer = Deltares.Standard.IO.Xml.XmlSerializer; namespace Deltares.Dam.Data.Sensors; /// /// This class represents a sensor or monitoring point used in dikes. /// This entity is created for Dam Live /// [Serializable] public class Sensor : IName, IVisibleEnabled { private readonly NetTopologySuite.Geometries.Geometry geometry; private readonly HashSet plLineTypes; private double relativeLocation; private int id; private SensorType type; public Sensor() { plLineTypes = new HashSet(); ID = -1; geometry = new Point(0, 0, 0); } public Sensor(Point point) : this() { if (point == null) { throw new ArgumentNullException("point"); } geometry = point; } public Sensor(double x, double y, double z) : this(new Point(x, y, z)) {} /// /// Gets or sets the ID. /// /// /// The ID should be unique. /// [PropertyOrder(1, 1)] public int ID { get { return id; } set { id = value; DataEventPublisher.AfterChange(this, s => s.ID); } } /// /// Gets or sets the depth. /// /// /// The depth. /// [PropertyOrder(1, 5)] [Format("F3")] public double Depth { get { return ZRd; } set { DataEventPublisher.BeforeChange(this, s => s.Depth); ZRd = value; DataEventPublisher.AfterChange(this, s => s.Depth); } } /// /// Gets or sets the relative location along the profile. /// /// /// The relative location in meter. /// [PropertyOrder(1, 4)] [Format("F3")] public double RelativeLocation { get { return relativeLocation; } set { DataEventPublisher.BeforeChange(this, s => s.RelativeLocation); relativeLocation = value; RelativeLocationSpecified = true; DataEventPublisher.AfterChange(this, s => s.RelativeLocation); } } /// /// Gets or sets the X rd. /// /// /// The X rd. /// [Browsable(false)] public double XRd { get { return geometry.Coordinate.X; } set { geometry.Coordinate.X = value; } } /// /// Gets or sets the Y rd. /// /// /// The Y rd. /// [Browsable(false)] public double YRd { get { return geometry.Coordinate.Y; } set { geometry.Coordinate.Y = value; } } /// /// Gets or sets the Z rd. Same as Depth?? /// /// /// The Z rd. /// [Browsable(false)] public double ZRd { get { return geometry.Coordinate.Z; } set { geometry.Coordinate.Z = value; } } /// /// Gets a value indicating whether the relative location is specified. /// /// /// true if the relative location is specified; otherwise, false. /// [Browsable(false)] public bool RelativeLocationSpecified { get; private set; } /// /// Gets or sets the type of this sensor. /// /// /// The type. Default value is PiezoMetricHead. /// [PropertyOrder(1, 7)] public SensorType Type { get { return type; } set { type = value; } } /// /// Gets or sets the PL line array. Used for serialization only. /// /// /// The PL line array. /// [XmlElement("PLLineMappings")] [Specification(typeof(NotNullSpecification))] [Specification(typeof(ContiansAtLeastOneItem))] [Browsable(false)] public PLLineType[] PLLineMappings { get { return plLineTypes.ToArray(); } set { ClearPLLines(); foreach (PLLineType lineType in value) { Add(lineType); } } } [XmlIgnore] [PropertyOrder(1, 6)] public string PLLineMappingsAsString { get { var res = ""; foreach (PLLineType plLineType in plLineTypes) { switch (plLineType) { case PLLineType.PL1: res = res + "1; "; break; case PLLineType.PL2: res = res + "2; "; break; case PLLineType.PL3: res = res + "3; "; break; case PLLineType.PL4: res = res + "4; "; break; } } return res; } set { ClearPLLines(); List locPlLineTypes = ParseStringToPlLineTypes(value); foreach (int plLineType in locPlLineTypes) { switch (plLineType) { case 1: plLineTypes.Add(PLLineType.PL1); break; case 2: plLineTypes.Add(PLLineType.PL2); break; case 3: plLineTypes.Add(PLLineType.PL3); break; case 4: plLineTypes.Add(PLLineType.PL4); break; } } } } /// /// Gets or sets the name of this sensor. /// /// /// The name string value should not be empty and unique. /// [Specification(typeof(NotEmptySpecification))] [PropertyOrder(1, 2)] public string Name { get; set; } /// /// Adds the specified PL line. /// /// The PL line. public void Add(PLLineType plLine) { plLineTypes.Add(plLine); } /// /// Removes the specified PL line. /// /// The PL line. public void Remove(PLLineType plLine) { plLineTypes.Remove(plLine); } /// /// Clears the PL lines list. /// public void ClearPLLines() { plLineTypes.Clear(); } /// /// Determines whether this instance is valid. /// /// /// true if this instance is valid; otherwise, false. /// public bool IsValid() { return !Validator.Validate(this).Any(); } /// /// Determines whether this instance is transient (associated with a correct ID in the context of the repository). /// /// /// true if this instance is transient; otherwise, false. /// public bool IsTransient() { return ID < 0; } public override string ToString() { string name = string.IsNullOrWhiteSpace(Name) ? "name_not_set" : Name; return $"[ID: {ID}, Name: {name}, Depth: {Depth}, Type: {Type}, RelativeLocation: {RelativeLocation}]"; } public bool IsVisible(string property) { switch (property) { case "XRd": return false; case "YRd": return false; case "ZRd": return false; default: return true; } } public bool IsEnabled(string property) { return true; } /// /// Serializes this instance to a xml string. /// /// A xml string internal string Serialize() { var xmlSerializer = new XmlSerializer(); return xmlSerializer.SerializeToString(this); } /// /// Deserializes the specified XML. /// /// The XML to deserialize. /// A sensor instance internal static Sensor Deserialize(string xml) { var xmlDeserializer = new XmlDeserializer(); return (Sensor) xmlDeserializer.XmlDeserializeFromString(xml, typeof(Sensor)); } private List ParseStringToPlLineTypes(string value) { value = value.Trim(); var ids = new List(); string[] idsarr = value.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); foreach (string s in idsarr) { try { int val = Int32.Parse(s); ids.Add(val); } catch (Exception) { // surpress errors, just do not use value } } return ids; } #region Business Rules /// /// Specication to test if the value of the candidate is valid /// internal class ContiansAtLeastOneItem : PredicateSpecification> { public ContiansAtLeastOneItem() : base(x => x.Any()) { Description = "The sensor should contain at least one PL Line mapping."; } } #endregion }