Working with the ArcGIS snapping environment


Summary
The ArcGIS snapping environment provides simple interfaces that allow you to enable snapping in your tools for use in the ArcGIS for Desktop and Engine environments. This topic describes these interfaces and contains examples on how to use snapping in your tools.


About the ArcGIS snapping environment

The current version of ArcGIS includes a new snapping environment that can be used by both ArcMap and ArcGIS Engine developers. The snapping environment allows you to enable snapping in all your custom tools, not just those in the editor. The snapping environment is accessed as an extension in both ArcMap and ArcGIS Engine.
ISnappingEnvironment is the primary interface for working with and managing the snapping environment. You can use this interface to control whether snapping is enabled, what snapping types are active, as well as to specify the snapping tolerance and define snapping symbology. It also provides access to the IPointSnapper interface, which contains the snapping logic and evaluates snap candidates against the snapping cache according to the properties defined on ISnappingEnvironment.
While snapping is enabled, visible features are placed in a snapping cache that is managed by IPointSnapper. The snapping cache is maintained and rebuilt as needed when IPointSnapper.Snap is called.
ISnappingResult provides access to the most recent evaluated snap position from IPointSnapper.Snap. This position is used to update the current cursor location.
The ISnappingFeedback interface is used to show and update the snapping symbology and snap tips as defined in the snapping environment.
ArcGIS still supports the older snapping methods in your tools—ISnapEnvironment for Desktop and IEngineSnapEnvironment for Engine—so you need to be aware of which environment your particular tool is using. These methods are still restricted for use in the editor only. For an overview of snapping capabilities in ArcGIS, see Snapping in ArcGIS.

Accessing the snapping environment from ArcMap

