using System; using GeoAPI.Geometries; using GisSharpBlog.NetTopologySuite.Geometries.Utilities; using Wintellect.PowerCollections; namespace GisSharpBlog.NetTopologySuite.Operation.Overlay.Snap { /// /// Snaps the vertices and segments of a to another Geometry's vertices. /// Improves robustness for overlay operations, by eliminating /// nearly parallel edges (which cause problems during noding and intersection calculation). /// public class GeometrySnapper { private const double SnapPrexisionFactor = 10E-10; private readonly IGeometry srcGeom; /// /// /// /// public GeometrySnapper(IGeometry g) { srcGeom = g; } /// /// Estimates the snap tolerance for a Geometry, taking into account its precision model. /// /// /// The estimated snap tolerance public static double ComputeOverlaySnapTolerance(IGeometry g) { double snapTolerance = ComputeSizeBasedSnapTolerance(g); /** * Overlay is carried out in most precise precision model * of inputs. * If this precision model is fixed, then the snap tolerance * must reflect the grid size. * Precisely, the snap tolerance should be at least * the distance from a corner of a precision grid cell * to the centre point of the cell. */ IPrecisionModel pm = g.PrecisionModel; if (pm.PrecisionModelType == PrecisionModels.Fixed) { double fixedSnapTol = (1/pm.Scale)*2/1.415; if (fixedSnapTol > snapTolerance) { snapTolerance = fixedSnapTol; } } return snapTolerance; } /// /// /// /// /// public static double ComputeSizeBasedSnapTolerance(IGeometry g) { IEnvelope env = g.EnvelopeInternal; double minDimension = Math.Min(env.Height, env.Width); double snapTol = minDimension*SnapPrexisionFactor; return snapTol; } /// /// /// /// /// /// public static double ComputeOverlaySnapTolerance(IGeometry g0, IGeometry g1) { return Math.Min(ComputeOverlaySnapTolerance(g0), ComputeOverlaySnapTolerance(g1)); } /// /// Snaps two geometries together with a given tolerance. /// /// /// /// /// public static IGeometry[] Snap(IGeometry g0, IGeometry g1, double snapTolerance) { IGeometry[] snapGeom = new IGeometry[2]; GeometrySnapper snapper0 = new GeometrySnapper(g0); snapGeom[0] = snapper0.SnapTo(g1, snapTolerance); GeometrySnapper snapper1 = new GeometrySnapper(g1); /** * Snap the second geometry to the snapped first geometry * (this strategy minimizes the number of possible different points in the result) */ snapGeom[1] = snapper1.SnapTo(snapGeom[0], snapTolerance); return snapGeom; } /// /// Snaps the vertices in the component s /// of the source geometry /// to the vertices of the given geometry. /// /// /// /// public IGeometry SnapTo(IGeometry g, double tolerance) { ICoordinate[] snapPts = ExtractTargetCoordinates(g); SnapTransformer snapTrans = new SnapTransformer(tolerance, snapPts); return snapTrans.Transform(srcGeom); } /// /// /// /// /// public ICoordinate[] ExtractTargetCoordinates(IGeometry g) { // TODO: should do this more efficiently. Use CoordSeq filter to get points, KDTree for uniqueness & queries Set ptSet = new Set(g.Coordinates); ICoordinate[] result = new ICoordinate[ptSet.Count]; ptSet.CopyTo(result, 0); return result; } /// /// Computes the snap tolerance based on the input geometries. /// /// /// private double ComputeSnapTolerance(ICoordinate[] ringPts) { double minSegLen = ComputeMinimumSegmentLength(ringPts); // Use a small percentage of this to be safe double snapTol = minSegLen/10; return snapTol; } /// /// /// /// /// private double ComputeMinimumSegmentLength(ICoordinate[] pts) { double minSegLen = Double.MaxValue; for (int i = 0; i < pts.Length - 1; i++) { double segLen = pts[i].Distance(pts[i + 1]); if (segLen < minSegLen) { minSegLen = segLen; } } return minSegLen; } } /// /// /// internal class SnapTransformer : GeometryTransformer { private readonly double snapTolerance; private readonly ICoordinate[] snapPts; /// /// /// /// /// public SnapTransformer(double snapTolerance, ICoordinate[] snapPts) { this.snapTolerance = snapTolerance; this.snapPts = snapPts; } /// /// /// /// /// /// protected override ICoordinateSequence TransformCoordinates(ICoordinateSequence coords, IGeometry parent) { ICoordinate[] srcPts = coords.ToCoordinateArray(); ICoordinate[] newPts = SnapLine(srcPts, snapPts); return factory.CoordinateSequenceFactory.Create(newPts); } /// /// /// /// /// /// private ICoordinate[] SnapLine(ICoordinate[] srcPts, ICoordinate[] snapPts) { LineStringSnapper snapper = new LineStringSnapper(srcPts, snapTolerance); return snapper.SnapTo(snapPts); } } }