ThinkGeo.com    |     Documentation    |     Premium Support

Snapping when drawing new line

Hi,

I have a requirement to “snap” while drawing a new line to existing points on another layer.

I already wrote a custom EditInteractiveOverlay to get “snapping” working when editing an existing line. I based the code for snapping while editing on the sample in:

Can we use a custom TrackInteractiveOverlay to support snapping while drawing a new line ?
The track mode we use is the TrackMode.StraightLine

Hi Yohan,

If you want to write a custom track layer with snap function, please try to override OnVertexAdding function, but the behavior should looks like you click to add a new vertex, the new vertex will snap to target nearest point. It won’t looks like the sample for edit overlay, which can snap when your mouse move.

The reason is for Edit we have an event MoveVertexCore, but for Track we only have the MouseMoveCore, we cannot implement the same logic in MouseMoveCore.

Wish that’s helpful.

Regards,

Ethan

Hi Ethan,

Could you provide a sample for this?

Have not worked with vertex before.

Hi Yohan,

You can write the custom layer just like this class: https://github.com/ThinkGeo/SnapToLayerSample-ForWpf/blob/master/SnapToLayer/SnapToLayerEditInteractiveOverlay.cs

And the event should looks like this:

    protected override void OnVertexAdding(VertexAddingTrackInteractiveOverlayEventArgs e)
    {

        PointShape snapPointShape = null;
        if (toleranceType == ToleranceCoordinates.Screen)
        {
            snapPointShape = FindNearestSnappingPointPixel(targetControlPoint);
        }
        else
        {
            snapPointShape = FindNearestSnappingPoint(targetControlPoint);
        }

        e.AddingVertex = new Vertex(snapPointShape);
        
        base.OnVertexAdding(e);
    }

Wish that’s helpful.

Regards,

Ethan

Hi Ethan,

I have tried writing the custom track layer.
However I 've run into a problem.
It seems that DrawTileCore is not called in the custom track layer.

is there another way to get the info required in FindNearestSnappingPointPixel and FindNearestSnappingPoint ?

here is my code for the layer:

>  class SnapToLayerTrackInteractiveOverlay: TrackInteractiveOverlay
>     {
>         private List<FeatureLayer> toSnapShapeFeatureLayerList = new List<FeatureLayer>();
>         private float tolerance;
>         private DistanceUnit toleranceUnit;
>         private GeographyUnit geographyUnit;

>         private RectangleShape currentWorldExtent;
>         private float mapWidth;
>         private float mapHeight;
>         private ToleranceCoordinates toleranceType;

>         private bool enableSnapping = false;

>         public SnapToLayerTrackInteractiveOverlay():base()
>         {

>         }

>         //FeatureLayer for the layer to be snapped to.
>         public List<FeatureLayer> ToSnapShapeFeatureLayerList
>         {
>             get { return toSnapShapeFeatureLayerList; }

>         }

>         public ToleranceCoordinates ToleranceType
>         {
>             get { return toleranceType; }
>             set { toleranceType = value; }
>         }

>         public float Tolerance
>         {
>             get { return tolerance; }
>             set { tolerance = value; }
>         }

>         public DistanceUnit ToleranceUnit
>         {
>             get { return toleranceUnit; }
>             set { toleranceUnit = value; }
>         }

>         public bool EnableSnapping
>         {
>             get { return enableSnapping; }
>             set { enableSnapping = value; }
>         }

>         protected override void OnVertexAdding(VertexAddingTrackInteractiveOverlayEventArgs e)
>         {

>             if (enableSnapping && ToSnapShapeFeatureLayerList != null && ToSnapShapeFeatureLayerList.Count > 0 && this.TrackMode == TrackMode.StraightLine)
>             {
>                 var targetControlPoint = new PointShape(e.AddingVertex.X, e.AddingVertex.Y);

>                 PointShape snapPointShape = null;
>                 if (toleranceType == ToleranceCoordinates.Screen)
>                 {
>                     snapPointShape = FindNearestSnappingPointPixel(targetControlPoint);
>                 }
>                 else
>                 {
>                     snapPointShape = FindNearestSnappingPoint(targetControlPoint);
>                 }

>                 if (snapPointShape != null)
>                 {
>                     e.AddingVertex = new Vertex(snapPointShape);
>                 }                
>             }

>             base.OnVertexAdding(e);
>         }

>         //Function to find if dragged control point is within the tolerance of a vertex of layer in screen (pixels) coordinates.
>         private PointShape FindNearestSnappingPointPixel(PointShape targetPointShape)
>         {
>             PointShape toSnapPointShape = null;
>             float nearestDistance = float.MaxValue;

>             foreach (var toSnapShapeFeatureLayer in ToSnapShapeFeatureLayerList)
>             {
>                 toSnapShapeFeatureLayer.Open();
>                 Collection<Feature> toSnapInMemoryFeatures = toSnapShapeFeatureLayer.FeatureSource.GetFeaturesNearestTo(targetPointShape, GeographyUnit.Meter, 1, ReturningColumnsType.AllColumns);
>                 toSnapShapeFeatureLayer.Close();

>                 if (toSnapInMemoryFeatures.Count == 1)
>                 {
>                     PointShape nearestPointInLayer = (PointShape)toSnapInMemoryFeatures[0].GetShape();

>                     float screenDistance = ExtentHelper.GetScreenDistanceBetweenTwoWorldPoints(currentWorldExtent, nearestPointInLayer, targetPointShape, mapWidth, mapHeight);

>                     if (screenDistance <= tolerance && screenDistance < nearestDistance)
>                     {
>                         toSnapPointShape = nearestPointInLayer;
>                         nearestDistance = screenDistance;
>                     }
>                 }
>             }

>             if (toSnapPointShape != null)
>             {
>                 return new PointShape(toSnapPointShape.X, toSnapPointShape.Y);
>             }

>             return null;
>         }

>         //Function to find if dragged control point is within the tolerance of a vertex of layer in world coordinates.
>         private PointShape FindNearestSnappingPoint(PointShape targetPointShape)
>         {
>             PointShape toSnapPointShape = null;
>             double nearestDistance = double.MaxValue;

>             foreach (var toSnapShapeFeatureLayer in ToSnapShapeFeatureLayerList)
>             {
>                 toSnapShapeFeatureLayer.Open();
>                 Collection<Feature> toSnapInMemoryFeatures = toSnapShapeFeatureLayer.FeatureSource.GetFeaturesNearestTo(targetPointShape, GeographyUnit.Meter, 1, ReturningColumnsType.AllColumns);
>                 toSnapShapeFeatureLayer.Close();

>                 if (toSnapInMemoryFeatures.Count == 1)
>                 {
>                     PointShape nearestPointInLayer = (PointShape)toSnapInMemoryFeatures[0].GetShape();

>                     double Distance = nearestPointInLayer.GetDistanceTo(targetPointShape, geographyUnit, toleranceUnit);

>                     if (Distance <= tolerance && Distance < nearestDistance)
>                     {
>                         toSnapPointShape = nearestPointInLayer;
>                         nearestDistance = Distance;
>                     }
>                 }
>             }


>             if (toSnapPointShape != null)
>             {
>                 return new PointShape(toSnapPointShape.X, toSnapPointShape.Y);
>             }

>             return null;
>         }



>         protected override void DrawTileCore(GeoCanvas geoCanvas)
>         {
>             //Sets the geography Unit used in FindNearestSnappingPoint function
>             geographyUnit = geoCanvas.MapUnit;
>             currentWorldExtent = geoCanvas.CurrentWorldExtent;
>             mapWidth = geoCanvas.Width;
>             mapHeight = geoCanvas.Height;

>             base.DrawTileCore(geoCanvas);
>         }
>     }

