// Copyright (C) Stichting Deltares 2019. 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.
///
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;
}
}
}