Snap to IGraphicsLayer

2728
5
Jump to solution
07-29-2015 10:33 PM
MarkMindlin
Occasional Contributor III

Hi there,

I am using ISnappingEnvironment for a snap to a point feature in a feature layer in ArcMap 10.3. And I draw a IMarkerElement(IMarkerSymbol) at the point of successfull snap(first position). If user clicks to far from a point (not with actual tolerance of the snap) I also draw another IMarkerElement(IMarkerSymbol) at this second position.

I am wondering how to snap to graphic that I created at the second position. Could IGraphicsLayer help in this case? I guess that snap could work with feature layers and (probably) with graphics layers?

I tried to add a graphic layer to the map, but I don't see it included in the snap:

//create IMarkerElement pMarkerElement here

...

IMap pMap = pActiveView.FocusMap;

ICompositeGraphicsLayer compositeGraphicsLayer = (ICompositeGraphicsLayer)pMap.BasicGraphicsLayer;

IGraphicsLayer graphicsLayer = null;

try 

{graphicsLayer = compositeGraphicsLayer.FindLayer(strGraphicLayerName);}

catch (Exception ex) { }

if (graphicsLayer == null)

{

graphicsLayer = compositeGraphicsLayer.AddLayer(strGraphicLayerName, null);

}

graphicsLayer.Activate(pActiveView.ScreenDisplay);

pMap.ActiveGraphicsLayer = (ILayer)graphicsLayer;

pGraphicsContainer = graphicsLayer as IGraphicsContainer;

pGraphicsContainer.AddElement(pMarkerElement, 0);

0 Kudos
1 Solution

Accepted Solutions
MarkMindlin
Occasional Contributor III
0 Kudos
5 Replies
MarkMindlin
Occasional Contributor III
0 Kudos
SeanJones
Esri Regular Contributor

Programmatically you can snap to any geometries you place in the snapping cache. This is briefly described in the Snapping to other geometries topic in Working with the ArcGIS snapping environment.

To snap to graphics you would need to get the geometry of the graphics you are interested in and place it in the cache, usually updating the cache on a screen refresh or map extent changed (OnAfterDraw). The Construction Guides Addin, source not included, uses this technique.

I encourage you to vote for that idea to include snapping to graphics as a core ArcMap feature.

MarkMindlin
Occasional Contributor III

Thank you Sean!

Could you get into more details how to update "the cache on a screen refresh or map extent changed (OnAfterDraw)."

Could you share some code?

Especially ESRI says that

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.

0 Kudos
SeanJones
Esri Regular Contributor

Sure,

In the context of that construction guide sample it was easier to place the graphics that you wanted to both see and snap to in an IGeometryBag.

    private void AddConstructionGuideGeometry(IGeometry geometry)
    {
      if (geometry == null)
        throw new ArgumentNullException();

      if (m_guideGeomBag == null)
        m_guideGeomBag = new GeometryBagClass();
      if (((IGeometryCollection)m_guideGeomBag).GeometryCount == 0)
        m_guideGeomBag.SpatialReference = geometry.SpatialReference;
     
      object missing = Type.Missing;
      ((IGeometryCollection)m_guideGeomBag).AddGeometry(geometry, ref missing, ref missing);

      UpdateGuideSnapCache();

      IInvalidArea3 invalidArea = new InvalidAreaClass();
      invalidArea.Display = ScreenDisplay;
      invalidArea.Add(geometry);
      invalidArea.InvalidateEx((short)esriScreenCache.esriAllScreenCaches, 2);
    }

The snap cache is updated with the contents of the bag and referenced by a token. Technically you can add individual geometries and track them with their own tokens but that just complicates things.

    private void UpdateGuideSnapCache()

    {

      if (GuideSnapToken == NullGuideSnapToken && m_guideGeomBag != null)

        GuideSnapToken = SnappingEnvironment.PointSnapper.CacheShapes(m_guideGeomBag, "Construction Guide");

      else

        SnappingEnvironment.PointSnapper.UpdateCachedShapes(GuideSnapToken, m_guideGeomBag);

    }

You can draw the contents of the bag when the screen refreshes using an IActiveView AfterDraw event.

    private void DrawGuides()
    {
      if (m_guideGeomBag == null)
        return;

      double refScale = ScreenDisplay.DisplayTransformation.ReferenceScale;
      ScreenDisplay.DisplayTransformation.ReferenceScale = 0.0;
      double resolution = ScreenDisplay.DisplayTransformation.Resolution;

      IGeometryCollection geomColl = (IGeometryCollection)m_guideGeomBag;
      for (int i = 0; i < geomColl.GeometryCount; i++)
      {
        IGeometry geom = geomColl.get_Geometry(i);
        switch (geom.GeometryType)
        {
          case esriGeometryType.esriGeometryPoint:
            ScreenDisplay.SetSymbol((ISymbol)GuideMarkerSymbol);
            ScreenDisplay.DrawPoint(geom);
            break;
          case esriGeometryType.esriGeometryPolyline:
            ScreenDisplay.SetSymbol((ISymbol)GuideLineSymbol);
            ScreenDisplay.DrawPolyline(geom);
            break;
          default:
            break;
        }
      }

      ScreenDisplay.DisplayTransformation.ReferenceScale = refScale;
    }

In this sample the snapping cache isn't being updated on extent change, but if you had thousands of custom geometries to snap to, you would add/remove geometries to the cache based on screen extent.

To clear the snap cache and stop the drawing you can remove the geometries from the cache by their token and empty the bag.

    internal void RemoveConstructionGuidesFromCache()
    {
      if (GuideSnapToken != NullGuideSnapToken)
      {
        if (SnappingEnvironment.PointSnapper != null)
          SnappingEnvironment.PointSnapper.RemoveCachedShapes(GuideSnapToken);
        GuideSnapToken = NullGuideSnapToken;
      }

      if (m_guideGeomBag == null)
        return;
     
      if (ScreenDisplay != null)
      {
        IInvalidArea3 invalidArea = new InvalidAreaClass();
        invalidArea.Display = ScreenDisplay;

        IGeometryCollection geomColl = m_guideGeomBag as IGeometryCollection;
        for (int i = 0; i < geomColl.GeometryCount; i++)
          invalidArea.Add(geomColl.get_Geometry(i));
     
        invalidArea.InvalidateEx((short)esriScreenCache.esriAllScreenCaches, 2);
      }

      m_guideGeomBag.SetEmpty();
      m_guideGeomBag = null;
    }

Hope this helps. We'll clean up the whole source and publish it someday.

MarkMindlin
Occasional Contributor III

Thanks Sean! Great example!

0 Kudos