Hi Yohan,

I tested your code, it looks the DrawTileCore works well.

I guess you forget to assign the custom layer to map, for example:

 map.TrackOverlay = new SnapToLayerTrackInteractiveOverlay();

Regards,

Ethan

Hi Ethan,

I have assigned the custom layer to the map.

SnapToLayerTrackInteractiveOverlay snapToLayerTrackInteractiveOverlay = new SnapToLayerTrackInteractiveOverlay();

        //using Screen (Pixel) coordinates for tolerance.
        snapToLayerTrackInteractiveOverlay.ToleranceType = ToleranceCoordinates.Screen;
        snapToLayerTrackInteractiveOverlay.Tolerance = 25;

        //Example using World coordinates for tolerance.
        //snapToLayerTrackInteractiveOverlay.ToleranceType = ToleranceCoordinates.World;
        //snapToLayerTrackInteractiveOverlay.Tolerance = 150;
        //snapToLayerTrackInteractiveOverlay.ToleranceUnit = DistanceUnit.Meter;       

        snapToLayerTrackInteractiveOverlay.EnableSnapping = false;
        snapToLayerTrackInteractiveOverlay.ToSnapShapeFeatureLayerList.Clear();

        mainMap.TrackOverlay = snapToLayerTrackInteractiveOverlay;

I’ve figured out the problem. When TrackMode = TrackMode.StraightLine , OnVertexAdding is called before DrawTileCore when the user adds a new line.
Is there a work around for this?

Another problem I found is that only the first point of the line is snapped. The end point do not snap to the nearest point.
Maybe we also need to add the snapping function to OnVertexAdded ?

Hi Yohan,

That’s because the behavior of StraightLine and Line is different, I think you can try to use the MouseMoveCore function, and get the values from interactionArguments parameter.

For example:

    protected override InteractiveResult MouseMoveCore(InteractionArguments interactionArguments)
    {
        geographyUnit = interactionArguments.MapUnit;
        currentWorldExtent = interactionArguments.CurrentExtent;
        mapWidth = interactionArguments.MapWidth;
        mapHeight = interactionArguments.MapHeight;
        
        return base.MouseMoveCore(interactionArguments);
    }

And if the end point don’t snap you can try the event OnTrackEnded or some other event.

Wish that’s helpful.

Regards,

Ethan

Hi Ethan,

Thank you for the reply.
I’ve managed to get the snapping working by moving the snapping logic to OnTrackEnding event.

Hi Yohan,

I am glad to hear that works and thanks for your update.

Regards,

Ethan