using System; using GeoAPI.Geometries; using GisSharpBlog.NetTopologySuite.Geometries; namespace GisSharpBlog.NetTopologySuite.Algorithm { /// /// Specifies and implements various fundamental Computational Geometric algorithms. /// The algorithms supplied in this class are robust for double-precision floating point. /// public static class CGAlgorithms { /// /// A value that indicates an orientation of clockwise, or a right turn. /// public const int Clockwise = -1; /// /// A value that indicates an orientation of clockwise, or a right turn. /// public const int Right = Clockwise; /// /// A value that indicates an orientation of counterclockwise, or a left turn. /// public const int CounterClockwise = 1; /// /// A value that indicates an orientation of counterclockwise, or a left turn. /// public const int Left = CounterClockwise; /// /// A value that indicates an orientation of collinear, or no turn (straight). /// public const int Collinear = 0; /// /// A value that indicates an orientation of collinear, or no turn (straight). /// public const int Straight = Collinear; /// /// Returns the index of the direction of the point q /// relative to a vector specified by p1-p2. /// /// The origin point of the vector. /// The final point of the vector. /// The point to compute the direction to. /// /// 1 if q is counter-clockwise (left) from p1-p2, /// -1 if q is clockwise (right) from p1-p2, /// 0 if q is collinear with p1-p2. /// public static int OrientationIndex(ICoordinate p1, ICoordinate p2, ICoordinate q) { // travelling along p1->p2, turn counter clockwise to get to q return 1, // travelling along p1->p2, turn clockwise to get to q return -1, // p1, p2 and q are colinear return 0. double dx1 = p2.X - p1.X; double dy1 = p2.Y - p1.Y; double dx2 = q.X - p2.X; double dy2 = q.Y - p2.Y; return RobustDeterminant.SignOfDet2x2(dx1, dy1, dx2, dy2); } /// /// Test whether a point lies inside a ring. /// The ring may be oriented in either direction. /// If the point lies on the ring boundary the result of this method is unspecified. /// This algorithm does not attempt to first check the point against the envelope /// of the ring. /// /// Point to check for ring inclusion. /// Assumed to have first point identical to last point. /// true if p is inside ring. public static bool IsPointInRing(ICoordinate p, ICoordinate[] ring) { if (p == null) { throw new ArgumentNullException("p", "coordinate 'p' is null"); } if (ring == null) { throw new ArgumentNullException("ring", "ring 'ring' is null"); } int crossings = 0; /* * For each segment l = (i-1, i), see if it crosses ray from test point in positive x direction. */ for (int i = 1; i < ring.Length; i++) { int i1 = i - 1; ICoordinate p1 = ring[i]; ICoordinate p2 = ring[i1]; if (!IsValidCoordinate(p1) || !IsValidCoordinate(p2)) { continue; } if (((p1.Y > p.Y) && (p2.Y <= p.Y)) || ((p2.Y > p.Y) && (p1.Y <= p.Y))) { double x1 = p1.X - p.X; double y1 = p1.Y - p.Y; double x2 = p2.X - p.X; double y2 = p2.Y - p.Y; /* * segment straddles x axis, so compute intersection. */ double xInt = RobustDeterminant.SignOfDet2x2(x1, y1, x2, y2)/(y2 - y1); /* * crosses ray if strictly positive intersection. */ if (0.0 < xInt) { crossings++; } } } /* * p is inside if number of crossings is odd. */ if ((crossings%2) == 1) { return true; } else { return false; } } /// /// Test whether a point lies on the line segments defined by a /// list of coordinates. /// /// /// /// /// true true if /// the point is a vertex of the line or lies in the interior of a line /// segment in the linestring. /// public static bool IsOnLine(ICoordinate p, ICoordinate[] pt) { LineIntersector lineIntersector = new RobustLineIntersector(); for (int i = 1; i < pt.Length; i++) { ICoordinate p0 = pt[i - 1]; ICoordinate p1 = pt[i]; if (!IsValidCoordinate(p0) || !IsValidCoordinate(p1)) { continue; } lineIntersector.ComputeIntersection((Coordinate) p, (Coordinate) p0, (Coordinate) p1); if (lineIntersector.HasIntersection) { return true; } } return false; } /// /// Computes whether a ring defined by an array of s is oriented counter-clockwise. /// The list of points is assumed to have the first and last points equal. /// This will handle coordinate lists which contain repeated points. /// This algorithm is only guaranteed to work with valid rings. /// If the ring is invalid (e.g. self-crosses or touches), /// the computed result may not be correct. /// > /// /// public static bool IsCCW(ICoordinate[] ring) { // # of points without closing endpoint int nPts = ring.Length - 1; // find highest point ICoordinate hiPt = ring[0]; int hiIndex = 0; for (int i = 1; i <= nPts; i++) { ICoordinate p = ring[i]; if (p.Y > hiPt.Y) { hiPt = p; hiIndex = i; } } if (!IsValidCoordinate(hiPt)) { return false; } // find distinct point before highest point int iPrev = hiIndex; do { iPrev = iPrev - 1; if (iPrev < 0) { iPrev = nPts; } } while (ring[iPrev].Equals2D(hiPt) && iPrev != hiIndex); // find distinct point after highest point int iNext = hiIndex; do iNext = (iNext + 1)%nPts; while (ring[iNext].Equals2D(hiPt) && iNext != hiIndex); ICoordinate prev = ring[iPrev]; ICoordinate next = ring[iNext]; /* * This check catches cases where the ring contains an A-B-A configuration of points. * This can happen if the ring does not contain 3 distinct points * (including the case where the input array has fewer than 4 elements), * or it contains coincident line segments. */ if (prev.Equals2D(hiPt) || next.Equals2D(hiPt) || prev.Equals2D(next)) { return false; } int disc = ComputeOrientation(prev, hiPt, next); /* * If disc is exactly 0, lines are collinear. There are two possible cases: * (1) the lines lie along the x axis in opposite directions * (2) the lines lie on top of one another * * (1) is handled by checking if next is left of prev ==> CCW * (2) will never happen if the ring is valid, so don't check for it * (Might want to assert this) */ bool isCCW = false; if (disc == 0) { // poly is CCW if prev x is right of next x isCCW = (prev.X > next.X); } else { // if area is positive, points are ordered CCW isCCW = (disc > 0); } return isCCW; } public static bool IsValidCoordinate(ICoordinate coordinate) { return !(double.IsInfinity(coordinate.X) || double.IsInfinity(coordinate.Y) || double.IsNaN(coordinate.X) || double.IsNaN(coordinate.Y)); } /// /// Computes the orientation of a point q to the directed line segment p1-p2. /// The orientation of a point relative to a directed line segment indicates /// which way you turn to get to q after travelling from p1 to p2. /// /// /// /// /// /// 1 if q is counter-clockwise from p1-p2, /// -1 if q is clockwise from p1-p2, /// 0 if q is collinear with p1-p2- /// public static int ComputeOrientation(ICoordinate p1, ICoordinate p2, ICoordinate q) { return OrientationIndex(p1, p2, q); } /// /// Computes the distance from a point p to a line segment AB. /// Note: NON-ROBUST! /// /// The point to compute the distance for. /// One point of the line. /// Another point of the line (must be different to A). /// The distance from p to line segment AB. public static double DistancePointLine(ICoordinate p, ICoordinate A, ICoordinate B) { // if start == end, then use pt distance if (A.Equals(B)) { return p.Distance(A); } // otherwise use comp.graphics.algorithms Frequently Asked Questions method /*(1) AC dot AB r = --------- ||AB||^2 r has the following meaning: r=0 Point = A r=1 Point = B r<0 Point is on the backward extension of AB r>1 Point is on the forward extension of AB 0= 1.0) { return p.Distance(B); } /*(2) (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay) s = ----------------------------- Curve^2 Then the distance from C to Point = |s|*Curve. */ double s = ((A.Y - p.Y)*(B.X - A.X) - (A.X - p.X)*(B.Y - A.Y)) / ((B.X - A.X)*(B.X - A.X) + (B.Y - A.Y)*(B.Y - A.Y)); return Math.Abs(s)*Math.Sqrt(((B.X - A.X)*(B.X - A.X) + (B.Y - A.Y)*(B.Y - A.Y))); } /// /// Computes the perpendicular distance from a point p /// to the (infinite) line containing the points AB /// /// The point to compute the distance for. /// One point of the line. /// Another point of the line (must be different to A). /// The perpendicular distance from p to line AB. public static double DistancePointLinePerpendicular(ICoordinate p, ICoordinate A, ICoordinate B) { // use comp.graphics.algorithms Frequently Asked Questions method /*(2) (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay) s = ----------------------------- Curve^2 Then the distance from C to Point = |s|*Curve. */ double s = ((A.Y - p.Y)*(B.X - A.X) - (A.X - p.X)*(B.Y - A.Y)) / ((B.X - A.X)*(B.X - A.X) + (B.Y - A.Y)*(B.Y - A.Y)); return Math.Abs(s)*Math.Sqrt(((B.X - A.X)*(B.X - A.X) + (B.Y - A.Y)*(B.Y - A.Y))); } /// /// Computes the distance from a line segment AB to a line segment CD. /// Note: NON-ROBUST! /// /// A point of one line. /// The second point of the line (must be different to A). /// One point of the line. /// Another point of the line (must be different to A). /// The distance from line segment AB to line segment CD. public static double DistanceLineLine(ICoordinate A, ICoordinate B, ICoordinate C, ICoordinate D) { // check for zero-length segments if (A.Equals(B)) { return DistancePointLine(A, C, D); } if (C.Equals(D)) { return DistancePointLine(D, A, B); } // AB and CD are line segments /* from comp.graphics.algo Solving the above for r and s yields (Ay-Cy)(Dx-Cx)-(Ax-Cx)(Dy-Cy) r = ----------------------------- (eqn 1) (Bx-Ax)(Dy-Cy)-(By-Ay)(Dx-Cx) (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay) s = ----------------------------- (eqn 2) (Bx-Ax)(Dy-Cy)-(By-Ay)(Dx-Cx) Let Point be the position vector of the intersection point, then Point=A+r(B-A) or Px=Ax+r(Bx-Ax) Py=Ay+r(By-Ay) By examining the values of r & s, you can also determine some other limiting conditions: If 0<=r<=1 & 0<=s<=1, intersection exists r<0 or r>1 or s<0 or s>1 line segments do not intersect If the denominator in eqn 1 is zero, AB & CD are parallel If the numerator in eqn 1 is also zero, AB & CD are collinear. */ double r_top = (A.Y - C.Y)*(D.X - C.X) - (A.X - C.X)*(D.Y - C.Y); double r_bot = (B.X - A.X)*(D.Y - C.Y) - (B.Y - A.Y)*(D.X - C.X); double s_top = (A.Y - C.Y)*(B.X - A.X) - (A.X - C.X)*(B.Y - A.Y); double s_bot = (B.X - A.X)*(D.Y - C.Y) - (B.Y - A.Y)*(D.X - C.X); if ((r_bot == 0) || (s_bot == 0)) { return Math.Min(DistancePointLine(A, C, D), Math.Min(DistancePointLine(B, C, D), Math.Min(DistancePointLine(C, A, B), DistancePointLine(D, A, B)))); } double s = s_top/s_bot; double r = r_top/r_bot; if ((r < 0) || (r > 1) || (s < 0) || (s > 1)) { //no intersection return Math.Min(DistancePointLine(A, C, D), Math.Min(DistancePointLine(B, C, D), Math.Min(DistancePointLine(C, A, B), DistancePointLine(D, A, B)))); } return 0.0; //intersection exists } /// /// Returns the signed area for a ring. The area is positive ifthe ring is oriented CW. /// /// /// public static double SignedArea(ICoordinate[] ring) { if (ring.Length < 3) { return 0.0; } double sum = 0.0; for (int i = 0; i < ring.Length - 1; i++) { double bx = ring[i].X; double by = ring[i].Y; double cx = ring[i + 1].X; double cy = ring[i + 1].Y; sum += (bx + cx)*(cy - by); } return -sum/2.0; } /// /// Computes the length of a linestring specified by a sequence of points. /// /// The points specifying the linestring. /// The length of the linestring. public static double Length(ICoordinateSequence pts) { if (pts.Count < 1) { return 0.0; } double sum = 0.0; for (int i = 1; i < pts.Count; i++) { sum += pts.GetCoordinate(i).Distance(pts.GetCoordinate(i - 1)); } return sum; } } }