private bool CheckSequences(CoordinateSequence c1, CoordinateSequence c2) { if (c1.Count != c2.Count) { return(false); } if (c1.Dimension != c2.Dimension) { return(false); } if (c1.Measures != c2.Measures) { return(false); } for (int i = 0; i < c1.Count; i++) { int j = c1.Count - i - 1; for (int k = 0; k < c1.Dimension; k++) { if (c1.GetOrdinate(i, k) != c2.GetOrdinate(j, k)) { if (!(double.IsNaN(c1.GetOrdinate(i, k)) && double.IsNaN(c2.GetOrdinate(j, k)))) { return(false); } } } } return(true); }
/// <summary> /// Applies this transformation to the i'th coordinate /// in the given CoordinateSequence. /// </summary> /// <param name="seq"> a <code>CoordinateSequence</code></param> /// <param name="i"> the index of the coordinate to transform</param> public void Transform(CoordinateSequence seq, int i) { double xp = _m00 * seq.GetOrdinate(i, 0) + _m01 * seq.GetOrdinate(i, 1) + _m02; double yp = _m10 * seq.GetOrdinate(i, 0) + _m11 * seq.GetOrdinate(i, 1) + _m12; seq.SetOrdinate(i, 0, xp); seq.SetOrdinate(i, 1, yp); }
/// <summary> /// /// </summary> /// <param name="seq">The coordinate sequence</param> public void Filter(CoordinateSequence seq, int i) { double xp = seq.GetOrdinate(i, 0) + _trans.X; double yp = seq.GetOrdinate(i, 1) + _trans.Y; seq.SetOrdinate(i, 0, xp); seq.SetOrdinate(i, 1, yp); }
/// <summary> /// Transforms the coordinate at <paramref name="index"/> of <paramref name="sequence"/> to the tile coordinate system. /// The return value is the position relative to the local point at (<paramref name="currentX"/>, <paramref name="currentY"/>). /// </summary> /// <param name="sequence">The input sequence</param> /// <param name="index">The index of the coordinate to transform</param> /// <param name="currentX">The current horizontal component of the cursor location. This value is updated.</param> /// <param name="currentY">The current vertical component of the cursor location. This value is updated.</param> /// <returns>The position relative to the local point at (<paramref name="currentX"/>, <paramref name="currentY"/>).</returns> public (int x, int y) Transform(CoordinateSequence sequence, int index, ref int currentX, ref int currentY) { int localX = (int)((sequence.GetOrdinate(index, Ordinate.X) - Left) / LongitudeStep); int localY = (int)((Top - sequence.GetOrdinate(index, Ordinate.Y)) / LatitudeStep); int dx = localX - currentX; int dy = localY - currentY; currentX = localX; currentY = localY; return(dx, dy); }
public void Filter(CoordinateSequence seq, int i) { var result = _transform.Transform( new[] { seq.GetOrdinate(i, Ordinate.X), seq.GetOrdinate(i, Ordinate.Y) }); seq.SetOrdinate(i, Ordinate.X, result[0]); seq.SetOrdinate(i, Ordinate.Y, result[1]); }
private static void FixSpike(CoordinateSequence seq, int fixIndex, int fixWithIndex) { seq.SetOrdinate(fixIndex, Ordinate.X, seq.GetOrdinate(fixWithIndex, Ordinate.X)); seq.SetOrdinate(fixIndex, Ordinate.Y, seq.GetOrdinate(fixWithIndex, Ordinate.Y)); if ((seq.Ordinates & Ordinates.Z) == Ordinates.Z) { seq.SetOrdinate(fixIndex, Ordinate.Z, seq.GetOrdinate(fixWithIndex, Ordinate.Z)); } if ((seq.Ordinates & Ordinates.M) == Ordinates.M) { seq.SetOrdinate(fixIndex, Ordinate.M, seq.GetOrdinate(fixWithIndex, Ordinate.M)); } }
// 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); }
/// <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); }
private static void CheckCoordinateAt(CoordinateSequence seq1, int pos1, CoordinateSequence seq2, int pos2, int dim) { Assert.AreEqual(seq1.GetOrdinate(pos1, Ordinate.X), seq2.GetOrdinate(pos2, Ordinate.X), "unexpected x-ordinate at pos " + pos2); Assert.AreEqual(seq1.GetOrdinate(pos1, Ordinate.Y), seq2.GetOrdinate(pos2, Ordinate.Y), "unexpected y-ordinate at pos " + pos2); // check additional ordinates for (int j = 2; j < dim; j++) { Assert.AreEqual(seq1.GetOrdinate(pos1, j), seq2.GetOrdinate(pos2, j), "unexpected " + j + "-ordinate at pos " + pos2); } }
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); }
public void Filter(CoordinateSequence seq) { if (!seq.HasZ) { _hasZ = false; return; } for (int i = 0; i < seq.Count; i++) { double z = seq.GetOrdinate(i, Ordinate.Z); _em.Add(seq.GetOrdinate(i, Ordinate.X), seq.GetOrdinate(i, Ordinate.Y), z); } }
/** * Computes a point which is the average of all coordinates * in a sequence. * If the sequence lies in a single plane, * the computed point also lies in the plane. * * @param seq a coordinate sequence * @return a Coordinate with averaged ordinates */ private static Coordinate AveragePoint(CoordinateSequence seq) { var a = new CoordinateZ(0, 0, 0); int n = seq.Count; for (int i = 0; i < n; i++) { a.X += seq.GetOrdinate(i, 0); a.Y += seq.GetOrdinate(i, 1); a.Z += seq.GetOrdinate(i, 2); } a.X /= n; a.Y /= n; a.Z /= n; return(a); }
protected static CoordinateSequence AddCoordinateToSequence(CoordinateSequence sequence, CoordinateSequenceFactory factory, double x, double y, double?z, double?m) { // Create a new sequence var newSequence = factory.Create(sequence.Count + 1, sequence.Dimension, sequence.Measures); // Copy old values 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)); } } // new coordinate newSequence.SetX(sequence.Count, x); newSequence.SetY(sequence.Count, y); if (z.HasValue) { newSequence.SetZ(sequence.Count, z.Value); } if (m.HasValue) { newSequence.SetM(sequence.Count, m.Value); } return(newSequence); }
/// <summary> /// Transforms the coordinate at <paramref name="index"/> of <paramref name="sequence"/> to the tile coordinate system. /// The return value is the position relative to the local point at (<paramref name="currentX"/>, <paramref name="currentY"/>). /// </summary> /// <param name="sequence">The input sequence</param> /// <param name="index">The index of the coordinate to transform</param> /// <param name="currentX">The current horizontal component of the cursor location. This value is updated.</param> /// <param name="currentY">The current vertical component of the cursor location. This value is updated.</param> /// <returns>The position relative to the local point at (<paramref name="currentX"/>, <paramref name="currentY"/>).</returns> public (int x, int y) Transform(CoordinateSequence sequence, int index, ref int currentX, ref int currentY) { var lon = sequence.GetOrdinate(index, Ordinate.X); var lat = sequence.GetOrdinate(index, Ordinate.Y); var meters = WebMercatorHandler.LatLonToMeters(lat, lon); var pixels = WebMercatorHandler.MetersToPixels(meters, _tile.Zoom, (int)_extent); int localX = (int)(pixels.x - _left); int localY = (int)(_top - pixels.y); int dx = localX - currentX; int dy = localY - currentY; currentX = localX; currentY = localY; return(dx, dy); }
public override double GetOrdinate(int index, int ordinateIndex) { // Z ord is always 0 if (ordinateIndex > 1) { return(0); } return(_seq.GetOrdinate(index, _indexMap[ordinateIndex])); }
public void Filter(CoordinateSequence seq) { if (!seq.HasZ) { // if no Z then short-circuit evaluation Done = true; return; } for (int i = 0; i < seq.Count; i++) { // if Z not populated then assign using model if (double.IsNaN(seq.GetZ(i))) { double z = _em.GetZ(seq.GetOrdinate(i, Ordinate.X), seq.GetOrdinate(i, Ordinate.Y)); seq.SetOrdinate(i, Ordinate.Z, z); } } }
/// <summary> /// Evaluates the <see cref="Interval"/> of the <paramref name="ordinate"/>-values in /// <paramref name="sequence"/> and writes it using the provided <paramref name="writer"/> /// </summary> /// <param name="sequence">The sequence</param> /// <param name="ordinate">The ordinate</param> /// <param name="writer">The writer</param> protected void WriteInterval(CoordinateSequence sequence, Ordinate ordinate, BinaryWriter writer) { Interval interval; if (!sequence.TryGetOrdinateIndex(ordinate, out int ordinateIndex)) { interval = ordinate == Ordinate.M ? Interval.Create(ShapeFileConstants.NoDataValue) : Interval.Create(double.NaN); } else if (ordinate == Ordinate.M) { double val = sequence.GetOrdinate(0, ordinateIndex); if (double.IsNaN(val)) { val = ShapeFileConstants.NoDataValue; } interval = Interval.Create(val); for (int i = 1, cnt = sequence.Count; i < cnt; i++) { val = sequence.GetOrdinate(i, ordinateIndex); if (double.IsNaN(val)) { val = ShapeFileConstants.NoDataValue; } interval = interval.ExpandedByValue(val); } } else { double val = sequence.GetOrdinate(0, ordinateIndex); interval = Interval.Create(val); for (int i = 1, cnt = sequence.Count; i < cnt; i++) { interval = interval.ExpandedByValue(sequence.GetOrdinate(i, ordinateIndex)); } } writer.Write(interval.Min); writer.Write(interval.Max); }
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> /// Write a <see cref="CoordinateSequence"/>. /// </summary> /// <param name="sequence">The coordinate sequence to write</param> /// <param name="emitSize">A flag indicating if the size of <paramref name="sequence"/> should be written, too.</param> /// <param name="writer">The writer.</param> protected void Write(CoordinateSequence sequence, bool emitSize, BinaryWriter writer) { if (emitSize) { writer.Write(sequence.Count); } // zm-values if not provided by sequence double ordinateZ = Coordinate.NullOrdinate; double ordinateM = Coordinate.NullOrdinate; // test if zm-values are provided by sequence bool getZ = sequence.HasZ; bool getM = sequence.HasM; // test if zm-values should be emitted bool writeZ = (HandleOrdinates & Ordinates.Z) == Ordinates.Z; bool writeM = (HandleOrdinates & Ordinates.M) == Ordinates.M; for (int index = 0; index < sequence.Count; index++) { writer.Write(sequence.GetOrdinate(index, 0)); writer.Write(sequence.GetOrdinate(index, 1)); if (writeZ) { if (getZ) { ordinateZ = sequence.GetZ(index); } writer.Write(ordinateZ); } if (writeM) { if (getM) { ordinateM = sequence.GetM(index); } writer.Write(ordinateM); } } }
/// <summary> /// Checks two <see cref="CoordinateSequence"/>s for equality. The following items are checked: /// <list type="bullet"> /// <item><description>size</description></item> /// <item><description>dimension up to <paramref name="dimension"/></description></item> /// <item><description>ordinate values with <paramref name="tolerance"/></description></item> /// </list> /// </summary> /// <param name="seq1">a sequence</param> /// <param name="seq2">another sequence</param> /// <returns><see langword="true"/> if both sequences are equal.</returns> public static bool CheckEqual(CoordinateSequence seq1, CoordinateSequence seq2, int dimension, double tolerance) { if (ReferenceEquals(seq1, seq2)) { return(true); } if (seq1 is null || seq2 is null) { return(false); } if (seq1.Count != seq2.Count) { return(false); } if (seq1.Dimension < dimension) { throw new ArgumentException("dimension too high for seq1", nameof(seq1)); } if (seq2.Dimension < dimension) { throw new ArgumentException("dimension too high for seq2", nameof(seq2)); } for (int i = 0, cnt = seq1.Count; i < cnt; i++) { for (int j = 0; j < dimension; j++) { double val1 = seq1.GetOrdinate(i, j); double val2 = seq2.GetOrdinate(i, j); if (double.IsNaN(val1)) { if (!double.IsNaN(val2)) { return(false); } } else if (Math.Abs(val1 - val2) > tolerance) { return(false); } } } return(true); }
// If X or Y is null, return an empty Point private Point makePointValid(Point point) { CoordinateSequence sequence = point.CoordinateSequence; GeometryFactory factory = point.Factory; CoordinateSequenceFactory csFactory = factory.CoordinateSequenceFactory; if (sequence.Count == 0) { return(point); } else if (Double.IsNaN(sequence.GetOrdinate(0, 0)) || Double.IsNaN(sequence.GetOrdinate(0, 1))) { return(factory.CreatePoint(csFactory.Create(0, sequence.Dimension))); } else if (sequence.Count == 1) { return(point); } else { throw new Exception(); //throw new RuntimeException("JTS cannot Create a point from a CoordinateSequence containing several points"); } }
/** * Returns the measure length of the segment. This method assumes that the * length of the LineString is defined by the absolute value of (last * coordinate - first coordinate) in the CoordinateSequence. If either * measure is not defined or the CoordinateSequence contains no coordinates, * then Double.NaN is returned. If there is only 1 element in the * CoordinateSequence, then 0 is returned. * * @return The measure length of the LineString */ public double GetMLength() { if (CoordinateSequence.Count == 0) { return(Double.NaN); } if (CoordinateSequence.Count == 1) { return(0.0D); } int lastIndex = CoordinateSequence.Count - 1; double begin = CoordinateSequence.GetOrdinate(0, Ordinate.M); double end = CoordinateSequence.GetOrdinate(lastIndex, Ordinate.M); return((Double.IsNaN(begin) || Double.IsNaN(end)) ? Double.NaN : Math.Abs(end - begin)); }
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); }
private static CoordinateSequence SetDimension(CoordinateSequenceFactory fact, CoordinateSequence seq, int dimension) { if (seq.Dimension == dimension && seq.Measures == 0) { return(seq); } var res = fact.Create(seq.Count, dimension, 0); dimension = Math.Min(dimension, seq.Dimension); for (int i = 0; i < seq.Count; i++) { for (int j = 0; j < dimension; j++) { res.SetOrdinate(i, j, seq.GetOrdinate(i, j)); } } return(res); }
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"); } }
/** * 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> /// 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); }
/// <summary> /// Rounds the Coordinates in the sequence to match the PrecisionModel /// </summary> public void Filter(CoordinateSequence seq, int i) { seq.SetOrdinate(i, 0, _precModel.MakePrecise(seq.GetOrdinate(i, 0))); seq.SetOrdinate(i, 1, _precModel.MakePrecise(seq.GetOrdinate(i, 1))); }
/// <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); } }