using System; using GeoAPI.Geometries; using GisSharpBlog.NetTopologySuite.Algorithm; using GisSharpBlog.NetTopologySuite.Geometries; using GisSharpBlog.NetTopologySuite.Utilities; namespace GisSharpBlog.NetTopologySuite.Noding.Snapround { /// /// Implements a "hot pixel" as used in the Snap Rounding algorithm. /// A hot pixel contains the interior of the tolerance square and the boundary /// minus the top and right segments. /// The hot pixel operations are all computed in the integer domain /// to avoid rounding problems. /// public class HotPixel { private readonly LineIntersector li = null; private readonly ICoordinate pt = null; private readonly ICoordinate originalPt = null; private readonly ICoordinate p0Scaled = null; private readonly ICoordinate p1Scaled = null; private readonly double scaleFactor; private double minx; private double maxx; private double miny; private double maxy; /* * The corners of the hot pixel, in the order: * 10 * 23 */ private readonly ICoordinate[] corner = new ICoordinate[4]; private Envelope safeEnv = null; /// /// Initializes a new instance of the class. /// /// /// /// public HotPixel(ICoordinate pt, double scaleFactor, LineIntersector li) { originalPt = pt; this.pt = pt; this.scaleFactor = scaleFactor; this.li = li; if (scaleFactor != 1.0) { this.pt = new Coordinate(Scale(pt.X), Scale(pt.Y)); p0Scaled = new Coordinate(); p1Scaled = new Coordinate(); } InitCorners(this.pt); } /// /// /// public ICoordinate Coordinate { get { return originalPt; } } /// /// Returns a "safe" envelope that is guaranteed to contain the hot pixel. /// /// public IEnvelope GetSafeEnvelope() { if (safeEnv == null) { double safeTolerance = 0.75/scaleFactor; safeEnv = new Envelope(originalPt.X - safeTolerance, originalPt.X + safeTolerance, originalPt.Y - safeTolerance, originalPt.Y + safeTolerance); } return safeEnv; } /// /// /// /// /// /// public bool Intersects(ICoordinate p0, ICoordinate p1) { if (scaleFactor == 1.0) { return IntersectsScaled(p0, p1); } CopyScaled(p0, p0Scaled); CopyScaled(p1, p1Scaled); return IntersectsScaled(p0Scaled, p1Scaled); } /// /// /// /// /// /// public bool IntersectsScaled(ICoordinate p0, ICoordinate p1) { double segMinx = Math.Min(p0.X, p1.X); double segMaxx = Math.Max(p0.X, p1.X); double segMiny = Math.Min(p0.Y, p1.Y); double segMaxy = Math.Max(p0.Y, p1.Y); bool isOutsidePixelEnv = maxx < segMinx || minx > segMaxx || maxy < segMiny || miny > segMaxy; if (isOutsidePixelEnv) { return false; } bool intersects = IntersectsToleranceSquare(p0, p1); Assert.IsTrue(!(isOutsidePixelEnv && intersects), "Found bad envelope test"); return intersects; } /// /// /// /// private void InitCorners(ICoordinate pt) { double tolerance = 0.5; minx = pt.X - tolerance; maxx = pt.X + tolerance; miny = pt.Y - tolerance; maxy = pt.Y + tolerance; corner[0] = new Coordinate(maxx, maxy); corner[1] = new Coordinate(minx, maxy); corner[2] = new Coordinate(minx, miny); corner[3] = new Coordinate(maxx, miny); } /// /// /// /// /// private double Scale(double val) { return (double) Math.Round(val*scaleFactor); } /// /// /// /// /// private void CopyScaled(ICoordinate p, ICoordinate pScaled) { pScaled.X = Scale(p.X); pScaled.Y = Scale(p.Y); } /// /// Tests whether the segment p0-p1 intersects the hot pixel tolerance square. /// Because the tolerance square point set is partially open (along the /// top and right) the test needs to be more sophisticated than /// simply checking for any intersection. However, it /// can take advantage of the fact that because the hot pixel edges /// do not lie on the coordinate grid. It is sufficient to check /// if there is at least one of: /// - a proper intersection with the segment and any hot pixel edge. /// - an intersection between the segment and both the left and bottom edges. /// - an intersection between a segment endpoint and the hot pixel coordinate. /// /// /// /// private bool IntersectsToleranceSquare(ICoordinate p0, ICoordinate p1) { bool intersectsLeft = false; bool intersectsBottom = false; li.ComputeIntersection(p0, p1, corner[0], corner[1]); if (li.IsProper) { return true; } li.ComputeIntersection(p0, p1, corner[1], corner[2]); if (li.IsProper) { return true; } if (li.HasIntersection) { intersectsLeft = true; } li.ComputeIntersection(p0, p1, corner[2], corner[3]); if (li.IsProper) { return true; } if (li.HasIntersection) { intersectsBottom = true; } li.ComputeIntersection(p0, p1, corner[3], corner[0]); if (li.IsProper) { return true; } if (intersectsLeft && intersectsBottom) { return true; } if (p0.Equals(pt)) { return true; } if (p1.Equals(pt)) { return true; } return false; } /// /// Test whether the given segment intersects /// the closure of this hot pixel. /// This is NOT the test used in the standard snap-rounding /// algorithm, which uses the partially closed tolerance square instead. /// This routine is provided for testing purposes only. /// /// /// /// private bool IntersectsPixelClosure(ICoordinate p0, ICoordinate p1) { li.ComputeIntersection(p0, p1, corner[0], corner[1]); if (li.HasIntersection) { return true; } li.ComputeIntersection(p0, p1, corner[1], corner[2]); if (li.HasIntersection) { return true; } li.ComputeIntersection(p0, p1, corner[2], corner[3]); if (li.HasIntersection) { return true; } li.ComputeIntersection(p0, p1, corner[3], corner[0]); if (li.HasIntersection) { return true; } return false; } } }