Custom Draw Tool

2596
8
03-11-2011 08:15 AM
EduardLucic
New Contributor
Hi,

I wanted to build a draw tool similar to the one in ArcGIS Explorer -- one that allows you to draw all kinds of shapes (not just lines and hand drawn polygons). In particular, to start I would need a Circle tool. I found this thread (http://forums.esri.com/Thread.asp?c=213&f=2455&t=289039) that shows an implementation of how a circle tool would be done. In the end, it all comes down to creating predefined polygons on a graphics layer that you add to draw and remove once the draw is finished. I was wondering if that's the best approach or if someone had already tried to create something similar some other way.

Thanks,
Ed
0 Kudos
8 Replies
JenniferNery
Esri Regular Contributor
0 Kudos
EduardLucic
New Contributor
Thanks Jennifer, I'll take a look. ArcGIS Explorer uses the Draw object in v2.2 to draw the shapes or is it something custom? When will the final be out? For the product I'm working on, we prefer not to use beta versions. So if I wanted to achieve the same with v2.1, are there any recommendations/guidelines?

Ed
0 Kudos
JenniferNery
Esri Regular Contributor
This is a sample that use Draw: http://help.arcgis.com/en/webapi/silverlight/samples/start.htm#DrawGraphics

I don't know when v2.2 Final will be released. If you need to use v2.1, you may need to create a wrapper class for Draw to include another mode for shapes. I think you can use DrawMode.Rectangle so that the envelope that will contain the shape is drawn. When draw is complete, you can scale your shaped geometry to the size of the envelope. The resulting geometry should still be a polygon but its vertices (PointCollection) will define the shape.
0 Kudos
EduardLucic
New Contributor
But if I use the DrawMode.Rectangle, the visual feedback will be a rectangle, right? If I wanted the visual feedback to be an ellipse or an arrow, would I have to mimic what's done in the DrawCircle class I mentioned in my previous post? The sample Measure Action in http://help.arcgis.com/en/webapi/silverlight/samples/start.htm#UtilityActions also does something along the lines of what I would need. Is it possible to get a general idea of how such a tool is implemented?

Ed
0 Kudos
JenniferNery
Esri Regular Contributor
Yes, that is correct. The shape is not displayed as you draw, what you get instead is a rectangle. It would be up to your wrapper class to update this shape.

The draw experience for the user is not like it would be in v2.2: http://help.arcgis.com/en/webapi/silverlight/2.2/samples/start.htm#DrawGraphics

You may use the Circle class from your previous post but this is also not interactive, it is meant for defining geometry given Center and Radius.
0 Kudos
EduardLucic
New Contributor
Yes it's true that Circle class doesn't handle interaction, but a later reply by Fred Spataro gives the source for the DrawCircle class that uses a GraphicsLayer and MouseUp, MouseDown tracking.

Later, Morten was saying to take a look at the Measure Radius tool in the Silverlight Showcase Application, but that doesn't show how it's implemented. Well, I guess it's close to the DrawCircle class, but I wanted to get some insight from those working on the Silverlight API so I don't go completely off track if there's a better way of doing things 😉

Ed
0 Kudos
JustinCornell
New Contributor III
Because 2.2 is not out yet I have extended the Draw class to create the circle just like it does with rectangle.  The only thing is you have to call StartDrawCircle() add the eventing for drawing the circle.  You will have to call EndDrawCircle to remove the events from the map.  It will fire the DrawCompleted event on the base class when you MouseLeftButtonUp event happens to complete the circle.  Enjoy.

    public class MarkupModel : Draw
    {
        #region DrawCircle
        private GraphicsLayer _circleDrawingLayer;
        private MapPoint _circleCenter;

        public void StartDrawCircle()
        {
            //end any previous circle drawing
            EndDrawCircle();

            if (_circleDrawingLayer == null)
                _circleDrawingLayer = new GraphicsLayer();

            Map.Layers.Add(_circleDrawingLayer);

            Map.MouseLeftButtonDown += Map_MouseLeftButtonDown;
            Map.MouseLeftButtonUp += Map_MouseLeftButtonUp;
            Map.MouseMove += Map_MouseMove;
        }

        public void EndDrawCircle()
        {
            Map.Layers.Remove(_circleDrawingLayer);

            Map.MouseLeftButtonDown -= Map_MouseLeftButtonDown;
            Map.MouseMove -= Map_MouseMove;
            Map.MouseLeftButtonUp -= Map_MouseLeftButtonUp;
        }

        public Graphic CreateCircle(MapPoint center, double radius)
        {
            int numPoints = 50;

            //make sure the radius is a positive number
            radius = Math.Abs(radius);  
            
            PointCollection ring = new PointCollection();
            

            // 2PI = 360 degrees, +1 so that the end points meet
            for (double i = 0; i < numPoints + 1; i++)
            {
                double theta = Math.PI * (i / (numPoints / 2));
                var vertexLat = center.Y + (radius * Math.Sin(theta));
                var vertexLng = center.X + (radius * Math.Cos(theta));
                var point = new MapPoint(vertexLng, vertexLat);
                ring.Add(point);
            }

            return new Graphic()
            {
                Geometry = new Polygon() { Rings = new System.Collections.ObjectModel.ObservableCollection<PointCollection>() { ring } },
                Symbol = base.FillSymbol
            };
        }

        #region Circle Event Handlers
        void Map_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            _circleCenter = Map.ScreenToMap(e.GetPosition(Map));
            e.Handled = true; //need to do this or the map will allow drag
        }

        void Map_MouseMove(object sender, MouseEventArgs e)
        {
            if (_circleCenter == null)
                return;

            var radiusPoint = Map.ScreenToMap(e.GetPosition(Map));
            if (radiusPoint != null)
            {
                //generate new circle points
                var radius = _circleCenter.DistanceTo(radiusPoint);
                var graphic = CreateCircle(_circleCenter, radius);

                //don't replace the graphic each time because it will lead to flickering, just replace geometry
                if (_circleDrawingLayer.Graphics.Count > 0)
                    _circleDrawingLayer.Graphics[0].Geometry = graphic.Geometry;
                else
                    _circleDrawingLayer.Graphics.Add(graphic);
            }
        }

        void Map_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            _circleCenter = null;
            if (_circleDrawingLayer == null || _circleDrawingLayer.Graphics.Count == 0)
                return;

            //only call the completed event when the polygon valid
            var polyGraphic = _circleDrawingLayer.Graphics.FirstOrDefault();
            if (polyGraphic != null && polyGraphic.Geometry.Extent.Width > 0)
            {
                DrawMode = ESRI.ArcGIS.Client.DrawMode.Polygon; //set the draw mode to eventing will work
                var poly = polyGraphic.Geometry as ESRI.ArcGIS.Client.Geometry.Polygon;
                foreach (var point in poly.Rings[0])
                {
                    AddVertex(point);
                }
                CompleteDraw();
                DrawMode = ESRI.ArcGIS.Client.DrawMode.None;  //turn off the draw mode, it never happened
            }

            _circleDrawingLayer.ClearGraphics();
        }
        #endregion Circle Event Handlers

        #endregion DrawCircle

    }


You will need this extension function that I created to get the distance between two points
        public static double DistanceTo(this MapPoint point1, MapPoint point2)
        {
            var a = (double)(point2.X - point1.X);
            var b = (double)(point2.Y - point1.Y);

            return Math.Sqrt(a * a + b * b);
        }
0 Kudos
EduardLucic
New Contributor
Thanks for your sample Justin 🙂

Ed
0 Kudos