[GIS] Determining if mouse click falls within polygon using ArcObjects

arcobjectscnetpoint-in-polygon

I have a polygon drawn on top of a layer using the graphicsContainer of the current activeView, then I want to be able to show a custom context menu if the user right clicks INSIDE the polygon. So I have to figure out a way to determine if the mouse click falls within the polygon. I have tried to use the IRelationalOperator2 as suggested in this question Does anyone know a function to determine if a Point exists within a polygon?, but I can't seem to get it working properly. Here is what I have so far:

System.Drawing.Point screenMousePoint = System.Windows.Forms.Form.MousePosition;
IPoint screenPoint = new Point();
screenPoint.X = screenMousePoint.X;
screenPoint.Y = screenMousePoint.Y;

IPoint mapPoint = this.controller.GetMapCoordinatesFromScreenCoordinates(screenPoint, activeView);

// this.currentDefinedArea is a polygon
IRelationalOperator2 relation = this.currentDefinedArea as IRelationalOperator2;

if (relation.Contains(mapPoint))
{
    // Show context menu
}

The "GetMapCoordinatesFromScreenCoordinates" method looks like this, and it's one of the snippets that is included in arcobjects:

public IPoint GetMapCoordinatesFromScreenCoordinates(IPoint screenPoint, IActiveView activeView)
    {
        if (screenPoint == null || screenPoint.IsEmpty || activeView == null)
        {
            return null;
        }

        IScreenDisplay screenDisplay = activeView.ScreenDisplay;
        IDisplayTransformation displayTransformation = screenDisplay.DisplayTransformation;

        return displayTransformation.ToMapPoint((System.Int32)screenPoint.X, (System.Int32)screenPoint.Y);
    }

I found out what was causing the above code to fail. Using:

System.Drawing.Point screenMousePoint = System.Windows.Forms.Form.MousePosition;

…gives a different set of X, Y coordinates than using the MouseEventArgs provided by the OnMouseDown method. Don't know why I wasn't using them in the first place…here's how my working code looks:

protected override void OnMouseDown(ESRI.ArcGIS.Desktop.AddIns.Tool.MouseEventArgs arg)
{
    if (arg.Button == System.Windows.Forms.MouseButtons.Right)
    {
        if (this.controller.CurrentDefinedArea != null)
        {
            IPoint screenPoint = new Point();
            screenPoint.X = arg.X;
            screenPoint.Y = arg.Y;

            IPoint mapPoint = this.controller.GetMapCoordinatesFromScreenCoordinates(screenPoint, activeView);

            IRelationalOperator2 relation = this.controller.CurrentDefinedArea as IRelationalOperator2;

            if (relation.Contains(mapPoint))
            {
                this.controller.CreateContextMenu(ArcMap.Application);
            }
        }
    }
}

Best Answer

As Peter Krebs has suggested on mouse down use IDisplayTransformation.ToMapPoint to assign the XY info to a point graphic. You can then use that graphic as a selector against your polygon layer.

public override void OnMouseDown(int Button, int Shift, System.Int32 X, System.Int32 Y)
{
    //MyBase.OnMouseDown(Button, Shift, X, Y)
    try {
        IMxApplication pMxApp = null;
        IActiveView ActiveView = default(IActiveView);

        pMxApp = (IMxApplication)m_App;

        m_MxDoc = (IMxDocument)m_App.Document;
        m_mxDocument = (MxDocument)m_App.Document;
        ActiveView = (IActiveView)(Map)m_MxDoc.FocusMap;

        IMap pMap = default(IMap);
        ISpatialReference pSpatialRef = default(ISpatialReference);

        pMap = m_MxDoc.FocusMap;
        pSpatialRef = pMap.SpatialReference;

        IScreenDisplay m_pDisplay = ActiveView.ScreenDisplay;

        m_pDisplay.StartDrawing(m_pDisplay.hDC, Convert.ToInt16(esriScreenCache.esriNoScreenCache));
        ISimpleMarkerSymbol simpleMarkerSymbol = new SimpleMarkerSymbolClass();

        ISymbol symbol = simpleMarkerSymbol as ISymbol;
        // Dynamic Cast
        m_pDisplay.SetSymbol(symbol);
        IDisplayTransformation displayTransformation = m_pDisplay.DisplayTransformation;

        // X and Y are in device coordinates
        m_Point = displayTransformation.ToMapPoint(X, Y);

        m_pDisplay.DrawPoint(m_Point);
        m_pDisplay.FinishDrawing();

        SelectFeature();

    } catch (Exception ex) {
        MessageBox.Show("Caught an unspecified error in the calling code: " + Constants.vbCrLf + ex.ToString());
    }

}



public void SelectFeature()
{
    //SelectFeature
    try {
        ILayer pLayer = default(ILayer);
        IActiveView pActiveView = default(IActiveView);
        IEnvelope pEnvelope = default(IEnvelope);
        ISpatialFilter pSF = default(ISpatialFilter);
        IEnumLayer pEnumLayer = default(IEnumLayer);

        m_MxDoc = (IMxDocument)m_App.Document;
        m_mxDocument = (MxDocument)m_App.Document;
        m_map = (Map)m_MxDoc.FocusMap;

        pActiveView = (IActiveView)m_map;
        if (m_map.LayerCount == 0)
            return;

        pEnumLayer = m_map.Layers;

        pLayer = pEnumLayer.Next;
        while (!(pLayer == null)) {
            if (pLayer.Name == "My Polygon Layer") {
                m_FLayer = (IFeatureLayer)pLayer;
            } else {
                return;
            }
            pLayer = pEnumLayer.Next;
        }

        m_FSel = (IFeatureSelection)m_FLayer;
        //Expand the points envelope to give better search results
        pEnvelope = m_MxDoc.CurrentLocation.Envelope;
        pEnvelope.Expand(m_MxDoc.SearchTolerance, m_MxDoc.SearchTolerance, false);
        pSF = new SpatialFilter();
        pSF.Geometry = pEnvelope;
        pSF.SpatialRel = esriSpatialRelEnum.esriSpatialRelIntersects;
        pSF.GeometryField = m_FLayer.FeatureClass.ShapeFieldName;

        //Refresh the old selection to erase it
        //pActiveView.PartialRefresh(esriViewDrawPhase.esriViewGeoSelection, Nothing, Nothing)
        //Perform the selection using a point created on mouse down
        m_FSel.SelectFeatures(pSF, esriSelectionResultEnum.esriSelectionResultNew, false);

        ISelectionSet pSelSet2 = default(ISelectionSet);
        // Draw all selected features.
        pSelSet2 = m_FSel.SelectionSet;

        if (pSelSet2.Count == 0) {
            MessageBox.Show("Polygon not selected");
        } else {
            MessageBox.Show("Polygon selected");
        }

        //Refresh again to draw the new selection
        pActiveView.PartialRefresh(esriViewDrawPhase.esriViewGeoSelection, null, null);
    } catch (Exception ex) {
        MessageBox.Show("Caught an unspecified error in the calling code: " + Constants.vbCrLf + ex.ToString());
    }

}
Related Question