using System;
using System.Collections.Generic;
using System.Linq;
using GeoAPI.Extensions.Feature;
using GeoAPI.Geometries;
using GisSharpBlog.NetTopologySuite.Geometries;
using NetTopologySuite.Extensions.Geometries;
using SharpMap.Api.Editors;
using SharpMap.Api.Layers;
using SharpMap.CoordinateSystems.Transformations;
namespace SharpMap.Editors.Snapping
{
public class SnapRule : ISnapRule
{
///
/// Criteria is used to select (filter) feature candidates (where can we snap)
///
public Func Criteria { get; set; }
///
/// Target layer, where new features will be created.
///
public ILayer NewFeatureLayer { get; set; }
public SnapRole SnapRole { get; set; }
public virtual bool Obligatory { get; set; }
///
/// Number of pixels where snapping will start working.
///
/// Used to construct the envelope used to select features which should be evaluated by this snap rule.
///
public int PixelGravity { get; set; }
///
///
///
///
///
///
///
///
///
///
/// based of the selected tracker in the snapSource the rule can behave differently
/// (only snap branches' first and last coordinate).
///
public virtual SnapResult Execute(IFeature sourceFeature, Tuple[] candidates, IGeometry sourceGeometry, IList snapTargets, ICoordinate worldPos, IEnvelope envelope, int trackingIndex)
{
if (candidates == null || SnapRole == SnapRole.None)
{
return null;
}
// hack preserve snapTargets functionality
var snapTargetGeometries = (snapTargets != null ? snapTargets.Select(t => t.Geometry) : Enumerable.Empty()).ToList();
var minDistance = double.MaxValue; // TODO: incapsulate minDistance in ISnapResult
SnapResult lastSnapResult = null;
foreach (var candidate in candidates)
{
var feature = candidate.Item1;
var layer = candidate.Item2;
if ((Criteria != null && !Criteria(layer, feature)) || (snapTargets != null && snapTargetGeometries.IndexOf(feature.Geometry) == -1))
{
continue;
}
var geometryToSnap = layer.CoordinateTransformation != null
? GeometryTransform.TransformGeometry(feature.Geometry, layer.CoordinateTransformation.MathTransform)
: feature.Geometry;
var snapResult = GetSnapResultForGeometry(worldPos, geometryToSnap, ref minDistance, feature);
if (snapResult == null)
{
continue;
}
snapResult.NewFeatureLayer = NewFeatureLayer;
lastSnapResult = snapResult;
}
return lastSnapResult;
}
private SnapResult GetSnapResultForGeometry(ICoordinate worldPos, IGeometry geometry, ref double minDistance, IFeature feature)
{
var polygon = geometry as IPolygon;
if (polygon != null)
{
switch (SnapRole)
{
case SnapRole.Free:
return PolygonSnapFree(ref minDistance, polygon, worldPos);
case SnapRole.AllTrackers:
return GeometrySnapAllTrackers(ref minDistance, polygon, worldPos);
default:
return PolygonSnapFreeAtObject(ref minDistance, polygon, worldPos);
}
}
var lineString = geometry as ILineString;
if (lineString != null)
{
switch (SnapRole)
{
case SnapRole.Free:
return LineStringSnapFree(ref minDistance, lineString, worldPos);
case SnapRole.FreeAtObject:
return LineStringSnapFreeAtObject(ref minDistance, feature, lineString, worldPos);
case SnapRole.TrackersNoStartNoEnd:
return null;
case SnapRole.AllTrackers:
return LineStringSnapAllTrackers(ref minDistance, lineString, worldPos);
case SnapRole.Start:
return LineStringSnapStart(lineString);
case SnapRole.End:
return LineStringSnapEnd(lineString);
case SnapRole.StartEnd:
return LineStringSnapStartEnd(ref minDistance, lineString, worldPos);
}
}
var multiLineString = geometry as IMultiLineString;
if (multiLineString != null)
{
foreach (var line in multiLineString.Geometries.OfType())
{
var snapresult = GetSnapResultForGeometry(worldPos, line, ref minDistance, feature);
if (snapresult != null)
{
return snapresult;
}
}
}
var multiPolygon = geometry as IMultiPolygon;
if (multiPolygon != null)
{
foreach (var line in multiPolygon.Geometries.OfType())
{
var snapresult = GetSnapResultForGeometry(worldPos, line, ref minDistance, feature);
if (snapresult != null)
{
return snapresult;
}
}
}
if (geometry is IPoint)
{
return new SnapResult(geometry.Coordinates[0], null, NewFeatureLayer, geometry, 0, 0)
{
Rule = this
};
}
return null;
}
private SnapResult LineStringSnapStartEnd(ref double minDistance, ILineString lineString, ICoordinate worldPos)
{
var c1 = lineString.Coordinates[0];
ICoordinate location;
int snapIndexPrevious;
int snapIndexNext;
var distance = GeometryHelper.Distance(c1.X, c1.Y, worldPos.X, worldPos.Y);
SnapResult snapResult = null;
if (distance < minDistance)
{
location = c1;
snapIndexPrevious = 0;
snapIndexNext = 0;
minDistance = distance;
snapResult = new SnapResult(location, null, null, lineString, snapIndexPrevious, snapIndexNext)
{
Rule = this
};
}
var c2 = lineString.Coordinates[lineString.Coordinates.Length - 1];
distance = GeometryHelper.Distance(c2.X, c2.Y, worldPos.X, worldPos.Y);
if (distance >= minDistance)
{
return snapResult;
}
location = c2;
snapIndexPrevious = lineString.Coordinates.Length - 1;
snapIndexNext = lineString.Coordinates.Length - 1;
return new SnapResult(location, null, null, lineString, snapIndexPrevious, snapIndexNext)
{
Rule = this
};
}
private SnapResult LineStringSnapEnd(ILineString lineString)
{
return new SnapResult(lineString.Coordinates[lineString.Coordinates.Length - 1], null, null, lineString,
lineString.Coordinates.Length - 1, lineString.Coordinates.Length - 1)
{
Rule = this
};
}
private SnapResult LineStringSnapStart(ILineString lineString)
{
return new SnapResult(lineString.Coordinates[0], null, null, lineString, 0, 0)
{
Rule = this
};
}
private SnapResult LineStringSnapAllTrackers(ref double minDistance, ILineString lineString, ICoordinate worldPos)
{
return GeometrySnapAllTrackers(ref minDistance, lineString, worldPos);
}
private SnapResult GeometrySnapAllTrackers(ref double minDistance, IGeometry geometry, ICoordinate worldPos)
{
SnapResult snapResult = null;
var coordinates = geometry.Coordinates;
for (int i = 0; i < coordinates.Length; i++)
{
var c1 = coordinates[i];
var distance = GeometryHelper.Distance(c1.X, c1.Y, worldPos.X, worldPos.Y);
if (distance >= minDistance)
{
continue;
}
minDistance = distance;
snapResult = new SnapResult(coordinates[i], null, null, geometry, i, i)
{
Rule = this
};
}
return snapResult;
}
private SnapResult LineStringSnapFreeAtObject(ref double minDistance, IFeature feature, ILineString lineString, ICoordinate worldPos)
{
int vertexIndex;
var nearestPoint = GeometryHelper.GetNearestPointAtLine(lineString, worldPos, minDistance, out vertexIndex);
if (nearestPoint == null)
{
return null;
}
minDistance = GeometryHelper.Distance(nearestPoint.X, nearestPoint.Y, worldPos.X, worldPos.Y);
return new SnapResult(nearestPoint, feature, null, lineString, vertexIndex - 1, vertexIndex)
{
Rule = this
};
}
private SnapResult LineStringSnapFree(ref double minDistance, ILineString lineString, ICoordinate worldPos)
{
SnapResult snapResult = null;
for (int i = 1; i < lineString.Coordinates.Length; i++)
{
var c1 = lineString.Coordinates[i - 1];
var c2 = lineString.Coordinates[i];
var distance = GeometryHelper.LinePointDistance(c1.X, c1.Y, c2.X, c2.Y, worldPos.X, worldPos.Y);
if (distance >= minDistance)
{
continue;
}
minDistance = distance;
snapResult = new SnapResult(GeometryFactory.CreateCoordinate(worldPos.X, worldPos.Y), null, null, lineString, i - 1, i)
{
Rule = this
};
}
return snapResult;
}
private SnapResult PolygonSnapFreeAtObject(ref double minDistance, IPolygon polygon, ICoordinate worldPos)
{
SnapResult snapResult = null;
for (int i = 1; i < polygon.Coordinates.Length; i++)
{
var c1 = polygon.Coordinates[i - 1];
var c2 = polygon.Coordinates[i];
double distance = GeometryHelper.LinePointDistance(c1.X, c1.Y, c2.X, c2.Y, worldPos.X, worldPos.Y);
if (distance >= minDistance)
{
continue;
}
minDistance = distance;
var min_c1 = polygon.Coordinates[i - 1];
var min_c2 = polygon.Coordinates[i];
snapResult = new SnapResult(GeometryHelper.NearestPointAtSegment(min_c1.X, min_c1.Y, min_c2.X, min_c2.Y, worldPos.X,
worldPos.Y), null, null, polygon, i - 1, i)
{
Rule = this
};
}
return snapResult;
}
private SnapResult PolygonSnapFree(ref double minDistance, IPolygon polygon, ICoordinate worldPos)
{
SnapResult snapResult = null;
for (int i = 1; i < polygon.Coordinates.Length; i++)
{
var c1 = polygon.Coordinates[i - 1];
var c2 = polygon.Coordinates[i];
var distance = GeometryHelper.LinePointDistance(c1.X, c1.Y, c2.X, c2.Y, worldPos.X, worldPos.Y);
if (distance >= minDistance)
{
continue;
}
minDistance = distance;
snapResult = new SnapResult(GeometryFactory.CreateCoordinate(worldPos.X, worldPos.Y), null, null, polygon, i - 1, i)
{
Rule = this
};
}
return snapResult;
}
}
}