// Copyright (C) Stichting Deltares 2017. All rights reserved. // // This file is part of the DAM Engine. // // The DAM Engine is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero 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 Affero General Public License for more details. // // You should have received a copy of the GNU Affero 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 Deltares.DamEngine.Data.Standard.Language; using Deltares.DamEngine.Data.Standard.Validation; namespace Deltares.DamEngine.Data.Geometry { /// /// Class containing the geometry data /// /// public class GeometryData : GeometryObject { private readonly List curveDataList = new List(); private readonly List loopDataList = new List(); private readonly List pointDataList = new List(); private readonly List surfaceDataList = new List(); private readonly GeometryPointString surfaceLine = new GeometryPointString(); private double bottom = GeometryConstants.DefaultBottomLimitGeometry; private double left = GeometryConstants.DefaultLeftLimitGeometry; private double right = GeometryConstants.DefaultRightLimitGeometry; private bool updatingSurfaceLine; #region properties /// /// Gets the points. /// /// /// The points. /// [Validate] public IList Points { get { return pointDataList; } } /// /// gets the Curve data list. /// /// /// The curves. /// public List Curves { get { return curveDataList; } } /// /// gets the Loop data list. /// /// /// The loops. /// public List Loops { get { return loopDataList; } } /// /// gets the Surface data list. /// public List Surfaces { get { return surfaceDataList; } } /// /// Gets the minimum geometry points x. /// /// /// The minimum geometry points x. /// public double MinGeometryPointsX { get { return Points.Select(geometryPoint => geometryPoint.X).Concat(new[] { double.MaxValue }).Min(); } } /// /// Gets the minimum geometry points z. /// /// /// The minimum geometry points z. /// public double MinGeometryPointsZ { get { return Points.Select(geometryPoint => geometryPoint.Z).Concat(new[] { double.MaxValue }).Min(); } } /// /// Gets the maximum geometry points x. /// /// /// The maximum geometry points x. /// public double MaxGeometryPointsX { get { return Points.Select(geometryPoint => geometryPoint.X).Concat(new[] { double.MinValue }).Max(); } } /// /// Gets the maximum geometry points z. /// /// /// The maximum geometry points z. /// public double MaxGeometryPointsZ { get { return Points.Select(geometryPoint => geometryPoint.Z).Concat(new[] { double.MinValue }).Max(); } } /// /// Gets or sets the left. /// /// /// The left. /// public double Left { get { return left; } set { left = value; } } /// /// Gets or sets the right. /// /// /// The right. /// public double Right { get { return right; } set { right = value; } } /// /// Gets or sets the bottom. /// /// /// The bottom. /// public double Bottom { get { return bottom; } set { bottom = value; } } /// /// Gets all points on the Left boundary. /// /// private List GetLeftPoints() { var geometryPoints = pointDataList.Where(gp => Math.Abs(gp.X - Left) < GeometryConstants.Accuracy).ToList(); return geometryPoints; } /// /// Gets all points on the Right boundary. /// /// private List GetRightPoints() { var geometryPoints = pointDataList.Where(point => Math.Abs(point.X - Right) < GeometryConstants.Accuracy).ToList(); return geometryPoints; } /// /// Gets the geometry bounds. /// /// public override GeometryBounds GetGeometryBounds() { return new GeometryBounds(Left, Right, Bottom, Bottom + Math.Min(Right - Left, 20)); } #endregion #region Functions #region create functions /// /// Adjust the Geometry Bottom, Left and Right properties to the currently contained surfaces /// public void Rebox() { double xMin = double.MaxValue; double xMax = double.MinValue; double zMin = double.MaxValue; double zMax = double.MinValue; foreach (var point in pointDataList) { xMin = Math.Min(point.X, xMin); xMax = Math.Max(point.X, xMax); zMin = Math.Min(point.Z, zMin); zMax = Math.Max(point.Z, zMax); } bottom = zMin; left = xMin; right = xMax; } #endregion #region remove functions /// /// Clears this instance. /// public void Clear() { pointDataList.Clear(); curveDataList.Clear(); surfaceDataList.Clear(); // newlyEffectedPoints.Clear(); // newlyEffectedCurves.Clear(); } /// /// deletes all the Loop from IGeometryLoop. /// private void DeleteAllLoops() { Loops.Clear(); } #endregion #region other functions #region calculation function /// /// CheckIfIntersectStricktly /// Determines if two lines intersect each other stricktly (so no extrapolated points). /// /// Line 1 GeometryPoint 1 /// Line 1 GeometryPoint 2 /// Line 2 GeometryPoint 1 /// Line 2 GeometryPoint 2 /// Intersection coordinates /// True if lines intersect each other private static bool CheckIfIntersectStricktly(Point2D beginPoint1, Point2D endPoint1, Point2D beginPoint2, Point2D endPoint2, ref Point2D intersect) { Point2D ip; var res = Routines2D.DetermineIf2DLinesIntersectStrickly(beginPoint1.X, beginPoint1.Z, endPoint1.X, endPoint1.Z, beginPoint2.X, beginPoint2.Z, endPoint2.X, endPoint2.Z, out ip); if (ip != null) { intersect = ip; } return res == LineIntersection.Intersects; } /// /// CheckIfIntersectStricktly /// Determines if two lines intersect each other stricktly (so no extrapolated points). /// /// Line 1 GeometryPoint 1 /// Line 1 GeometryPoint 2 /// Line 2 GeometryPoint 1 /// Line 2 GeometryPoint 2 /// Intersection coordinates /// True if lines intersect each other public static bool CheckIfIntersect(double[] aL1P1, double[] aL1P2, double[] aL2P1, double[] aL2P2, ref double[] aIntersect) { var p1 = new Point2D(aL1P1[0], aL1P1[1]); var p2 = new Point2D(aL1P2[0], aL1P2[1]); var p3 = new Point2D(aL2P1[0], aL2P1[1]); var p4 = new Point2D(aL2P2[0], aL2P2[1]); var ip = new Point2D(); var res = CheckIfIntersectStricktly(p1, p2, p3, p4, ref ip); if (res) { aIntersect[0] = ip.X; aIntersect[1] = ip.Z; } return res; } /// /// Gets the height of the surface(s) intersected at the given x. /// /// The x. /// public double GetSurfaceHeight(double x) { double surfaceHeight = -Double.MaxValue; double[] intersectionPoints = IntersectLayers(x, -9999); for (int i = 0; i < intersectionPoints.Length; i++) { if (intersectionPoints[i] > surfaceHeight) { surfaceHeight = intersectionPoints[i]; } } return surfaceHeight; } /// /// All the Intersection of layers in respect with a given vertical are detemined here. /// /// Startingpoint of the Vertical (X) /// Startingpoint of the Vertical (Y) /// List of Z intersection coordinates private double[] IntersectLayers(double aXCoord, double aZCoord) { if (Surfaces == null) { throw new Exception("Empty Surfaces in IntersectLayers"); } var beginPoint2 = new Point2D() { X = aXCoord, Z = aZCoord }; var endPoint2 = new Point2D() { X = aXCoord, Z = 99999 }; var referencePoint = new Point2D(); var intersections = new List(); for (int surfaceIndexLocal = 0; surfaceIndexLocal < Surfaces.Count; surfaceIndexLocal++) { var outerLoopCurveList = Surfaces[surfaceIndexLocal].OuterLoop.CurveList; for (int curveIndexLocal = 0; curveIndexLocal < outerLoopCurveList.Count; curveIndexLocal++) { //Check for each curve if it intersects with x coordinate Point2D beginPoint1 = outerLoopCurveList[curveIndexLocal].GetHeadPoint(CurveDirection.Forward); Point2D endPoint1 = outerLoopCurveList[curveIndexLocal].GetEndPoint(CurveDirection.Forward); if (Math.Max(beginPoint1.X, endPoint1.X) >= aXCoord && Math.Min(beginPoint1.X, endPoint1.X) <= aXCoord) { if (CheckIfIntersectStricktly(beginPoint1, endPoint1, beginPoint2, endPoint2, ref referencePoint)) { if (referencePoint.Z > aZCoord && intersections.Contains(referencePoint.Z) == false) { intersections.Add(referencePoint.Z); } } } } } return intersections.ToArray(); } /// /// Returns a list of boundary curves. These are curves which are used in only one surface so they have to be on a boundary (inner or outer) /// /// private List GetBoundaryCurves() { var curves = new List(); var loops = new List(); foreach (var surface in Surfaces) { loops.Add(surface.OuterLoop); // Todo Ask Rob/Tom: when a real "doughnut" type surface (so hole in the center) is permitted, adding the innerloops here will // result in a wrong list of curves (because it will include the inner loop curves defining the hole) for its actual purpose: // the determination of the surfaceline. When there is always a surface defined within the "dougnut" (so no real hole), // this code will work and the innerloop must even be added to prevent finding internal boundaries. So this depends on the specs! loops.AddRange(surface.InnerLoops); } foreach (var loop in loops) { foreach (var curve in loop.CurveList) { if (curves.Contains(curve)) { // Second appearance, remove curves.Remove(curve); } else { curves.Add(curve); } } } return curves; } #endregion #endregion #endregion /// /// Ordered list of all geometry points at the surface /// public virtual GeometryPointString SurfaceLine { get { if (surfaceLine.CalcPoints.Count == 0 && pointDataList.Count > 0) { UpdateSurfaceLine(); } return surfaceLine; } } /// /// Checks geometry for loose curves and AutoRegeneration /// /// [Validate] public ValidationResult[] ValidateGeometry() { var validationList = new List(); { foreach (var point in Points) { foreach (var point1 in Points) { if (point != point1) { bool isValidated = false; foreach (var validatedItem in validationList) { if (validatedItem.Subject == point) { isValidated = true; } } if (!isValidated) { if (Math.Abs(point.X - point1.X) < GeometryConstants.Accuracy && Math.Abs(point.Z - point1.Z) < GeometryConstants.Accuracy) { validationList.Add(new ValidationResult(ValidationResultType.Error, point + " and " + point1 + " values are same.", point1)); } } } } } } if (Surfaces.Count < 1) { validationList.Add(new ValidationResult(ValidationResultType.Error, "No soil surface available.", this)); } return validationList.ToArray(); } /// /// Updates the line at the top of the geometry /// private void UpdateSurfaceLine() { if (updatingSurfaceLine) { return; } updatingSurfaceLine = true; var bCurves = GetBoundaryCurves(); if (bCurves.Count == 0) { surfaceLine.CalcPoints.Clear(); } var curvesCopy = GetCurvesCopy(bCurves); var curves = GetTopCurves(curvesCopy); CreateSurfaceLinePointString(curves); updatingSurfaceLine = false; } /// /// Removes the boundary curves from the given list of curves. /// The boundaries themselves are determined from the given geometry /// /// The curves. /// The geometry (as string). private static void RemoveBoundaryCurves(List curves, GeometryPointString geometry) { var minX = geometry.GetMinX(); var maxX = geometry.GetMaxX(); var minZ = geometry.GetMinZ(); foreach (var curve in curves.ToArray()) { if (IsBoundaryCurve(curve, minX, maxX, minZ)) { curves.Remove(curve); } } } /// /// get all geometrypoints from all geometrycurves /// /// /// private static GeometryPointString GetAllPointsFromCurveList(List curveList) { var result = new GeometryPointString(); foreach (var curve in curveList) { result.CalcPoints.Add(curve.EndPoint); result.CalcPoints.Add(curve.HeadPoint); } return result; } /// /// Gets next connected top curve in list of curves /// /// /// /// /// private GeometryCurve GetNextTopCurve(GeometryCurve curve, List boundaryCurves, List excludedCurves) { // if current curve ends on right limit then that must have been the last one so stop the search if (Math.Abs(curve.HeadPoint.X - Right) < GeometryConstants.Accuracy || Math.Abs(curve.EndPoint.X - Right) < GeometryConstants.Accuracy) { return null; } foreach (var geometryCurve in boundaryCurves) { if (geometryCurve != curve && !excludedCurves.Contains(geometryCurve)) { if (AreConnected(curve, geometryCurve)) { return geometryCurve; } } } return null; } /// /// Returns a that represents this instance. /// /// /// A that represents this instance. /// public override string ToString() { return LocalizationManager.GetTranslatedText(this, "GeometryData"); } /// /// Synchronizes the loops. /// public void SynchronizeLoops() { DeleteAllLoops(); foreach (var surface in Surfaces) { // #Bka: as real donuts (or holes in geom) are not allowed, there can be no innerloop that // is NOT an outerloop for another surface. So no need to sync innerloops. if (surface.OuterLoop != null && surface.OuterLoop.IsLoop()) { loopDataList.Add(surface.OuterLoop); } } } /// /// create a copy of the curves /// /// /// private List GetCurvesCopy(List bCurves) { var outerloopCurvesCopy = new List(bCurves); return outerloopCurvesCopy; } /// /// Create a surface line from points in curves /// Precondition is that the curves start at the left boundary and are connected left to right /// (not neccesarily neat head-end) /// /// /// private void CreateSurfaceLinePointString(List curves) { surfaceLine.CalcPoints.Clear(); if (curves.Count == 0) { return; } var reversed = false; // The headpoint of the first curve must be on the left boundary otherwise the // surface line will be in the wrong order. So make sure. if (!(Math.Abs(curves[0].HeadPoint.X - Left) < GeometryConstants.Accuracy)) { curves[0].Reverse(); reversed = true; } foreach (var curve in curves) { if (!surfaceLine.CalcPoints.Contains(curve.HeadPoint)) { surfaceLine.CalcPoints.Add(curve.HeadPoint); } if (!surfaceLine.CalcPoints.Contains(curve.EndPoint)) { surfaceLine.CalcPoints.Add(curve.EndPoint); } } if (reversed) { curves[0].Reverse(); } } /// /// get curves of the top side of the outerloop, vertical curves are omitted /// /// /// private List GetTopCurves(List curves) { GeometryCurve topCurve; // Remove all curves on the geometry boundary if (GetLeftPoints().Count > 0 && GetRightPoints().Count > 0) { foreach (var curve in curves.ToArray()) { if (IsBoundaryCurve(curve, Left, Right, Bottom)) { curves.Remove(curve); } } // Make sure you start with topcurve = curve at the left top position topCurve = curves.Where(g => Math.Abs(g.HeadPoint.X - Left) < GeometryConstants.Accuracy || Math.Abs(g.EndPoint.X - Left) < GeometryConstants.Accuracy) .OrderByDescending(c => c.HeadPoint.Z) .FirstOrDefault(); } else { GeometryPointString gString = GetAllPointsFromCurveList(curves); RemoveBoundaryCurves(curves, gString); var minX = gString.GetMinX(); // Make sure you start with topcurve = curve at the left top position topCurve = curves.Where(g => Math.Abs(g.HeadPoint.X - minX) < GeometryConstants.Accuracy || Math.Abs(g.EndPoint.X - minX) < GeometryConstants.Accuracy).OrderByDescending(c => c.HeadPoint.Z).FirstOrDefault(); } var topCurvesLocal = new List(); while (topCurve != null) { topCurvesLocal.Add(topCurve); topCurve = GetNextTopCurve(topCurve, curves, topCurvesLocal); } return topCurvesLocal; } /// /// Indicates whether a curve is on the boundary of the geometry /// /// /// /// /// /// private static bool IsBoundaryCurve(GeometryCurve curve, double minX, double maxX, double minZ) { if (Math.Abs(curve.HeadPoint.X - minX) < GeometryConstants.Accuracy && Math.Abs(curve.EndPoint.X - minX) < GeometryConstants.Accuracy) { return true; } if (Math.Abs(curve.HeadPoint.X - maxX) < GeometryConstants.Accuracy && Math.Abs(curve.EndPoint.X - maxX) < GeometryConstants.Accuracy) { return true; } if (Math.Abs(curve.HeadPoint.Z - minZ) < GeometryConstants.Accuracy && Math.Abs(curve.EndPoint.Z - minZ) < GeometryConstants.Accuracy) { return true; } return false; } private bool AreConnected(GeometryCurve curve1, GeometryCurve curve2) { return (curve1.HeadPoint == curve2.HeadPoint || curve1.HeadPoint == curve2.EndPoint || curve1.EndPoint == curve2.HeadPoint || curve1.EndPoint == curve2.EndPoint); } } }