// Copyright (C) Stichting Deltares 2025. 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 NetTopologySuite.Features; using NetTopologySuite.Geometries; using NetTopologySuite.IO; namespace Deltares.Maps; public class Feature : IFeature { private readonly NetTopologySuite.Features.Feature innerFeature; private readonly AttributesDictionary attributes; private string geomWkt; private int? oldHashCode; private Guid id = Guid.NewGuid(); private Feature(string geomWkt) { this.geomWkt = geomWkt; Geometry geometry = ParseGeometryString(geomWkt); innerFeature = new NetTopologySuite.Features.Feature(geometry, new AttributesTable()); attributes = new AttributesDictionary(innerFeature.Attributes); } private Feature(Geometry geometry) { geomWkt = WriteWktString(geometry); innerFeature = new NetTopologySuite.Features.Feature(geometry, new AttributesTable()); attributes = new AttributesDictionary(innerFeature.Attributes); } private Feature(NetTopologySuite.Features.Feature feature) { geomWkt = WriteWktString(feature.Geometry); innerFeature = feature; attributes = new AttributesDictionary(innerFeature.Attributes); } public object this[string attributeName] { get { return attributes[attributeName]; } set { attributes[attributeName] = value; } } public object Tag { get; set; } public IAttributesTable Attributes { get { return attributes; } } public virtual string WktFormat { get { if (innerFeature.Geometry == null) { if (string.IsNullOrEmpty(geomWkt)) { geomWkt = WriteWktString(innerFeature.Geometry); } } return geomWkt; } } /// /// Gets the geometry. It parses the geometry wkt string when needed /// public Geometry Geometry { get { if (innerFeature.Geometry == null) { if (!string.IsNullOrEmpty(geomWkt)) { innerFeature.Geometry = ParseGeometryString(geomWkt); } else { throw new InvalidOperationException( "The internal state for this object is not valid. The internal geometry is empty."); } } return innerFeature.Geometry; } } public virtual Guid Id { get { return id; } } public static Feature Create(NetTopologySuite.Features.Feature innerFeature) { return new Feature(innerFeature); } public static Feature Create(string geomWkt, IEnumerable> attributes) { Feature feature = Create(geomWkt); foreach (KeyValuePair attribute in attributes) { feature.AddAttribute(attribute.Key, attribute.Value); } return feature; } /// /// Creates a feature with a geometry parsed from the argument string /// /// The well known geometry string /// A feature public static Feature Create(string geomWkt) { if (geomWkt == null) { throw new ArgumentNullException("geomWkt"); } try { return new Feature(geomWkt); } catch (Exception exception) { if (exception.Message.Contains("','")) { throw new ArgumentException( "There was an error parsing the WKT geometry string, maybe due to an incorrect culture format conversion", "geomWkt", exception); } } throw new InvalidOperationException(); } public static Feature Create(double x, double y, IEnumerable> attributes) { Feature feature = Create(x, y); foreach (KeyValuePair attribute in attributes) { feature.AddAttribute(attribute.Key, attribute.Value); } return feature; } public static Feature Create(Coordinate coordinate) { if (coordinate == null) { throw new ArgumentNullException("coordinate"); } return new Feature(new Point(coordinate.X, coordinate.Y)); } public static Feature Create(Geometry geometry) { if (geometry == null) { throw new ArgumentNullException("geometry"); } return new Feature(geometry); } public static bool operator ==(Feature x, Feature y) { return Equals(x, y); } public static bool operator !=(Feature x, Feature y) { return !(x == y); } public void AddAttribute(string attributeName, object value) { try { Attributes.Add(attributeName, value); } catch (ArgumentException exception) { throw new ArgumentException(attributeName, exception); } } public override bool Equals(object obj) { var other = obj as Feature; if (other == null) { return false; } // handle the case of comparing two NEW objects bool otherIsTransient = Equals(other.Id, Guid.Empty); bool thisIsTransient = Equals(Id, Guid.Empty); if (otherIsTransient && thisIsTransient) { return ReferenceEquals(other, this); } return other.Id.Equals(Id); } public override int GetHashCode() { // Once we have a hash code we'll never change it if (oldHashCode.HasValue) { return oldHashCode.Value; } bool thisIsTransient = Equals(Id, Guid.Empty); // When this instance is transient, we use the base GetHashCode() // and remember it, so an instance can NEVER change its hash code. if (thisIsTransient) { oldHashCode = base.GetHashCode(); return oldHashCode.Value; } return Id.GetHashCode(); } public NetTopologySuite.Features.Feature GetInnerFeature() { return innerFeature; } private void IntializeGeometry() { id = Guid.NewGuid(); } /// /// Creates an empty feature with a point geometry /// /// /// /// private static Feature Create(double x, double y) { return new Feature(new Point(x, y)); } private string WriteWktString(Geometry geometry) { var writer = new WKTWriter(); return CultureHelper.InvokeWithUSCulture(() => writer.Write(geometry)); } private Geometry ParseGeometryString(string geomWkt) { var reader = new WKTReader(); return reader.Read(geomWkt); } }