// Copyright (C) Stichting Deltares and State of the Netherlands 2025. All rights reserved. // // This file is part of Riskeer. // // Riskeer is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser 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 Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser 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 GeoAPI.Geometries; using MathNet.Spatial.Euclidean; using MathNet.Spatial.Units; using NetTopologySuite.Geometries; using Point2D = Core.Common.Base.Geometry.Point2D; namespace Core.Common.Geometry { /// /// This class contains more advanced mathematical routines for 2D geometries. /// public static class AdvancedMath2D { /// /// Calculates the intersection between two polygons, which can result in any number of polygons which represent the intersecting area. Polygons /// are defined by an array of points. /// /// Points of the first polygon. /// Points of the second polygon. /// A collection of point arrays. Each point array describes an intersecting area of the polygons. /// Thrown in case of invalid polygons. public static IEnumerable PolygonIntersectionWithPolygon(IEnumerable pointsOfPolygonA, IEnumerable pointsOfPolygonB) { Polygon polygonA = PointsToPolygon(pointsOfPolygonA); Polygon polygonB = PointsToPolygon(pointsOfPolygonB); try { return BuildSeparateAreasFromCoordinateList(polygonA.Intersection(polygonB)); } catch (TopologyException e) { throw new InvalidPolygonException(e.Message, e); } } /// /// Completes a line shape so that it becomes a polygon by adding two bottom points to the shape. /// The location of the bottom points are determined by the 's first and /// last points' x-coordinate and by for the y-coordinate. /// /// The line to complete. /// The level at which to place the points completing the polygon. /// A new collection of , with the line's points and /// the two new bottom points. /// Thrown when is null. /// /// Thrown when contains /// less than 2 points. public static IEnumerable CompleteLineToPolygon(IEnumerable line, double completingPointsLevel) { if (line == null) { throw new ArgumentNullException(nameof(line)); } if (line.Count() < 2) { throw new ArgumentException(@"The line needs to have at least two points to be able to create a complete polygon.", nameof(line)); } return GetPointsFromLine(line, completingPointsLevel); } /// /// Transforms X coordinates in a 2D X, Y plane using: /// - A reference point as starting point of the line. /// - An offset at which the reference coincides with the X axis. /// - A rotation from North of the X coordinates around the origin after subtracting the offset in degrees. /// /// The X coordinates of a line. /// The point of reference where the line is transposed to. /// The offset at which the referencePoints coincides with the X axis. /// The rotation from the North in degrees. /// A collection of with the transformed X coordinates. /// Thrown when or /// is null. public static IEnumerable FromXToXY(IEnumerable xCoordinates, Point2D referencePoint, double offset, double rotation) { if (xCoordinates == null) { throw new ArgumentNullException(nameof(xCoordinates), @"Cannot transform to coordinates without a source."); } if (referencePoint == null) { throw new ArgumentNullException(nameof(referencePoint), @"Cannot transform to coordinates without a reference point."); } return xCoordinates.Select(coordinate => { var referenceVector = new Vector2D(referencePoint.X, referencePoint.Y); Vector2D pointVector = referenceVector + new Vector2D(0, coordinate - offset).Rotate(-rotation, AngleUnit.Degrees); return new Point2D(pointVector.X, pointVector.Y); }).ToArray(); } /// /// Gets the interior point of a polygon. /// /// The outer ring of the polygon. /// The inner rings of the polygon. /// The interior point. /// Thrown when any /// parameter is null. public static Point2D GetPolygonInteriorPoint(IEnumerable outerRing, IEnumerable> innerRings) { if (outerRing == null) { throw new ArgumentNullException(nameof(outerRing)); } if (innerRings == null) { throw new ArgumentNullException(nameof(innerRings)); } Polygon outerPolygon = PointsToPolygon(outerRing); IEnumerable innerPolygons = innerRings.Select(PointsToPolygon).ToArray(); var polygon = new Polygon(outerPolygon.Shell, innerPolygons.Select(p => p.Shell).ToArray()); IPoint interiorPoint = polygon.InteriorPoint; return new Point2D(interiorPoint.X, interiorPoint.Y); } /// /// Gets an indicator whether the given lies in the polygon. /// /// The point to check. /// The outer ring of the polygon. /// The inner rings of the polygon. /// true when the lies in the polygon; false otherwise. /// Thrown when any parameter is null. public static bool PointInPolygon(Point2D point, IEnumerable outerRing, IEnumerable> innerRings) { if (point == null) { throw new ArgumentNullException(nameof(point)); } if (outerRing == null) { throw new ArgumentNullException(nameof(outerRing)); } if (innerRings == null) { throw new ArgumentNullException(nameof(innerRings)); } Polygon outerPolygon = PointsToPolygon(outerRing); IEnumerable innerPolygons = innerRings.Select(PointsToPolygon).ToArray(); var polygon = new Polygon(outerPolygon.Shell, innerPolygons.Select(p => p.Shell).ToArray()); return polygon.Covers(new Point(point.X, point.Y)); } private static IEnumerable GetPointsFromLine(IEnumerable line, double completingPointsLevel) { foreach (Point2D point in line) { yield return point; } yield return new Point2D(line.Last().X, completingPointsLevel); yield return new Point2D(line.First().X, completingPointsLevel); } private static Polygon PointsToPolygon(IEnumerable points) { List pointList = points.ToList(); Point2D firstPoint = pointList.First(); if (!firstPoint.Equals(pointList.Last())) { pointList.Add(firstPoint); } Coordinate[] coordinates = pointList.Select(p => new Coordinate(p.X, p.Y)).ToArray(); return new Polygon(new LinearRing(coordinates)); } private static IEnumerable BuildSeparateAreasFromCoordinateList(IGeometry geometry) { var geometryCollection = geometry as GeometryCollection; if (geometryCollection == null) { if (geometry.Coordinates.Any()) { return new[] { geometry.Coordinates.Distinct().Select(c => new Point2D(c.X, c.Y)).ToArray() }; } return Enumerable.Empty(); } var areas = new List(); if (!geometryCollection.IsEmpty) { for (var i = 0; i < geometry.NumGeometries; i++) { areas = areas.Union(BuildSeparateAreasFromCoordinateList(geometryCollection[i])).ToList(); } } return areas; } } }