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);
}
}
}