using System; using System.Text; using Core.Gis.GeoApi.Geometries; using Core.GIS.NetTopologySuite.Algorithm; namespace Core.GIS.NetTopologySuite.Geometries { /// /// Represents a line segment defined by two Coordinates. /// Provides methods to compute various geometric properties /// and relationships of line segments. /// This class is designed to be easily mutable (to the extent of /// having its contained points public). /// This supports a common pattern of reusing a single LineSegment /// object as a way of computing segment properties on the /// segments defined by arrays or lists of Coordinates. /// [Serializable] public class LineSegment : IComparable { private ICoordinate p0 = null, p1 = null; /// /// /// /// /// public LineSegment(ICoordinate p0, ICoordinate p1) { this.p0 = p0; this.p1 = p1; } /// /// /// /// public LineSegment(LineSegment ls) : this(ls.p0, ls.p1) {} /// /// /// public LineSegment() : this(new Coordinate(), new Coordinate()) {} /// /// /// public ICoordinate P1 { get { return p1; } set { p1 = value; } } /// /// /// public ICoordinate P0 { get { return p0; } set { p0 = value; } } /// /// Computes the length of the line segment. /// /// The length of the line segment. public double Length { get { return P0.Distance(P1); } } /// /// Tests whether the segment is horizontal. /// /// true if the segment is horizontal. public bool IsHorizontal { get { return P0.Y == P1.Y; } } /// /// Tests whether the segment is vertical. /// /// true if the segment is vertical. public bool IsVertical { get { return P0.X == P1.X; } } /// /// The angle this segment makes with the x-axis (in radians). /// public double Angle { get { return Math.Atan2(P1.Y - P0.Y, P1.X - P0.X); } } /// /// /// /// /// public ICoordinate GetCoordinate(int i) { return i == 0 ? P0 : P1; } /// /// /// /// public void SetCoordinates(LineSegment ls) { SetCoordinates(ls.P0, ls.P1); } /// /// /// /// /// public void SetCoordinates(ICoordinate p0, ICoordinate p1) { P0.X = p0.X; P0.Y = p0.Y; P1.X = p1.X; P1.Y = p1.Y; } /// /// Computes the that lies a given /// fraction along the line defined by this segment. /// /// /// A fraction of 0.0 returns the start point of the segment; /// A fraction of 1.0 returns the end point of the segment. /// If the fraction is < 0.0 or > 1.0 the point returned /// will lie before the start or beyond the end of the segment. /// /// the fraction of the segment length along the line /// the point at that distance public Coordinate PointAlong(double segmentLengthFraction) { var coord = new Coordinate(); coord.X = p0.X + segmentLengthFraction*(p1.X - p0.X); coord.Y = p0.Y + segmentLengthFraction*(p1.Y - p0.Y); return coord; } /// /// Determines the orientation of a LineSegment relative to this segment. /// The concept of orientation is specified as follows: /// Given two line segments A and L, /// A is to the left of a segment L if A lies wholly in the /// closed half-plane lying to the left of L /// A is to the right of a segment L if A lies wholly in the /// closed half-plane lying to the right of L /// otherwise, A has indeterminate orientation relative to L. This /// happens if A is collinear with L or if A crosses the line determined by L. /// /// The LineSegment to compare. /// /// 1 if seg is to the left of this segment, /// -1 if seg is to the right of this segment, /// 0 if seg has indeterminate orientation relative to this segment. /// public int OrientationIndex(LineSegment seg) { var orient0 = CGAlgorithms.OrientationIndex(P0, P1, seg.P0); var orient1 = CGAlgorithms.OrientationIndex(P0, P1, seg.P1); // this handles the case where the points are Curve or collinear if (orient0 >= 0 && orient1 >= 0) { return Math.Max(orient0, orient1); } // this handles the case where the points are R or collinear if (orient0 <= 0 && orient1 <= 0) { return Math.Max(orient0, orient1); } // points lie on opposite sides ==> indeterminate orientation return 0; } /// /// Reverses the direction of the line segment. /// public void Reverse() { var temp = P0; P0 = P1; P1 = temp; } /// /// Puts the line segment into a normalized form. /// This is useful for using line segments in maps and indexes when /// topological equality rather than exact equality is desired. /// public void Normalize() { if (P1.CompareTo(P0) < 0) { Reverse(); } } /// /// Computes the distance between this line segment and another one. /// /// /// public double Distance(LineSegment ls) { return CGAlgorithms.DistanceLineLine(P0, P1, ls.P0, ls.P1); } /// /// Computes the distance between this line segment and a point. /// public double Distance(ICoordinate p) { return CGAlgorithms.DistancePointLine(p, P0, P1); } /// /// Computes the perpendicular distance between the (infinite) line defined /// by this line segment and a point. /// /// /// public double DistancePerpendicular(ICoordinate p) { return CGAlgorithms.DistancePointLinePerpendicular(p, P0, P1); } /// /// Compute the projection factor for the projection of the point p /// onto this LineSegment. The projection factor is the constant k /// by which the vector for this segment must be multiplied to /// equal the vector for the projection of p. /// /// /// public double ProjectionFactor(ICoordinate p) { if (p.Equals(P0)) { return 0.0; } if (p.Equals(P1)) { return 1.0; } // Otherwise, use comp.graphics.algorithms Frequently Asked Questions method /* 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 /// Compute the projection of a point onto the line determined /// by this line segment. /// Note that the projected point may lie outside the line segment. /// If this is the case, the projection factor will lie outside the range [0.0, 1.0]. /// /// /// public ICoordinate Project(ICoordinate p) { if (p.Equals(P0) || p.Equals(P1)) { return new Coordinate(p); } var r = ProjectionFactor(p); ICoordinate coord = new Coordinate { X = P0.X + r*(P1.X - P0.X), Y = P0.Y + r*(P1.Y - P0.Y) }; return coord; } /// /// Project a line segment onto this line segment and return the resulting /// line segment. The returned line segment will be a subset of /// the target line line segment. This subset may be null, if /// the segments are oriented in such a way that there is no projection. /// Note that the returned line may have zero length (i.e. the same endpoints). /// This can happen for instance if the lines are perpendicular to one another. /// /// The line segment to project. /// The projected line segment, or null if there is no overlap. public LineSegment Project(LineSegment seg) { var pf0 = ProjectionFactor(seg.P0); var pf1 = ProjectionFactor(seg.P1); // check if segment projects at all if (pf0 >= 1.0 && pf1 >= 1.0) { return null; } if (pf0 <= 0.0 && pf1 <= 0.0) { return null; } var newp0 = Project(seg.P0); if (pf0 < 0.0) { newp0 = P0; } if (pf0 > 1.0) { newp0 = P1; } var newp1 = Project(seg.P1); if (pf1 < 0.0) { newp1 = P0; } if (pf1 > 1.0) { newp1 = P1; } return new LineSegment(newp0, newp1); } /// /// Computes the closest point on this line segment to another point. /// /// The point to find the closest point to. /// /// A Coordinate which is the closest point on the line segment to the point p. /// public ICoordinate ClosestPoint(ICoordinate p) { var factor = ProjectionFactor(p); if (factor > 0 && factor < 1) { return Project(p); } var dist0 = P0.Distance(p); var dist1 = P1.Distance(p); return dist0 < dist1 ? P0 : P1; } /// /// Computes the closest points on a line segment. /// /// /// /// A pair of Coordinates which are the closest points on the line segments. /// public ICoordinate[] ClosestPoints(LineSegment line) { // test for intersection var intPt = Intersection(line); if (intPt != null) { return new ICoordinate[] { intPt, intPt }; } /* * if no intersection closest pair contains at least one endpoint. * Test each endpoint in turn. */ var closestPt = new ICoordinate[2]; var minDistance = Double.MaxValue; var close00 = ClosestPoint(line.P0); minDistance = close00.Distance(line.P0); closestPt[0] = close00; closestPt[1] = line.P0; var close01 = ClosestPoint(line.P1); double dist = close01.Distance(line.P1); if (dist < minDistance) { minDistance = dist; closestPt[0] = close01; closestPt[1] = line.P1; } var close10 = line.ClosestPoint(P0); dist = close10.Distance(P0); if (dist < minDistance) { minDistance = dist; closestPt[0] = P0; closestPt[1] = close10; } var close11 = line.ClosestPoint(P1); dist = close11.Distance(P1); if (dist < minDistance) { minDistance = dist; closestPt[0] = P1; closestPt[1] = close11; } return closestPt; } /// /// Computes an intersection point between two segments, if there is one. /// There may be 0, 1 or many intersection points between two segments. /// If there are 0, null is returned. If there is 1 or more, a single one /// is returned (chosen at the discretion of the algorithm). If /// more information is required about the details of the intersection, /// the {RobustLineIntersector} class should be used. /// /// /// An intersection point, or null if there is none. public ICoordinate Intersection(LineSegment line) { LineIntersector li = new RobustLineIntersector(); li.ComputeIntersection(P0, P1, line.P0, line.P1); if (li.HasIntersection) { return li.GetIntersection(0); } return null; } /// /// /// /// /// /// public static bool operator ==(LineSegment obj1, LineSegment obj2) { return Equals(obj1, obj2); } /// /// /// /// /// /// public static bool operator !=(LineSegment obj1, LineSegment obj2) { return !(obj1 == obj2); } /// /// Returns true if other is /// topologically equal to this LineSegment (e.g. irrespective /// of orientation). /// /// /// A LineSegment with which to do the comparison. /// /// /// true if other is a LineSegment /// with the same values for the x and y ordinates. /// public bool EqualsTopologically(LineSegment other) { return P0.Equals(other.P0) && P1.Equals(other.P1) || P0.Equals(other.P1) && P1.Equals(other.P0); } /// /// Returns true if o has the same values for its points. /// /// A LineSegment with which to do the comparison. /// /// true if o is a LineSegment /// with the same values for the x and y ordinates. /// public override bool Equals(object o) { if (o == null) { return false; } if (!(o is LineSegment)) { return false; } var other = (LineSegment) o; return p0.Equals(other.p0) && p1.Equals(other.p1); } /// /// /// /// public override string ToString() { var sb = new StringBuilder("LINESTRING( "); sb.Append(P0.X).Append(" "); sb.Append(P0.Y).Append(", "); sb.Append(P1.X).Append(" "); sb.Append(P1.Y).Append(")"); return sb.ToString(); } /// /// Return HashCode. /// public override int GetHashCode() { return base.GetHashCode(); } /// /// Compares this object with the specified object for order. /// Uses the standard lexicographic ordering for the points in the LineSegment. /// /// /// The LineSegment with which this LineSegment /// is being compared. /// /// /// A negative integer, zero, or a positive integer as this LineSegment /// is less than, equal to, or greater than the specified LineSegment. /// public int CompareTo(object o) { var other = (LineSegment) o; var comp0 = P0.CompareTo(other.P0); return comp0 != 0 ? comp0 : P1.CompareTo(other.P1); } } }