/// <inheritdoc cref="ICoordinateSequenceFilter.Filter"/> public void Filter(CoordinateSequence seq, int index) { /* * This logic also handles skipping Point geometries */ if (index < 1) { return; } var p0 = seq.GetCoordinate(index - 1); var p1 = seq.GetCoordinate(index); double delx = (p1.X - p0.X) / _numSubSegs; double dely = (p1.Y - p0.Y) / _numSubSegs; for (int i = 0; i < _numSubSegs; i++) { double x = p0.X + i * delx; double y = p0.Y + i * dely; var pt = new Coordinate(x, y); var minPtDist = new PointPairDistance(); DistanceToPoint.ComputeDistance(_geom, pt, minPtDist); _maxPtDist.SetMaximum(minPtDist); } }
/// <summary> /// Computes the distance from a point to a sequence of line segments. /// </summary> /// <param name="p">A point</param> /// <param name="line">A sequence of contiguous line segments defined by their vertices</param> /// <returns>The minimum distance between the point and the line segments</returns> public static double PointToSegmentString(Coordinate p, CoordinateSequence line) { if (line.Count == 0) { throw new ArgumentException( "Line array must contain at least one vertex"); } var lastStart = line.GetCoordinate(0); var currentEnd = lastStart.Copy(); // this handles the case of length = 1 double minDistance = p.Distance(lastStart); for (int i = 1; i < line.Count; i++) { line.GetCoordinate(i, currentEnd); double dist = PointToSegment(p, lastStart, currentEnd); if (dist < minDistance) { minDistance = dist; } lastStart.CoordinateValue = currentEnd; } return(minDistance); }
public void Filter(CoordinateSequence seq, int i) { if (i == 0) { return; } seq.GetCoordinate(i - 1, p0); seq.GetCoordinate(i, p1); rcc.CountSegment(p0, p1); }
public void Filter(CoordinateSequence seq, int i) { // compare to vertex CheckVertexDistance(seq.GetCoordinate(i)); // compare to segment, if this is one if (i > 0) { CheckSegmentDistance(seq.GetCoordinate(i - 1), seq.GetCoordinate(i)); } }
private static CoordinateSequence InsertTopologyExceptionPoint(Coordinate coord, CoordinateSequence seq, CoordinateSequenceFactory factory) { var res = factory.Create(2 * seq.Count, seq.Ordinates); if (Replace(seq.GetCoordinate(0), coord)) { res.SetOrdinate(0, Ordinate.X, coord.X); res.SetOrdinate(0, Ordinate.Y, coord.Y); res.SetOrdinate(0, Ordinate.Z, coord.Z); } else { res.SetOrdinate(0, Ordinate.X, seq.GetOrdinate(0, Ordinate.X)); res.SetOrdinate(0, Ordinate.Y, seq.GetOrdinate(0, Ordinate.Y)); res.SetOrdinate(0, Ordinate.Z, seq.GetOrdinate(0, Ordinate.Z)); } var last = res.GetCoordinate(0); int off = 0; for (int i = 1; i < seq.Count; i++) { var curr = seq.GetCoordinate(i); bool add = true; if (Replace(curr, coord)) { res.SetOrdinate(i + off, Ordinate.X, coord.X); res.SetOrdinate(i + off, Ordinate.Y, coord.Y); res.SetOrdinate(i + off, Ordinate.Z, coord.Z); add = false; } else if (last.Distance(coord) + coord.Distance(curr) <= 1.000000000000000000001 * last.Distance(curr)) { res.SetOrdinate(i + off, Ordinate.X, coord.X); res.SetOrdinate(i + off, Ordinate.Y, coord.Y); res.SetOrdinate(i + off, Ordinate.Z, coord.Z); off += 1; } if (add) { res.SetOrdinate(i + off, Ordinate.X, seq.GetOrdinate(i, Ordinate.X)); res.SetOrdinate(i + off, Ordinate.Y, seq.GetOrdinate(i, Ordinate.Y)); res.SetOrdinate(i + off, Ordinate.Z, seq.GetOrdinate(i, Ordinate.Z)); } last = curr; } var tmp = factory.Create(seq.Count + off, seq.Ordinates); CoordinateSequences.Copy(res, 0, tmp, 0, tmp.Count); return(tmp); }
/// <summary> /// Function to return a coordinate sequence that is ensured to be closed. /// </summary> /// <param name="sequence">The base sequence</param> /// <param name="factory">The factory to use in case we need to create a new sequence</param> /// <returns>A closed coordinate sequence</returns> private static CoordinateSequence EnsureClosedSequence(CoordinateSequence sequence, CoordinateSequenceFactory factory) { //This sequence won't serve a valid linear ring if (sequence.Count < 3) { return(null); } //The sequence is closed var start = sequence.GetCoordinate(0); int lastIndex = sequence.Count - 1; var end = sequence.GetCoordinate(lastIndex); if (start.Equals2D(end)) { return(sequence); } // The sequence is not closed // 1. Test for a little offset, in that case simply correct x- and y- ordinate values const double eps = 1E-7; if (start.Distance(end) < eps) { sequence.SetX(lastIndex, start.X); sequence.SetY(lastIndex, start.Y); return(sequence); } // 2. Close the sequence by adding a new point, this is heavier var newSequence = factory.Create(sequence.Count + 1, sequence.Dimension, sequence.Measures); int maxDim = sequence.Dimension; for (int i = 0; i < sequence.Count; i++) { for (int dim = 0; dim < maxDim; dim++) { newSequence.SetOrdinate(i, dim, sequence.GetOrdinate(i, dim)); } } for (int dim = 0; dim < maxDim; dim++) { newSequence.SetOrdinate(sequence.Count, dim, sequence.GetOrdinate(0, dim)); } return(newSequence); }
/// <summary> /// /// </summary> /// <param name="geom"></param> protected override void Visit(Geometry geom) { if (!(geom is Polygon)) { return; } var elementEnv = geom.EnvelopeInternal; if (!_rectEnv.Intersects(elementEnv)) { return; } // test each corner of rectangle for inclusion var rectPt = _rectSeq.CreateCoordinate(); for (int i = 0; i < 4; i++) { _rectSeq.GetCoordinate(i, rectPt); if (!elementEnv.Contains(rectPt)) { continue; } // check rect point in poly (rect is known not to touch polygon at this point) if (SimplePointInAreaLocator.ContainsPointInPolygon(rectPt, (Polygon)geom)) { ContainsPoint = true; return; } } }
/// <summary> /// Computes the signed area for a ring. The signed area is positive if the /// <list type="Table"> /// <listheader> /// <term>value</term> /// <description>meaning</description> /// </listheader> /// <item><term>> 0</term> /// <description>The ring is oriented clockwise (CW)</description></item> /// <item><term>< 0</term> /// <description>The ring is oriented counter clockwise (CCW)</description></item> /// <item><term>== 0</term> /// <description>The ring is degenerate or flat</description></item> /// </list> /// ring is oriented CW, negative if the ring is oriented CCW, and zero if the /// ring is degenerate or flat. /// </summary> /// <param name="ring">The coordinates forming the ring</param> /// <returns>The signed area of the ring</returns> public static double OfRingSigned(CoordinateSequence ring) { int n = ring.Count; if (n < 3) { return(0.0); } /** * Based on the Shoelace formula. * http://en.wikipedia.org/wiki/Shoelace_formula */ var p1 = ring.GetCoordinateCopy(0); var p2 = ring.GetCoordinateCopy(1); double x0 = p1.X; p2.X -= x0; double sum = 0.0; for (int i = 1; i < n - 1; i++) { double p0Y = p1.Y; p1.X = p2.X; p1.Y = p2.Y; ring.GetCoordinate(i + 1, p2); p2.X -= x0; sum += p1.X * (p0Y - p2.Y); } return(sum / 2.0); }
/// <summary> /// Converts sequence /// </summary> /// <param name="cs"></param> /// <returns></returns> public static IEnumerable <PPoint> ToPPoints(this CoordinateSequence cs) { for (int i = 0; i < cs.Count; i++) { yield return(cs.GetCoordinate(i).ToPPoint()); } }
/// <summary> /// Computes the length of a <c>LineString</c> specified by a sequence of points. /// </summary> /// <param name="pts">The points specifying the <c>LineString</c></param> /// <returns>The length of the <c>LineString</c></returns> public static double OfLine(CoordinateSequence pts) { // optimized for processing CoordinateSequences int n = pts.Count; if (n <= 1) { return(0.0); } double len = 0.0; var p = pts.GetCoordinateCopy(0); double x0 = p.X; double y0 = p.Y; for (int i = 1; i < n; i++) { pts.GetCoordinate(i, p); double x1 = p.X; double y1 = p.Y; double dx = x1 - x0; double dy = y1 - y0; len += Math.Sqrt(dx * dx + dy * dy); x0 = x1; y0 = y1; } return(len); }
public void Filter(CoordinateSequence seq, int index) { if (index == 0) { return; } var p0 = seq.GetCoordinate(index - 1); var p1 = seq.GetCoordinate(index); var midPt = new Coordinate( (p0.X + p1.X) / 2, (p0.Y + p1.Y) / 2); minPtDist.Initialize(); DistanceToPointFinder.ComputeDistance(geom, midPt, minPtDist); maxPtDist.SetMaximum(minPtDist); }
public void Filter(CoordinateSequence seq, int i) { if (i <= 0) { return; } // extract LineSegment var p0 = seq.GetCoordinate(i - 1); var p1 = seq.GetCoordinate(i); bool isBorder = Intersects(_envelope, p0, p1) && !ContainsProperly(_envelope, p0, p1); if (isBorder) { var seg = new LineSegment(p0, p1); _segments.Add(seg); } }
/// <summary> /// Tests whether a point lies on the line defined by a list of /// coordinates. /// </summary> /// <param name="p">The point to test</param> /// <param name="line">The line coordinates</param> /// <returns> /// <c>true</c> if the point is a vertex of the line or lies in the interior /// of a line segment in the line /// </returns> public static bool IsOnLine(Coordinate p, CoordinateSequence line) { var lineIntersector = new RobustLineIntersector(); var p0 = line.CreateCoordinate(); var p1 = p0.Copy(); int n = line.Count; for (int i = 1; i < n; i++) { line.GetCoordinate(i - 1, p0); line.GetCoordinate(i, p1); lineIntersector.ComputeIntersection(p, p0, p1); if (lineIntersector.HasIntersection) { return(true); } } return(false); }
private void gatherDim4(CoordinateSequence cs, Dictionary <Coordinate, Double> map) { if (cs.Dimension == 4) { for (int i = 0; i < cs.Count; i++) { map.TryAdd(cs.GetCoordinate(i), cs.GetOrdinate(i, 3)); } } }
/// <summary> /// /// </summary> /// <param name="seq0"></param> /// <param name="seq1"></param> /// <returns></returns> public bool HasIntersection(CoordinateSequence seq0, CoordinateSequence seq1) { for (int i = 1; i < seq0.Count && !_hasIntersection; i++) { seq0.GetCoordinate(i - 1, pt00); seq0.GetCoordinate(i, pt01); for (int j = 1; j < seq1.Count && !_hasIntersection; j++) { seq1.GetCoordinate(j - 1, pt10); seq1.GetCoordinate(j, pt11); li.ComputeIntersection(pt00, pt01, pt10, pt11); if (li.HasIntersection) { _hasIntersection = true; } } } return(_hasIntersection); }
/// <summary> /// Determines the <see cref="Geometries.Location"/> of a point in a ring. /// </summary> /// <param name="p">The point to test</param> /// <param name="ring">A coordinate sequence forming a ring</param> /// <returns>The location of the point in the ring</returns> public static Location LocatePointInRing(Coordinate p, CoordinateSequence ring) { var counter = new RayCrossingCounter(p); var p1 = ring.CreateCoordinate(); var p2 = ring.CreateCoordinate(); int count = ring.Count; for (int i = 1; i < count; i++) { ring.GetCoordinate(i, p1); ring.GetCoordinate(i - 1, p2); counter.CountSegment(p1, p2); if (counter.IsOnSegment) { return(counter.Location); } } return(counter.Location); }
/** * Computes an average normal vector from a list of polygon coordinates. * Uses Newell's method, which is based * on the fact that the vector with components * equal to the areas of the projection of the polygon onto * the Cartesian axis planes is normal. * * @param seq the sequence of coordinates for the polygon * @return a normal vector */ private static Vector3D AverageNormal(CoordinateSequence seq) { int n = seq.Count; var sum = new CoordinateZ(0, 0, 0); var p1 = new CoordinateZ(0, 0, 0); var p2 = new CoordinateZ(0, 0, 0); for (int i = 0; i < n - 1; i++) { seq.GetCoordinate(i, p1); seq.GetCoordinate(i + 1, p2); sum.X += (p1.Y - p2.Y) * (p1.Z + p2.Z); sum.Y += (p1.Z - p2.Z) * (p1.X + p2.X); sum.Z += (p1.X - p2.X) * (p1.Y + p2.Y); } sum.X /= n; sum.Y /= n; sum.Z /= n; var norm = Vector3D.Create(sum).Normalize(); return(norm); }
private Coordinate[] ReducePointwise(CoordinateSequence coordinates) { var coordReduce = new Coordinate[coordinates.Count]; // copy coordinates and reduce for (int i = 0; i < coordinates.Count; i++) { var coord = coordinates.GetCoordinate(i).Copy(); _targetPm.MakePrecise(coord); coordReduce[i] = coord; } return(coordReduce); }
// Use map to restore M values on the coordinate array private CoordinateSequence restoreDim4(CoordinateSequence cs, Dictionary <Coordinate, Double> map) { CoordinateSequence seq = new PackedCoordinateSequenceFactory(PackedCoordinateSequenceFactory.PackedType.Double).Create(cs.Count, 4); for (int i = 0; i < cs.Count; i++) { seq.SetOrdinate(i, 0, cs.GetOrdinate(i, 0)); seq.SetOrdinate(i, 1, cs.GetOrdinate(i, 1)); seq.SetOrdinate(i, 2, cs.GetOrdinate(i, 2)); Double d = map[cs.GetCoordinate(i)]; seq.SetOrdinate(i, 3, d == null ? Double.NaN : d); } return(seq); }
protected bool IsAllCoordsEqual(CoordinateSequence seq, Coordinate coord) { for (int i = 0; i < seq.Count; i++) { if (!coord.Equals(seq.GetCoordinate(i))) { return(false); } if (coord.X != seq.GetOrdinate(i, 0)) { return(false); } if (coord.Y != seq.GetOrdinate(i, 1)) { return(false); } if (seq.HasZ) { if (coord.Z != seq.GetZ(i)) { return(false); } } if (seq.HasM) { if (coord.M != seq.GetM(i)) { return(false); } } if (seq.Dimension > 2) { if (coord[2] != seq.GetOrdinate(i, 2)) { return(false); } } if (seq.Dimension > 3) { if (coord[3] != seq.GetOrdinate(i, 3)) { return(false); } } } return(true); }
private Coordinate[] ReduceCompress(CoordinateSequence coordinates) { var noRepeatCoordList = new CoordinateList(); // copy coordinates and reduce for (int i = 0; i < coordinates.Count; i++) { var coord = coordinates.GetCoordinate(i).Copy(); _targetPm.MakePrecise(coord); noRepeatCoordList.Add(coord, false); } // remove repeated points, to simplify returned geometry as much as possible var noRepeatCoords = noRepeatCoordList.ToCoordinateArray(); return(noRepeatCoords); }
public void Filter(CoordinateSequence seq, int i) { if (seq == null) { throw new ArgumentNullException(); } if (seq.Count == 1) { return; } if (i > seq.Count - 2) { return; } LineSegment currentSegment; SpikeFix spikeFix; int index; if (i == 0) { currentSegment = new LineSegment(seq.GetCoordinate(0), seq.GetCoordinate(1)); if (IsClosed(seq)) { _lastSegment = new LineSegment(seq.GetCoordinate(seq.Count - 2), seq.GetCoordinate(0)); spikeFix = CheckSpike(_lastSegment, currentSegment); if (spikeFix != SpikeFix.NoSpike) { index = spikeFix == SpikeFix.First ? seq.Count - 2 : 1; FixSpike(seq, seq.Count - 1, index); FixSpike(seq, 0, index); _changed = true; } } _lastSegment = currentSegment; return; } currentSegment = new LineSegment(_lastSegment.P1, seq.GetCoordinate(i + 1)); spikeFix = CheckSpike(_lastSegment, currentSegment); if (spikeFix != SpikeFix.NoSpike) { index = i + (int)spikeFix; FixSpike(seq, i, index); _changed = true; } _lastSegment = new LineSegment(seq.GetCoordinate(i), seq.GetCoordinate(i + 1)); }
private static void DoTest(CoordinateSequence forward, CoordinateSequence reversed) { const double eps = 1e-12; Assert.AreEqual(forward.Count, reversed.Count, "Coordinate sequences don't have same size"); Assert.AreEqual(forward.Ordinates, reversed.Ordinates, "Coordinate sequences don't serve same ordinate values"); var ordinates = ToOrdinateArray(forward.Ordinates); int j = forward.Count; for (int i = 0; i < forward.Count; i++) { j--; foreach (var ordinate in ordinates) { Assert.AreEqual(forward.GetOrdinate(i, ordinate), reversed.GetOrdinate(j, ordinate), eps, string.Format("{0} values are not within tolerance", ordinate)); } var cf = forward.GetCoordinate(i); var cr = reversed.GetCoordinate(j); Assert.IsFalse(ReferenceEquals(cf, cr), "Coordinate sequences deliver same coordinate instances"); Assert.IsTrue(cf.Equals(cr), "Coordinate sequences do not provide equal coordinates"); } }
/// <summary> /// Computes whether a ring defined by an <see cref="CoordinateSequence"/> is /// oriented counter-clockwise. /// <list type="Bullet"> /// <item>The list of points is assumed to have the first and last points equal.</item> /// <item>This will handle coordinate lists which contain repeated points.</item> /// </list> /// This algorithm is <b>only</b> guaranteed to work with valid rings.If the /// ring is invalid(e.g.self-crosses or touches), the computed result may not /// be correct. /// </summary> /// <param name="ring">A <c>CoordinateSequence</c>s forming a ring.</param> /// <returns><c>true</c> if the ring is oriented counter-clockwise.</returns> /// <exception cref="ArgumentException">Thrown if there are too few points to determine orientation (< 4)</exception> public static bool IsCCW(CoordinateSequence ring) { // # of points without closing endpoint int nPts = ring.Count - 1; // sanity check if (nPts < 3) { throw new ArgumentException( "Ring has fewer than 4 points, so orientation cannot be determined", nameof(ring)); } // find highest point var hiPt = ring.GetCoordinate(0); int hiIndex = 0; for (int i = 1; i <= nPts; i++) { var p = ring.GetCoordinate(i); if (p.Y > hiPt.Y) { hiPt = ring.GetCoordinate(i); hiIndex = i; } } // find distinct point before highest point Coordinate prev; int iPrev = hiIndex; do { iPrev = iPrev - 1; if (iPrev < 0) { iPrev = nPts; } prev = ring.GetCoordinate(iPrev); } while (prev.Equals2D(hiPt) && iPrev != hiIndex); // find distinct point after highest point Coordinate next; int iNext = hiIndex; do { iNext = (iNext + 1) % nPts; next = ring.GetCoordinate(iNext); } while (next.Equals2D(hiPt) && iNext != hiIndex); /** * 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); } var disc = Index(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; if (disc == OrientationIndex.Collinear) { // polygon is CCW if previous 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); }
/** * Returns a coordinateSequence free of Coordinates with X or Y NaN value, * and if desired, free of duplicated coordinates. makeSequenceValid keeps * the original dimension of input sequence. * * @param sequence input sequence of coordinates * @param preserveDuplicateCoord if duplicate coordinates must be preserved * @param close if the sequence must be closed * @return a new CoordinateSequence with valid XY values */ private static CoordinateSequence makeSequenceValid(CoordinateSequence sequence, bool preserveDuplicateCoord, bool close) { int dim = sequence.Dimension; // we Add 1 to the sequence size for the case where we have to close the linear ring double[] array = new double[(sequence.Count + 1) * sequence.Dimension]; bool modified = false; int count = 0; // Iterate through coordinates, skip points with x=NaN, y=NaN or duplicate for (int i = 0; i < sequence.Count; i++) { if (Double.IsNaN(sequence.GetOrdinate(i, 0)) || Double.IsNaN(sequence.GetOrdinate(i, 1))) { modified = true; continue; } if (!preserveDuplicateCoord && count > 0 && sequence.GetCoordinate(i).Equals(sequence.GetCoordinate(i - 1))) { modified = true; continue; } for (int j = 0; j < dim; j++) { array[count * dim + j] = sequence.GetOrdinate(i, j); if (j == dim - 1) { count++; } } } // Close the sequence if it is not closed and there is already 3 distinct coordinates if (close && count > 2 && (array[0] != array[(count - 1) * dim] || array[1] != array[(count - 1) * dim + 1])) { System.Array.Copy(array, 0, array, count * dim, dim); modified = true; count++; } // Close z, m dimension if needed if (close && count > 3 && dim > 2) { for (int d = 2; d < dim; d++) { if (array[(count - 1) * dim + d] != array[d]) { modified = true; } array[(count - 1) * dim + d] = array[d]; } } if (modified) { double[] shrinkedArray = new double[count * dim]; System.Array.Copy(array, 0, shrinkedArray, 0, count * dim); return(PackedCoordinateSequenceFactory.DoubleFactory.Create(shrinkedArray, dim)); } else { return(sequence); } }
/// <summary> /// Gets the coordinate at the given index /// </summary> /// <param name="index">The index</param> /// <returns>The coordinate at the given index</returns> public Coordinate GetCoordinate(int index) { return(_pts.GetCoordinate(_start + index)); }
/// <summary> /// Tests for equality using all supported accessors, /// to provides test coverage for them. /// </summary> /// <param name="seq"></param> /// <param name="coords"></param> /// <returns></returns> protected bool IsEqual(CoordinateSequence seq, Coordinate[] coords) { if (seq.Count != coords.Length) { return(false); } // carefully get coordinate of the same type as the sequence var p = seq.CreateCoordinate(); for (int i = 0; i < seq.Count; i++) { if (!coords[i].Equals(seq.GetCoordinate(i))) { return(false); } // Ordinate named getters if (!coords[i].X.Equals(seq.GetX(i))) { return(false); } if (!coords[i].Y.Equals(seq.GetY(i))) { return(false); } if (seq.HasZ) { if (!coords[i].Z.Equals(seq.GetZ(i))) { return(false); } } if (seq.HasM) { if (!coords[i].M.Equals(seq.GetM(i))) { return(false); } } // Ordinate indexed getters if (!coords[i].X.Equals(seq.GetOrdinate(i, 0))) { return(false); } if (!coords[i].Y.Equals(seq.GetOrdinate(i, 1))) { return(false); } if (seq.Dimension > 2) { if (!coords[i][2].Equals(seq.GetOrdinate(i, 2))) { return(false); } } if (seq.Dimension > 3) { if (!coords[i][3].Equals(seq.GetOrdinate(i, 3))) { return(false); } } // Coordinate getter seq.GetCoordinate(i, p); if (!coords[i].X.Equals(p.X)) { return(false); } if (!coords[i].Y.Equals(p.Y)) { return(false); } if (seq.HasZ) { if (!coords[i].Z.Equals(p.Z)) { return(false); } } if (seq.HasM) { if (!coords[i].M.Equals(p.M)) { return(false); } } } return(true); }
private static bool IsClosed(CoordinateSequence seq) { return(seq.GetCoordinate(seq.Count - 1).Equals2D(seq.GetCoordinate(0))); }
/// <summary> /// Tests if a ring defined by a <see cref="CoordinateSequence"/> is /// oriented counter-clockwise. /// <list type="bullet"> /// <item><description>The list of points is assumed to have the first and last points equal.</description></item> /// <item><description>This handles coordinate lists which contain repeated points.</description></item> /// <item><description>This handles rings which contain collapsed segments (in particular, along the top of the ring).</description></item> /// </list> /// This algorithm is guaranteed to work with valid rings. /// It also works with "mildly invalid" rings /// which contain collapsed(coincident) flat segments along the top of the ring. /// If the ring is "more" invalid (e.g.self-crosses or touches), /// the computed result may not be correct. /// </summary> /// <param name="ring">A <c>CoordinateSequence</c>s forming a ring (with first and last point identical).</param> /// <returns><c>true</c> if the ring is oriented counter-clockwise.</returns> /// <exception cref="ArgumentException">Thrown if there are too few points to determine orientation (< 4)</exception> public static bool IsCCW(CoordinateSequence ring) { // # of points without closing endpoint int nPts = ring.Count - 1; // sanity check if (nPts < 3) { throw new ArgumentException( "Ring has fewer than 4 points, so orientation cannot be determined", nameof(ring)); } /* * Find first highest point after a lower point, if one exists * (e.g. a rising segment) * If one does not exist, hiIndex will remain 0 * and the ring must be flat. * Note this relies on the convention that * rings have the same start and end point. */ var upHiPt = ring.GetCoordinate(0); double prevY = upHiPt.Y; Coordinate upLowPt = null; int iUpHi = 0; for (int i = 1; i <= nPts; i++) { double py = ring.GetOrdinate(i, Ordinate.Y); /* * If segment is upwards and endpoint is higher, record it */ if (py > prevY && py >= upHiPt.Y) { upHiPt = ring.GetCoordinate(i); iUpHi = i; upLowPt = ring.GetCoordinate(i - 1); } prevY = py; } /* * Check if ring is flat and return default value if so */ if (iUpHi == 0) { return(false); } /* * Find the next lower point after the high point * (e.g. a falling segment). * This must exist since ring is not flat. */ int iDownLow = iUpHi; do { iDownLow = (iDownLow + 1) % nPts; } while (iDownLow != iUpHi && ring.GetOrdinate(iDownLow, Ordinate.Y) == upHiPt.Y); var downLowPt = ring.GetCoordinate(iDownLow); int iDownHi = iDownLow > 0 ? iDownLow - 1 : nPts - 1; var downHiPt = ring.GetCoordinate(iDownHi); /* * Two cases can occur: * 1) the hiPt and the downPrevPt are the same. * This is the general position case of a "pointed cap". * The ring orientation is determined by the orientation of the cap * 2) The hiPt and the downPrevPt are different. * In this case the top of the cap is flat. * The ring orientation is given by the direction of the flat segment */ if (upHiPt.Equals2D(downHiPt)) { /* * Check for the case where the cap has configuration A-B-A. * 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 (upLowPt.Equals2D(upHiPt) || downLowPt.Equals2D(upHiPt) || upLowPt.Equals2D(downLowPt)) { return(false); } /* * It can happen that the top segments are coincident. * This is an invalid ring, which cannot be computed correctly. * In this case the orientation is 0, and the result is false. */ var index = Index(upLowPt, upHiPt, downLowPt); return(index == OrientationIndex.CounterClockwise); } else { /* * Flat cap - direction of flat top determines orientation */ double delX = downHiPt.X - upHiPt.X; return(delX < 0); } }