using System; using System.Collections.Generic; using System.Linq; using UglyToad.PdfPig.Geometry; namespace UglyToad.PdfPig.DocumentLayoutAnalysis { /// /// Contains helpful tools for distance measures. /// public static class Distances { /// /// The Euclidean distance is the "ordinary" straight-line distance between two points. /// /// The first point. /// The second point. public static double Euclidean(PdfPoint point1, PdfPoint point2) { double dx = point1.X - point2.X; double dy = point1.Y - point2.Y; return Math.Sqrt(dx * dx + dy * dy); } /// /// The weighted Euclidean distance. /// /// The first point. /// The second point. /// The weight of the X coordinates. Default is 1. /// The weight of the Y coordinates. Default is 1. public static double WeightedEuclidean(PdfPoint point1, PdfPoint point2, double wX = 1.0, double wY = 1.0) { double dx = point1.X - point2.X; double dy = point1.Y - point2.Y; return Math.Sqrt(wX * dx * dx + wY * dy * dy); } /// /// The Manhattan distance between two points is the sum of the absolute differences of their Cartesian coordinates. /// Also known as rectilinear distance, L1 distance, L1 norm, snake distance, city block distance, taxicab metric. /// /// The first point. /// The second point. public static double Manhattan(PdfPoint point1, PdfPoint point2) { return Math.Abs(point1.X - point2.X) + Math.Abs(point1.Y - point2.Y); } /// /// The angle in degrees between the horizontal axis and the line between two points. /// /// The first point. /// The second point. /// public static double Angle(PdfPoint point1, PdfPoint point2) { return Math.Atan2(point2.Y - point1.Y, point2.X - point1.X) * 180.0 / Math.PI; } /// /// The absolute distance between the Y coordinates of two points. /// /// The first point. /// The second point. /// public static double Vertical(PdfPoint point1, PdfPoint point2) { return Math.Abs(point2.Y - point1.Y); } /// /// The absolute distance between the X coordinates of two points. /// /// The first point. /// The second point. /// public static double Horizontal(PdfPoint point1, PdfPoint point2) { return Math.Abs(point2.X - point1.X); } /// /// Find the index of the nearest point, excluding itself. /// /// /// The reference point, for which to find the nearest neighbour. /// The list of neighbours candidates. /// /// /// The distance measure to use. /// The distance between reference point, and its nearest neighbour. /// internal static int FindIndexNearest(this T element, IReadOnlyList candidates, Func candidatesPoint, Func pivotPoint, Func distanceMeasure, out double distance) { if (candidates == null || candidates.Count == 0) { throw new ArgumentException("Distances.FindIndexNearest(): The list of neighbours candidates is either null or empty.", "points"); } if (distanceMeasure == null) { throw new ArgumentException("Distances.FindIndexNearest(): The distance measure must not be null.", "distanceMeasure"); } distance = double.MaxValue; int closestPointIndex = -1; var candidatesPoints = candidates.Select(candidatesPoint).ToList(); var pivot = pivotPoint(element); for (var i = 0; i < candidates.Count; i++) { double currentDistance = distanceMeasure(candidatesPoints[i], pivot); if (currentDistance < distance && !candidates[i].Equals(element)) { distance = currentDistance; closestPointIndex = i; } } return closestPointIndex; } /// /// Find the index of the nearest line, excluding itself. /// /// /// The reference line, for which to find the nearest neighbour. /// The list of neighbours candidates. /// /// /// The distance measure between two lines to use. /// The distance between reference line, and its nearest neighbour. internal static int FindIndexNearest(this T element, IReadOnlyList candidates, Func candidatesLine, Func pivotLine, Func distanceMeasure, out double distance) { if (candidates == null || candidates.Count == 0) { throw new ArgumentException("Distances.FindIndexNearest(): The list of neighbours candidates is either null or empty.", "lines"); } if (distanceMeasure == null) { throw new ArgumentException("Distances.FindIndexNearest(): The distance measure must not be null.", "distanceMeasure"); } distance = double.MaxValue; int closestLineIndex = -1; var candidatesLines = candidates.Select(candidatesLine).ToList(); var pivot = pivotLine(element); for (var i = 0; i < candidates.Count; i++) { double currentDistance = distanceMeasure(candidatesLines[i], pivot); if (currentDistance < distance && !candidates[i].Equals(element)) { distance = currentDistance; closestLineIndex = i; } } return closestLineIndex; } } }