To access the snapping environment from ArcMap, use the FindExtensionByCLSID or FindExtensionByName method from the ArcMap application to obtain a reference to the snapping environment. In most cases, this code is inserted into the tool’s OnClick method. See the following code example:
[C#]
IPoint m_CurrentMouseCoords;
ISnappingEnvironment m_SnappingEnv;
IPointSnapper m_Snapper;
ISnappingFeedback m_SnappingFeedback;

//Get the snap environment and initialize the feedback.
m_SnappingEnv = m_application.FindExtensionByName("ESRI Snapping")as
    ISnappingEnvironment;
m_Snapper = m_SnappingEnv.PointSnapper;
m_SnappingFeedback = new SnappingFeedbackClass();
m_SnappingFeedback.Initialize(hook, m_SnappingEnv, true);
[VB.NET]
Dim m_CurrentMouseCoords As IPoint
Dim m_SnappingEnv As ISnappingEnvironment
Dim m_Snapper As IPointSnapper
Dim m_SnappingFeedback As ISnappingFeedback

'Get the snap environment and initialize the feedback.
m_SnappingEnv = TryCast(m_application.FindExtensionByName("ESRI Snapping"), ISnappingEnvironment)
m_Snapper = m_SnappingEnv.PointSnapper
m_SnappingFeedback = New SnappingFeedbackClass()
m_SnappingFeedback.Initialize(hook, m_SnappingEnv, True)

Accessing the snapping environment from ArcGIS Engine

To access the snapping environment from an ArcGIS Engine application, use the IHookHelper2 interface to obtain a reference to IExtensionManager, then obtain a reference to the snapping environment. In most cases, this code would be inserted into the tool’s OnClick method. See the following code example:
[C#]
IExtensionManager extensionManager = m_hookHelper2.ExtensionManager;
if (extensionManager != null)
{
    UID guid = new UIDClass();
    guid.Value = "{E07B4C52-C894-4558-B8D4-D4050018D1DA}"; //Snapping extension.
    IExtension extension = extensionManager.FindExtension(guid);
    m_SnappingEnvironment = extension as ISnappingEnvironment;
}
[VB.NET]
Dim extensionManager As IExtensionManager = m_hookHelper2.ExtensionManager
If Not extensionManager Is Nothing Then
    Dim guid As UID = New UID
    guid.Value = "{E07B4C52-C894-4558-B8D4-D4050018D1DA}" 'Snapping extension.
    Dim extension As IExtension = extensionManager.FindExtension(guid)
    m_SnappingEnvironment = TryCast(extension, ISnappingEnvironment)
End If

Working with the snapping cache

As a developer, you are not responsible for building the cache; the cache is cleared when various events take place in the system. When a call to Snap is made, the cache is evaluated to determine whether or not it is built, and whether or not the existing cache can satisfy the current request (that is, is it within the cache’s extent?). If these conditions are met, the existing cache is used to determine whether a valid snap result exists. If these conditions are not met, the cache is rebuilt based on the snap candidate point passed to the method. If the cache takes too long to build, the build is aborted, and the subsequent Snap request attempts to build the cache using a reduced extent. This is reset when the map extent changes.
The cache is cleared in a number of instances for you; you do not need to account for these situations. The following list describes the cases when ClearCache is called:
  • A layer is added, moved, or deleted from the table of contents.
  • The scale suppression of a layer is changed.
  • The visibility of a layer is changed.
  • A definition query on a layer is changed.
  • A SQL query on a layer is changed.
  • A layer’s data source is changed.
  • A feature is created, updated, or deleted.
  • The extent of the map changes.
  • The active data frame changes.
  • The active view changes (for example, the focus changes from the main map window to a viewer window or you switch from data view to layout view in ArcMap).
  • The snapping tolerance (ISnappingEnvironment.Tolerance) is updated.
  • Intersection snapping is turned on or off.
  • Text snapping is enabled or disabled.
  • CacheShapes, UpdateCachedShapes, or RemoveCachedShapes is called.
  • The ISnappingEnvironment.IgnoreIMSLayers property is changed.
The snapping cache is cleared when any of the following events are fired:
  • IHookHelperEvents.OnHookUpdated
  • IActiveViewEvents.ContentsChanged
  • IActiveViewEvents.FocusMapChanged
  • IObjectClassEvents.OnChange
  • IObjectClassEvents.OnCreate
  • IObjectClassEvents.OnDelete
There is no equivalent method to build the cache explicitly; this is handled internally when a client issues a call to IPointSnapper.Snap, at which time it is determined whether the cache needs to be rebuilt.

Configuring the snapping environment

The ArcGIS snapping environment supports a number of configuration options to further constrain valid snapping results. One of the primary methods of configuring the snapping environment is to change the types of snapping that are active. By default, snapping to points, endpoints, vertices, and edges is enabled.
Use the esriSnappingType enumeration to define which snapping types should be evaluated when IPointSnapper.Snap is called. An individual snapping type can be specified or a combination of snapping types can be used. The combination of snapping types is accomplished using a bit mask. Combinations can be specified using either of the methods in the following code example, although the first method is preferred for code readability:
[C#]
//Specify combination of esriSnappingType enumerations.
snappingEnvironment.SnappingType = esriSnappingType.esriSnappingTypeEdge +
    esriSnappingType.esriSnappingTypeEndpoint;

//Specify bitwise combination for edge and endpoint snapping.
snappingEnvironment.SnappingType = (esriSnappingType)63;
[VB.NET]
'Specify combination of esriSnappingType enumerations.
snappingEnvironment.SnappingType = esriSnappingType.esriSnappingTypeEdge + esriSnappingType.esriSnappingTypeEndpoint

'Specify bitwise combination for edge and endpoint snapping.
snappingEnvironment.SnappingType = DirectCast(63, esriSnappingType)

Snapping priority

Snapping types are evaluated in a fixed order, as follows.
  1. esriSnappingTypePoint
  2. esriSnappingTypeIntersection
  3. esriSnappingTypeEndpoint
  4. esriSnappingTypeVertex
  5. esriSnappingTypeMidpoint
  6. esriSnappingTypeTangent
  7. esriSnappingTypeEdge
SnappingType is an application-level setting.

Additional snapping agents in ArcMap

The snapping environment also supports additional geometries associated with editing, such as the edit sketch. In these cases, the editing snapping types operate against these geometries in the same way as traditional feature geometries. For example, snapping to points must be enabled to snap to topology nodes, and snapping to edges must be enabled to snap to edit sketch edges. Because these snapping agents are only available in ArcMap, accessing the properties is handled through an editor extension rather then ISnappingEnvironment. The following code example illustrates how to access these properties:
[C#]
public void SetTopologyNodeSnapping(bool SetEnabled)
{
    ESRI.ArcGIS.esriSystem.UID editorUID = new ESRI.ArcGIS.esriSystem.UIDClass();
    editorUID.Value = "{F8842F20-BB23-11D0-802B-0000F8037368}"; // esriEditor.Editor
    IEditor editor = ArcMap.Application.FindExtensionByCLSID(editorUID)as IEditor;

    ESRI.ArcGIS.esriSystem.UID editorsnapUID = new ESRI.ArcGIS.esriSystem.UIDClass();
    editorsnapUID.Value = "{810F5661-29A6-42C4-BD24-02E300629C36}"; 
        // esriEditorExt.EditorSnapping
    IEditorSnapping editorSnapping = editor.FindExtension(editorsnapUID)as
        IEditorSnapping;

    editorSnapping.SnapToTopologyNodes = SetEnabled;
}
[VB.NET]
Public Sub SetTopologyNodeSnapping(SetEnabled As Boolean)
    Dim editorUID As ESRI.ArcGIS.esriSystem.UID = New ESRI.ArcGIS.esriSystem.UIDClass()
    editorUID.Value = "{F8842F20-BB23-11D0-802B-0000F8037368}"
    
    ' esriEditor.Editor
    Dim editor As IEditor = TryCast(ArcMap.Application.FindExtensionByCLSID(editorUID), IEditor)
    Dim editorsnapUID As ESRI.ArcGIS.esriSystem.UID = New ESRI.ArcGIS.esriSystem.UIDClass()
    editorsnapUID.Value = "{810F5661-29A6-42C4-BD24-02E300629C36}"
    
    ' esriEditorExt.EditorSnapping
    Dim editorSnapping As IEditorSnapping = TryCast(editor.FindExtension(editorsnapUID), IEditorSnapping)
    editorSnapping.SnapToTopologyNodes = SetEnabled
End Sub

Snapping to other geometries

CacheShapes provides a mechanism to snap to geometries that are not part of a feature associated with a FeatureLayer. For example, snapping to the edit sketch in the ArcMap editor uses this approach and adds the current edit sketch geometry to the snapping cache using this method.
It is your responsibility to ensure that the lifetime and currency of the cached shapes you inject are maintained. Failure to do this results in shapes being orphaned in the cache, which can result in invalid snapping results from a user perspective. 
Two other methods are used to manage these caches, IPointSnapper.UpdateCachedShapes and IPointSnapper.RemoveCachedShapes. Managing these caches uses a token-based approach. When you add geometries to the cache, CacheShapes returns a token that corresponds to those shapes. This token can then be used in the other methods to specify which shapes you want replaced (UpdateCachedShapes) or removed (RemoveCachedShapes).
The Name argument is used when a snap result is returned after snapping to a cached shape. This is returned in lieu of the layer name in the SnapTip. Empty strings are considered valid; however, they should be avoided as they do not provide a real indication of what was snapped to.

Excluding layers

The ArcGIS snapping environment provides a number of methods to tailor the types of layers that are considered snapping candidates. 
  • IMS layers
    The IgnoreIMSLayers property excludes Internet map server (IMS) feature service layers from participating in the snapping cache. In most cases, the time required to build the snapping cache against IMS layers is lengthy, so they are automatically excluded from the snapping cache. In certain situations, the IMS layer response time is reasonable (such as an IMS service on an intranet). Setting this property to false will include these layers in the snapping cache. By default, IgnoreIMSLayers is set to true.
  • Individual layers
    The ExcludedLayers property limits the set of available layers used in snapping to a subset of the layers available in the map. To manage the list of layers available for snapping, you must acquire a reference to the set of excluded layers, passed as an ISet reference. Using the set, you can manage the layers by adding or removing a given layer using ISet.Add, ISet.Remove, or ISet.RemoveAll. See the following code example:
[C#]
ISet excludedLayerSet = new ESRI.ArcGIS.esriSystem.SetClass();
pointSnapper.ExcludedLayers(ref excludedLayerSet);

//Add a layer to be excluded. 
excludedLayerSet.Add(excludedLayer);
pointSnapper.ClearCache();
[VB.NET]
Dim excludedLayerSet As ISet = New ESRI.ArcGIS.esriSystem.SetClass()
pointSnapper.ExcludedLayers(excludedLayerSet)

'Add a layer to be excluded.
excludedLayerSet.Add(excludedLayer)
pointSnapper.ClearCache()
Use ISet.Find to determine whether a layer has already been added to the list. While adding duplicate references is not necessarily problematic, avoid the practice because an excessive number of items in the set can result in reduced performance.
Once the set of layers being excluded is modified, ensure the cache is cleared by calling IPointSnapper.ClearCache.

Adding snapping to a custom tool

If your custom tool calls the editor Sketch tool for input, you do not need to enable snapping in your tool because it is already built-in to the Sketch tool. For all other cases where your tool creates its own geometry, such as a point picker or other screen feedback, you can enable snapping. To use ArcGIS snapping in your tools, follow these steps:
  1. In the tool's OnClick event,
    • Obtain a reference to the snapping extension
    • Acquire references to ISnappingEnvironment and IPointSnapper
    • Initialize the SnappingFeedback (optional)
  2. In the tool's OnMouseMove event,
    • Call IPointSnapper.Snap during mouse move using the current location
    • If a SnapResult is returned:
      • Update the cursor location
      • Update the snapping feedback (if using feedback)
  3. In the tool's Refresh method,
    • Refresh the snapping feedback (if using feedback)
In most cases, the code to access the snapping environment should be included in the tool's OnClick method.
In most tools, snapping occurs during the MouseMove event. In your tool, you can call IPointSnapper.Snap to return an ISnappingResult in this event. If the result is not null, indicating that snapping occurred, you can use the ISnappingResult.Location property to update the cursor position to the snap point and update the snapping feedback, the tips, and the symbology, accordingly.
Your tool can then use this updated cursor position to update screen feedback within the same method or use the snapped point in the tool's MouseDown event to create geometry.
The following code performs the snap during MouseMove:
[C#]
public override void OnMouseMove(int Button, int Shift, int X, int Y)
{
    //Convert the point from pixels to map units.
    m_CurrentMouseCoords = QueryMapPoint(m_screenDisplay, X, Y);

    //Test the location against the snap environment.
    ISnappingResult snapResult = m_Snapper.Snap(m_CurrentMouseCoords);

    //Update the snapping feedback.
    m_SnappingFeedback.Update(snapResult, 0);

    //Update the current location to move the cursor to the snapped location.
    if (snapResult != null)
        m_CurrentMouseCoords = snapResult.Location;

    m_linefeedback.MoveTo(m_CurrentMouseCoords);
}
[VB.NET]
Public Overrides Sub OnMouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Integer, ByVal Y As Integer)
'Convert the point from pixels to map units.
m_CurrentMouseCoords = QueryMapPoint(m_screenDisplay, X, Y)
'Test the location against the snap environment.
Dim snapResult As ISnappingResult = m_Snapper.Snap(m_CurrentMouseCoords)
'Update the snapping feedback.
m_SnappingFeedback.Update(snapResult, 0)
'Update the current location to move the cursor to the snapped location.
If Not snapResult Is Nothing Then
    m_CurrentMouseCoords = snapResult.Location
End If
m_linefeedback.MoveTo(m_CurrentMouseCoords)
End Sub
In the tool's Refresh method, refresh the snapping feedback, including the snap tip. The following code illustrates how to update the snapping feedback:
[C#]
public override void Refresh(int hDC)
{
    //Refresh the previous location of the snap tip.
    if (m_SnappingFeedback != null)
        m_SnappingFeedback.Refresh(hDC);
}
[VB.NET]
Public Overrides Sub Refresh(ByVal hDC As Integer)
'Refresh the previous location of the snap tip.
If Not m_SnappingFeedback Is Nothing Then
    m_SnappingFeedback.Refresh(hDC)
End If
End Sub


See Also:

Snapping in ArcGIS




To use the code in this topic, reference the following assemblies in your Visual Studio project. In the code files, you will need using (C#) or Imports (VB .NET) directives for the corresponding namespaces (given in parenthesis below if different from the assembly name):
Development licensing Deployment licensing
ArcGIS for Desktop Basic ArcGIS for Desktop Basic
ArcGIS for Desktop Standard ArcGIS for Desktop Standard
ArcGIS for Desktop Advanced ArcGIS for Desktop Advanced
Engine Developer Kit Engine