Register  |  Login
ThinkGeo - GPS Tracking and Mapping Solutions  |  Home  |  Cygnus Track  |   Code Community

Discussion Forums

The online community for users of Map Suite GIS components

RSS Feed Available AddThis - Bookmarking and Sharing Button Printer Friendly

PrevPrev NextNext

Sample Code: Writing a Donut Point Style

Posted by ThinkGeo on 05-21-2009 10:34 AM

This post will focus on a request we got from a customer, who wanted to know how to draw a donut to represent a point. The effect is that a point is represented by two concentric circles. The interior of the smaller circle is transparent, the rim of the two circles is drawn by a pen and the space between them is filled with a brush.

I wanted to share the implementation with you and show you just how easy it is to create your own styles with Map Suite that accomplish just about anything. Following are two kinds of DonutPointStyle. The first is a general one that can be used with any GeoCanvas, such as GdiPlusGeoCanvas, WPFGeoCanvas, or even a GeoCanvas you create yourself. The second one is specific for GDIPlusGeoCanvas; it is easier to implement but needs the System.Drawing assembly.

General DonutPointStyle

Here is the general DonutPointStyle. The API is fairly straightforward; however, there are a few properties I want to expand on.

OuterRadius:
This is the radius of the outer circle. It is in screen coordinate and has the type of Single.

InnerRadius:
This is the radius of the inner circle. It is in screen coordinate and has the type of Single.

FillBrush:
This is the GeoBrush to fill the space between the two concentric circles.

OuterPen:
This is the GeoPen for the rim of the donut, including both the outer rim and the inner one.

VertexCountInQuarter
This specifies how many vertices will make up a quarter of a circle in a donut. The bigger this value is, the more accurate the donut will be, but the longer the drawing will take. The default value is 10.

Source Code: General DonutPointStyle

   
   // This class allows you to draw a donut for each point on the canvas.
    class DonutPointStyle : PointStyle
    {
        // A default constructor that just calls the main constructor.
        public DonutPointStyle()
            : this(0, 0, new GeoSolidBrush(), new GeoPen(GeoColor.SimpleColors.Transparent), 10)
        { }

        // A constructor with fewer parameters that calls the main constructor.
        public DonutPointStyle(float outerRadius, float innerRadius, GeoBrush fillBrush)
            : this(outerRadius, innerRadius, fillBrush, new GeoPen(GeoColor.SimpleColors.Transparent), 10)
        { }

        // A constructor with fewer parameters that calls the main constructor.
        public DonutPointStyle(float outerRadius, float innerRadius, GeoBrush fillBrush, GeoPen outerPen)
            : this(outerRadius, innerRadius, fillBrush, outerPen, 10)
        { }

        // The main constructor, it allows you to set all of the options available.
        public DonutPointStyle(float outerRadius, float innerRadius, GeoBrush fillBrush, GeoPen outerPen, int vertexCountInQuarter)
            : base()
        {
            this.OuterRadius = outerRadius;
            this.InnerRadius = innerRadius;
            this.FillBrush = fillBrush;
            this.OuterPen = outerPen;
            this.VertexCountInQuarter = vertexCountInQuarter;
        }

        // This is the outer radius of the donut in screen coordinate. 
        public float OuterRadius
        {
            get;
            set;
        }

        // This is the inner radius of the donut in screen coordinate. 
        public float InnerRadius
        {
            get;
            set;
        }

        // This is the brush used to fill the donut.
        public GeoBrush FillBrush
        {
            get;
            set;
        }

        // This is the pen for the rim of the donut.
        public GeoPen OuterPen
        {
            get;
            set;
        }

        // This specifies how many vertices will make up a quarter of a circle in a donut.
        public int VertexCountInQuarter
        {
            get;
            set;
        }

        // This is the method we need to override.
        protected override void DrawCore(System.Collections.Generic.IEnumerable<Feature> features, GeoCanvas canvas, System.Collections.ObjectModel.Collection<SimpleCandidate> labelsInThisLayer, System.Collections.ObjectModel.Collection<SimpleCandidate> labelsInAllLayers)
        {
            foreach (Feature feature in features)
            {
                ScreenPointF screenPointF = ExtentHelper.ToScreenCoordinate(canvas.CurrentWorldExtent, feature, canvas.Width, canvas.Height);
                IEnumerable<ScreenPointF[]> screenPointFs = GetVerticesForADonut(screenPointF, OuterRadius, InnerRadius, VertexCountInQuarter);
                canvas.DrawArea(screenPointFs, OuterPen, FillBrush, DrawingLevel.LevelFour, this.XOffsetInPixel, this.YOffsetInPixel, PenBrushDrawingOrder.BrushFirst);
            }
        }

        // This method gets the vertices of a donut, which is composed of two circles.
        private IEnumerable<ScreenPointF[]> GetVerticesForADonut(ScreenPointF center, float outerRadius, float innerRadius, int vertexCountInQuarter)
        {
            List<ScreenPointF> outerScreenPointFs = GetVerticesForACircle(center, outerRadius, vertexCountInQuarter);
            List<ScreenPointF> innerScreenPointFs = GetVerticesForACircle(center, innerRadius, vertexCountInQuarter);
            outerScreenPointFs.Reverse();
            return new ScreenPointF[][] { outerScreenPointFs.ToArray(), innerScreenPointFs.ToArray() };
        }

        // This method gets the vertices of a circle.
        private List<ScreenPointF> GetVerticesForACircle(ScreenPointF center, float radius, int vertexCountInQuarter)
        {
            double deltaDegree = (double)90 / (vertexCountInQuarter + 1);
            List<ScreenPointF> screenPointFs = new List<ScreenPointF>();

            // +X
            screenPointFs.Add(new ScreenPointF(center.X + radius, center.Y));

            // Phase I
            for (double currentDegree = deltaDegree; currentDegree < 90; currentDegree += deltaDegree)
            {
                float x = (float)Math.Cos(currentDegree / 180 * Math.PI) * radius + center.X;
                float y = (float)Math.Sin(currentDegree / 180 * Math.PI) * radius + center.Y;
                screenPointFs.Add(new ScreenPointF(x, y));
            }
            // +Y
            screenPointFs.Add(new ScreenPointF(center.X, center.Y + radius));

            // Phase II
            for (int i = vertexCountInQuarter; i > 0; i--)
            {
                screenPointFs.Add(new ScreenPointF(2 * center.X - screenPointFs[i].X, screenPointFs[i].Y));
            }

            // -X
            screenPointFs.Add(new ScreenPointF(center.X - radius, center.Y));

            // Phase III
            for (int i = 1; i <= vertexCountInQuarter; i++)
            {
                screenPointFs.Add(new ScreenPointF(2 * center.X - screenPointFs[i].X, 2 * center.Y - screenPointFs[i].Y));
            }

            // -Y
            screenPointFs.Add(new ScreenPointF(center.X, center.Y - radius));

            // Phase IV
            for (int i = vertexCountInQuarter; i > 0; i--)
            {
                screenPointFs.Add(new ScreenPointF(screenPointFs[i].X, 2 * center.Y - screenPointFs[i].Y));
            }

            // +X
            screenPointFs.Add(new ScreenPointF(center.X + radius, center.Y));

            return screenPointFs;
        }
    }

