DotSpatial: Convert a Polygon Feature to System.Drawing.Region

2.3k views Asked by At

I want to paint a Polygon region with a color.

I know I can use Symbolizer to do this but I want that region to blink (change it's color in a timer) and using symbolizer seems to be slow for this purpose.

I'm already using Map.OnPaint event to draw a colored image of a point (in a PointLayer).

So how can I Convert a Polygon Feature (in a PolygonLayer) to System.Drawing.Region so I can use methods in Graphics class to paint that region?

Thanks in advance.

1

There are 1 answers

2
Ted On

Here is a demonstration. This is polygon specific, but should give you an idea of how to convert a polygon into a GraphicsPath, which you can then use to fill or draw with the Brush/Pen of your choice on the graphics object. This was only tested on a relatively simple shape, but it uses essentially the same drawing code as the MapPolygonLayer, so it should be pretty accurate. If you are in edit mode, you might want to get the vertices directly from the feature, rather than the way I got them.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using DotSpatial.Controls;
using DotSpatial.Data;
using DotSpatial.Symbology;

namespace WindowsFormsApplication3
{
    public partial class Form1 : Form
    {
        /// <summary>
        /// Timer for controlling flashing
        /// </summary>
        Timer timer1;

        /// <summary>
        /// True if the timer is actively flashing
        /// </summary>
        private bool timerEnabled;

        /// <summary>
        /// True if the 0 index shape should be colored yellow
        /// </summary>
        private bool highlighted;

        /// <summary>
        /// The layer with the polygon you wish to show flashing
        /// </summary>
        IMapFeatureLayer layer;

        /// <summary>
        /// Form constructor, initialize timer and paint event handler
        /// </summary>
        public Form1()
        {
            InitializeComponent();
            map1.Paint += map1_Paint;
            timer1 = new Timer();
            timer1.Interval = 1000;
            timer1.Tick += timer1_Tick;
        }

        /// <summary>
        /// Occurs when the timer ticks.  This changes the highlighting and forces the map to refresh itself.  This will not
        /// redraw all the other features, since those are cached.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void timer1_Tick(object sender, EventArgs e)
        {
            highlighted = !highlighted;
            map1.Invalidate();
        }

        /// <summary>
        /// Occurs when the map is painting.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void map1_Paint(object sender, PaintEventArgs e)
        {
            MapPolygonLayer pg = layer as MapPolygonLayer;

            // If the shape is highlighted, draw it here.
            if (pg != null && highlighted)
            {
                System.Drawing.Drawing2D.GraphicsPath borderPath = PolygonToGraphicsPath(e, pg, 0);
                e.Graphics.FillPath(Brushes.Yellow, borderPath);
                e.Graphics.DrawPath(Pens.Cyan, borderPath);
            }
        }

        /// <summary>
        /// Converts the polygon at index into a new shape.
        /// </summary>
        /// <param name="e">The paint event arguments from the paint event.</param>
        /// <param name="pg">The polygon layer</param>
        /// <param name="index">The integer zero based index of the shape to get</param>
        /// <returns></returns>
        private System.Drawing.Drawing2D.GraphicsPath PolygonToGraphicsPath(PaintEventArgs e, MapPolygonLayer pg, int index)
        {
            System.Drawing.Drawing2D.GraphicsPath borderPath = new System.Drawing.Drawing2D.GraphicsPath();

            // This controls the relationship between pixel and map coordinates
            MapArgs args = new MapArgs(map1.ClientRectangle, map1.PixelToProj(map1.ClientRectangle), e.Graphics);

            // These variables help define the offsets necessary for drawing from the args.
            double minX = args.MinX;
            double maxY = args.MaxY;
            double dx = args.Dx;
            double dy = args.Dy;

            // SoutherlandHodgman clipping is about preventing exceptions from points outside the bounds, while still keeping
            // the geometry the same inside the bounds.
            SoutherlandHodgman shClip = new SoutherlandHodgman(map1.ClientRectangle);
            ShapeRange shpx = pg.DataSet.ShapeIndices[index];

            // interleaved x/y values of all the shapes on the layer.
            double[] vertices = pg.DataSet.Vertex;
            for (int prt = 0; prt < shpx.Parts.Count; prt++)
            {
                PartRange prtx = shpx.Parts[prt];
                int start = prtx.StartIndex;
                int end = prtx.EndIndex;
                List<double[]> points = new List<double[]>();

                for (int i = start; i <= end; i++)
                {
                    double[] pt = new double[2];
                    pt[0] = (vertices[i * 2] - minX) * dx;
                    pt[1] = (maxY - vertices[i * 2 + 1]) * dy;
                    points.Add(pt);
                }
                // Actually do the SoutherlandHodgman clipping
                if (shClip != null)
                {
                    points = shClip.Clip(points);
                }
                // Prevent a lot of unnecessary drawing of duplicate pixels when zoomed out.
                List<Point> intPoints = DuplicationPreventer.Clean(points);
                if (intPoints.Count < 2)
                {
                    continue;
                }

                borderPath.StartFigure();
                Point[] pointArray = intPoints.ToArray();
                borderPath.AddLines(pointArray);
            }
            return borderPath;
        }

        /// <summary>
        /// When the first button is clicked, this will prompt the user to open a shapefile, and add it to the map.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click(object sender, EventArgs e)
        {
            layer = map1.AddFeatureLayer();

        }

        /// <summary>
        /// when the second button is clicked the feature should start flashing.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button2_Click(object sender, EventArgs e)
        {
            if (timerEnabled)
            {
                timer1.Stop();
                timerEnabled = false;
            }
            else
            {
                timer1.Start();
                timerEnabled = true;
            }

        }
    }
}