GDIPlusGeoCanvas-Specific DonutPointStyle

Now here is the GdiPlusDonutPointStyle. The API is quite similar to the general one, but has the following two differences:

  1. It doesn't have the property VertexCountInQuarter. Instead, it uses SmoothingMode to define the accuracy of the drawn donut.
  2. It still has FillBrush and OuterPen, but instead of a GeoBrush and a GeoPen, it uses a GDI+ Brush and a GDI+ Pen, respectively.

Have a look at the source code below. If you have any questions or suggestions, I would be happy to hear them. We do not have this in our SDK as this code isn't ready for integration (it does not have formal documentation, test cases, parameter validation etc.). We will, however, be adding this in a future release, but until then, enjoy the code.

Source Code: GdiPlusDonutPointStyle


  // This class allows you to draw a donut for each point on the canvas.
    class GdiPlusDonutPointStyle : PointStyle
    {
        // A default constructor that just calls the main constructor.
        public GdiPlusDonutPointStyle()
            : this(0, 0, new SolidBrush(Color.Transparent), new Pen(Color.Transparent))
        { }

        // A constructor with fewer parameters that calls the main constructor.
        public GdiPlusDonutPointStyle(float outerRadius, float innerRadius, Brush fillBrush)
            : this(outerRadius, innerRadius, fillBrush, new Pen(Color.Transparent))
        { }

        // The main constructor, it allows you to set all of the options available.
        public GdiPlusDonutPointStyle(float outerRadius, float innerRadius, Brush fillBrush, Pen outerPen)
            : base()
        {
            this.OuterRadius = outerRadius;
            this.InnerRadius = innerRadius;
            this.FillBrush = fillBrush;
            this.OuterPen = outerPen;
            this.SmoothingMode = SmoothingMode.HighQuality;
        }

        // This is the outer radius of the donut in screen coordinate. 
        public float OuterRadius
        {
            get;
            set;
        }

        // This is the inner radius of the donut in screen coordinate. 
        public float InnerRadius
        {
            get;
            set;
        }

        // This is the brush used to fill the donut.
        public Brush FillBrush
        {
            get;
            set;
        }

        // This is the pen for the rim of the donut.
        public Pen OuterPen
        {
            get;
            set;
        }

        // This specifies the smoothing mode of the donut. It defaults to high quality.
        public SmoothingMode SmoothingMode
        {
            get;
            set;
        }

        // This is the method we need to override.
        protected override void DrawCore(IEnumerable<Feature> features, GeoCanvas canvas, Collection<SimpleCandidate> labelsInThisLayer, Collection<SimpleCandidate> labelsInAllLayers)
        {
            // If the canvas is a GDI+ Canvas, its NativeImage should be a bitmap.
            Bitmap bitMap = (Bitmap)canvas.NativeImage;
            Graphics g = Graphics.FromImage(bitMap);
            g.SmoothingMode = SmoothingMode;

            foreach (Feature feature in features)
            {
                ScreenPointF screenPointF = ExtentHelper.ToScreenCoordinate(canvas.CurrentWorldExtent, feature, canvas.Width, canvas.Height);
                GraphicsPath graphicPath = new GraphicsPath();
                graphicPath.AddEllipse(screenPointF.X - OuterRadius, screenPointF.Y - OuterRadius, OuterRadius * 2, OuterRadius * 2);
                graphicPath.AddEllipse(screenPointF.X - InnerRadius, screenPointF.Y - InnerRadius, InnerRadius * 2, InnerRadius * 2);

                g.FillPath(FillBrush, graphicPath);
                g.DrawPath(OuterPen, graphicPath);
                graphicPath.Dispose();
            }
            g.Dispose();
        }
    }

0 Comments

You are not authorized to post a reply.
Active Forums 4.1