Click or drag to resize

Manipulating Polygons Tutorial

Verizon Connect Logo
Print this page
Learn more about Verizon Connect GeoBase.
Get information about the latest release
Overview

The Telogis.GeoBase.ShapeUI namespace includes the PolygonEntity and ShapeLayer classes. These are used to create and manipulate polygon shapes on a canvas.

In this tutorial we will create an application that demonstrates:

  • creation of a ShapeUI layer using the ShapeLayer class

  • creation of a polygon shape using the PolygonEntity class

  • the manipulation of a polygon's geometry by adding, moving and deleting polygon vertices (the junctures at which two line segments meet)

  • deleting a polygon shape

  • moving shapes up and down in rendering priority (that is, the z-index order in which shapes are rendered on a canvas)

The application we create will draw polygon shapes on the map, and allow the geometry of these shapes to be edited in real-time using a mouse. It will also allow the layer priority of shapes to be adjusted (moving the shape up or down the z-order: useful when shapes overlap), and individual shapes on a layer to be deleted, or an entire layer cleared.

The application will also include a status text field containing details of each action being performed on the selected shape.

Note Note

The complete code for the project example created in this tutorial is included at the bottom of this page.

Creating the Application

Create a new Visual Studio Project (a Windows Forms Application) called 'PolygonEditor'.

Add a reference to geobase.net.dll then switch to the Visual Studio 'Code' view (F7). To make the code simpler and more readable, add the following using directives to the top of the project form (Form1.cs).

C#
using Telogis.GeoBase;
using Telogis.GeoBase.Routing;
using Telogis.GeoBase.ShapeUI;
using Telogis.GeoBase.ShapeUI.State;
using Telogis.GeoBase.Geometry;

Return to the 'Design' view (Shift+F7) and add the following controls to the form:

  • a GeoBase MapCtrl control named mapMain

  • a text box named statusBox

  • a button named deleteButton with the label text 'Delete'

  • a button named editButton with the label text 'Edit'

  • a button named zoomButtonIn with the label text 'Zoom In'

  • a button named zoomButtonOut with the label text 'Zoom Out'

  • a button named priorityButtonUp with the label text 'Move Shape Up'

  • a button named priorityButtonDown with the label text 'Move Shape Down'

  • a button named deleteAllButton with the label text 'Delete All Shapes'

Re-size and move your new controls such that they appear similar to the screenshot below:

shapeUI example

Now add click events to all seven buttons, and a load event to the Form itself (Form1_Load). We will return to these and update them as we progress through this tutorial.

Note Note

The statusBox text box should be set to MultiLine. To do so, click the small arrow in the upper right corner of the text field when viewed in Visual Studio's 'Design' view and select the 'MultiLine' check box option.

Next, return to 'Code' view (F7) and add the following code to the top of the project such that it is global (immediately above the Form1 constructor).

This code will:

  • declare a ShapeLayer instance named myLayer

  • set an initial integer value of 0 to be used as a counter when setting the priority of any new PolygonEntity we create on the ShapeLayer

  • create an inherited MyShapeLayer class containing a #ctor constructor, and retrieve the created ShapeLayer's state using the ShapeLayer class GetDefaultState method and the SelectState constructor

C#
public ShapeLayer myLayer;
public int priority = 0;
public class MyShapeLayer : ShapeLayer {
    public MyShapeLayer(IMap map)
        : base(map) {
    }

    public override IState GetDefaultState() {
        return new SelectState(this, null);
    }
}

Add the following code to the Form1_Load method. This will create a new ShapeLayer using the #ctor constructor (using the IMap object mapMain). We then add events that will be triggered by any changes to the created layer (the ModeChanged, SelectionChanged, ShapeAdded, ShapeRemoved events).

It will also:

  • create a number of mouse events on mapMain to track mouse movements across the map

  • set up the map, including disabling ClickToCenter (we will be using mouse clicks to trigger our own actions, so need to turn off the default GeoBase actions)

  • set the visibility and state of five of our seven UI buttons using a call to UpdateButtons

  • add some initial text to the statusBox as a guide to users when the application is first launched.

C#
// create new shapelayer
myLayer = new MyShapeLayer(mapMain);

// add an event for when the layer needs to redraw
myLayer.RedrawRequired += OnRedrawRequired;

// enter selection mode
myLayer.EnterSelectMode();

// create events when the layer mode changes
myLayer.ModeChanged += new EventHandler<EventArgs>(myLayer_ModeChanged);
myLayer.SelectionChanged += new EventHandler<EventArgs>(myLayer_SelectionChanged);
myLayer.ShapeAdded += new EventHandler<ShapeEntityEventArgs>(myLayer_ShapeAdded);
myLayer.ShapeRemoved += new EventHandler<ShapeEntityEventArgs>(myLayer_ShapeRemoved);

// set the renderer for the layer
mapMain.Renderer = myLayer;

// create events for mouse movements
mapMain.MouseMove += OnMouseMove;
mapMain.MouseUp += OnMouseUp;
mapMain.MouseDown += OnMouseDown;
mapMain.MouseClick += OnMouseClick;
mapMain.MouseLeave += OnMouseLeave;

// set up the map
mapMain.ClickToCenter = false;
mapMain.AllowEdgePan = false;
mapMain.Zoom = 40;
mapMain.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right;

// set up the visibility of the edit and delete buttons
UpdateButtons();

// hide the buttons in the upper-left corner of the map
priorityButtonUp.Visible = false;
priorityButtonDown.Visible = false;
deleteAllButton.Visible = false;

// show a hint to users in the text box
statusBox.Text = "Right-click the map to add a new shape.";

Below the Form1_Load method, add the following methods. These will be called by the events we added above whenever the content of the myLayer ShapeLayer is changed.

These methods contain new text content for the statusBox which will provide users with information about the state of the myLayer ShapeLayer. The myLayer_ModeChanged method also calls UpdateButtons, which determines whether to show or hide some of our buttons (those that should only be visible when myLayer has an active Selection).

C#
void myLayer_ShapeRemoved(object sender, ShapeEntityEventArgs e) {
    // update the text box when a shape is removed
    statusBox.Text = "A shape has been deleted.";
}

void myLayer_ShapeAdded(object sender, ShapeEntityEventArgs e) {
    // update the text box when a shape is added
    statusBox.Text = "A shape has been added.";
}

// update the text box when the shape selection changes
void myLayer_SelectionChanged(object sender, EventArgs e) {
    // if there is a selection
    if (myLayer.Selection != null) {
        // advise that a polygon is selected
        statusBox.Text = "A shape has been selected.";
    } else {
        // or if not, advise that there is no selection
        statusBox.Text = "No shapes on the canvas are currently selected.";
    }
}

void myLayer_ModeChanged(object sender, EventArgs e) {
    // update the text box when the selection mode changes
    statusBox.Text = "Mode has changed to " + myLayer.CurrentMode.ToString() + " \r\n";
    UpdateButtons();
}

Directly below the myLayer_ModeChanged method, add the following UpdateButtons method. This code determines what text will be displayed on the editButton by checking the ShapeLayer's CurrentMode.

It will then check the myLayer ShapeLayer's Selection property and either enable or disable editButton, and deleteButton in the lower left corner of the window, and show or hide the priorityButtonUp, priorityButtonDown, and deleteAllButton buttons located in the upper left corner of the map.

C#
void UpdateButtons() {
    // change the edit button text depending on the selection mode
    if (myLayer.CurrentMode == ShapeLayer.Mode.Edit) {
        editButton.Text = "Done";
    } else {
        editButton.Text = "Edit";
    }
    // change the edit and deleted button state depending on the
    // selection state
    if (myLayer.Selection != null) {
        editButton.Enabled = true;
        deleteButton.Enabled = true;
        priorityButtonUp.Visible = true;
        priorityButtonDown.Visible = true;
        deleteAllButton.Visible = true;
    } else {
        editButton.Enabled = false;
        deleteButton.Enabled = false;
        priorityButtonUp.Visible = false;
        priorityButtonDown.Visible = false;
        deleteAllButton.Visible = false;
    }
}

Next, add the following three methods below the UpdateButtons method. These will be called by mouse events placed in the Form1_Load method to track mouse activity on the map.

C#
// gather information when the mouse is used
void OnMouseMove(object sender, MouseEventArgs e) {
    myLayer.OnMouseMove(sender, e);
}

void OnMouseUp(object sender, MouseEventArgs e) {
    myLayer.OnMouseUp(sender, e);
}

void OnMouseDown(object sender, MouseEventArgs e) {
    myLayer.OnMouseDown(sender, e);
}

Our shape will be created, and its state edited, using mouse actions. The following code creates a BoundingBox at the current mouse location, listing every shape found within it in an entities list.

If the left mouse button is clicked, and no shape is found below the mouse cursor (that is, the entities list is empty), the layer's Selection property it set to null.

If the right mouse button is clicked, and the entities list is not empty (that is, there is a shape below the mouse cursor) then the first shape in the entities list array will enter Edit mode, allowing its geometry to be edited.

If the right mouse button is clicked, and entities list is empty, then a new polygon (myPol) is created at the mouse location, and a PolygonEntity named myEntity created using the polygon, given a blue color fill, then added to the myLayer ShapeLayer using the AddShape method. The map is then invalidated, causing the shape to be rendered immediately.

Note Note

Each new myEntity object created will be given a higher priority than its immediate predecessor using the priority counter. Doing so ensures that newer objects are rendered on the canvas later than older objects, keeping in their correct relative order.

This snippet also adds several events (IsHighlightedChanged, GeometryChanged, and IsSelectedChanged) and OnEntityChanged that will call our own OnEntityChanged method.

Paste the following code directly below the OnMouseDown method.

C#
// when the mouse is clicked
void OnMouseClick(object sender, MouseEventArgs e) {
    if (!myLayer.OnMouseClick(sender, e)) {
        // get the location of the mouse click
        LatLon loc = mapMain.XYtoLatLon(e.X, e.Y);
        // list all shapes within a boundingbox
        // created using the current location
        List<ShapeEntity> entities = myLayer.QueryShapes(new BoundingBox(loc));
        entities.RemoveAll(shape => !shape.HitTest(loc));

        // if the left mouse button was clicked 
        if (e.Button == System.Windows.Forms.MouseButtons.Left) {
            // and there was no shape beneath
            if (entities.Count == 0) {
                // there is no selection
                 myLayer.Selection = null;
            }
         }

         // if the right mouse button was clicked
         if (e.Button == System.Windows.Forms.MouseButtons.Right) {
             // and there is one or more shapes
            if (entities.Count > 0) {
                // edit the shape
                myLayer.Edit(entities[0]);                     
            } else {
                // otherwise create a polygon based on the map zoom
                int size = 50;
                Telogis.GeoBase.Geometry.Polygon myPol = new Telogis.GeoBase.Geometry.Polygon(
                    new Telogis.GeoBase.Geometry.LineString[] {
                        new Telogis.GeoBase.Geometry.LineString(
                            mapMain.XYtoLatLon(e.X - size, e.Y - size),
                            mapMain.XYtoLatLon(e.X - size, e.Y + size),
                            mapMain.XYtoLatLon(e.X + size, e.Y + size),
                            mapMain.XYtoLatLon(e.X + size, e.Y - size),
                            mapMain.XYtoLatLon(e.X - size, e.Y - size)
                         )
                    }
                );

                // then create a polygon shape entity on the layer
                PolygonEntity myEntity = new PolygonEntity(myLayer, myPol);
                myEntity.Priority = priority;
                priority++;

                // give it a blue fill
                myEntity.Fill = new SolidBrush(Color.DeepSkyBlue);

                // create some events triggered when the shape's
                // properties are changed
                myEntity.IsHighlightedChanged += OnEntityChanged;
                myEntity.GeometryChanged += OnGeometryChanged;
                myEntity.IsSelectedChanged += OnEntityChanged;
                OnEntityChanged(myEntity, EventArgs.Empty);

                // add the shape to the layer
                myLayer.AddShape(myEntity);

                // and invalidate the map to show the shape
                        mapMain.Invalidate();
            }
        }
    }
}

Copy the following method below the OnMouseClick method. The OnEntityChanged method specifies the entity shape's appearance when in its selected or unselected states (as determined by the IsSelected property).

When not selected, the shape will have a thin black border; when selected, a thick black border.

C#
// when the shape is selected give it a thick
// black border. When it isn't, a thin one
void OnEntityChanged(object sender, EventArgs e) {
    PolygonEntity entity = (PolygonEntity)sender;
    if (entity.IsSelected) {
        entity.Outline = new Pen(Color.Black, 5.0f);
    } else {
        entity.Outline = new Pen(Color.Black, 1.0f);
    }
}

Copy the following methods below the OnEntityChanged method. These are invoked by the ShapeEntity events in the OnMouseClick method, and the RedrawRequired event in Form1_Load. These will update the editButton text, monitor mouse movements, and invalidate the map when needed.

C#
// if the shape's geometry is changed - a vertice added,
// deleted or moved - notify in the text field
void OnGeometryChanged(object sender, EventArgs e) {
    statusBox.Text = "The shape's geometry has been changed.";
}

void OnMouseLeave(object sender, EventArgs e) {
    myLayer.OnMouseLeave(sender, e);
}

// if a redraw is needed, invalidate the map
void OnRedrawRequired(object sender, EventArgs e) {
    mapMain.Invalidate();
}

Now we will configure the click methods for the zoom buttons zoomButtonOut and zoomButtonIn and the delete and edit buttons deleteButton and editButton. Update these methods in your project to match to code below.

Note Note

The order of these methods may differ in your project to those shown below. Their order is unimportant and determined by the order of their initial creation. Update the methods individually.

The zoom in and out buttons simply adjust the map zoom using the Zoom property.

The deleteButton method checks that there is an active Selection on myLayer, then deletes the shape selection using RemoveShape if one is found.

The editButton checks the status of the shape using the CurrentMode property. If the layer is already in Edit mode, the layer's mode is changed to Select using the FinishEditing method. If the layer is not already in Edit mode (as described by ShapeLayerMode enumeration), the ShapeLayer enters Edit mode allowing the shape's geometry to be manually manipulated.

C#
// zoom out when the zoom out button is clicked
private void zoomButtonOut_Click(object sender, EventArgs e) {
    mapMain.Zoom = mapMain.Zoom + 2;
}
C#
// zoom in when the zoom in button is clicked
private void zoomButtonIn_Click(object sender, EventArgs e) {
    mapMain.Zoom = mapMain.Zoom - 2;
}
C#
// if the delete button is clicked
private void deleteButton_Click(object sender, EventArgs e) {
    // check that there is an active selection
    if (myLayer.Selection != null) {
        // if there is, remove the entity
        myLayer.RemoveShape(myLayer.Selection);
    }
}
C#
// if the edit button is clicked
private void editButton_Click(object sender, EventArgs e) {
    // and the layer is in edit mode
    if (myLayer.CurrentMode == ShapeLayer.Mode.Edit) {
        // finish editing
        myLayer.FinishEditing();
    // if it isn't in edit mode
    } else if (myLayer.Selection != null) {
        // begin editing the selection
        myLayer.Edit(myLayer.Selection);
    }
}

We will also configure the methods needed for the two layer priority buttons priorityButtonUp and priorityButtonDown. Update these methods to match to code below.

These methods increase or lower the numeric priority of the selected shape using the Selection property. Increasing a shape's priority renders it higher in the z-index order, eventually bringing shapes placed below other shapes to the top. Reducing its priority will push the shape back, causing it to be rendered below other shapes sharing the same canvas area.

C#
// increase the priority of the shape. If several shapes overlap, 
// use this to adjust their rendering order
private void priorityButtonUp_Click(object sender, EventArgs e) {
    myLayer.Selection.Priority++;
    mapMain.Invalidate();
    statusBox.Text = "The shape's priority has been increased.";
}
C#
// lower the priority of the shape. If several shapes overlap, 
// use this to adjust their rendering order
private void priorityButtonDown_Click(object sender, EventArgs e) {
    myLayer.Selection.Priority--;
    mapMain.Invalidate();
    statusBox.Text = "The shape's priority has been reduced.";
}

Update the deleteAllButton_Click method to match the code below. This simply:

  • deletes everything on the myLayer ShapeLayer using the Clear method

  • updates the statusBox text to advise that the layer's content has been deleted

  • hides to map buttons displayed in the upper left corner of the map

C#
// delete all shapes on the layer
private void deleteAllButton_Click(object sender, EventArgs e) {
    myLayer.Clear();
    mapMain.Invalidate();
    statusBox.Text = "All shapes have been deleted from the canvas.";
    priorityButtonUp.Visible = false;
    priorityButtonDown.Visible = false;
    deleteAllButton.Visible = false;
}

To add some convenience to users, we will change the cursor to the drag hand, using the DragBehavior property, when the shift key is held down. Paste the following Form1_KeyDown method into the project. Like our other methods, the location of this method is unimportant, but for this example we place it below all other methods.

C#
// show the drag hand when holding down the keyboard shift key
private void Form1_KeyDown(object sender, KeyEventArgs e) {
    if ((e.Modifiers & Keys.Shift) == Keys.Shift && 
    mapMain.DragBehavior == DragBehavior.Box) {
        mapMain.DragBehavior = DragBehavior.Hand;
    }
}

And finally, add the following line to the Form1 method (directly below the InitializeComponent constructor). This will call the Form1_KeyDown method using the standards Windows KeyDown event.

C#
this.KeyDown += new KeyEventHandler(Form1_KeyDown);
Testing

Run the application. Add new polygon shapes by right-clicking the map and delete them by clicking with your mouse, then clicking Delete.

Note the text displayed in the statusBox text field. It will be updated with details of your actions as they are carried out.

To edit a shape, click the shape then click Edit (or simply right-click a shape: this is an automatic function). Edit the polygon's shape by dragging the border of the shape (doing so will add a new vertex) or by dragging existing vertices (identified as red circles, as in the image below). To delete an existing vertex, simply right-click it.

shapeUI example working

Place several polygon shapes close to one another such that they overlap, then select a shape and click the Move Shape Up or Move Shape Down buttons to show the effect of changing priority. If a polygon is selected and the layer is changed to Edit mode (by clicking the Edit button or right-clicking the shape), the priority of the selected shape will be increased to ensure that the entire shape is visible above any other shapes sharing the same space on the canvas (temporarily raising it above any overlapping shapes if needed). When editing has been completed, the shape's priority will return to its previous value.

Click Delete All Shapes to clear all shapes from the canvas (note that a shape must be selected on the map to view this button).

Note Note

The Edit and Delete buttons will only be active, and the Move Shape Up, Move Shape Down and Delete All Shapes buttons only visible, when a shape has been selected.

Click any area outside the polygon to exit select mode, and right-click a polygon or click the Done button to exit edit mode.

Complete Code

The complete code for this example project is included below:

C#
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Telogis.GeoBase;
using Telogis.GeoBase.ShapeUI;
using Telogis.GeoBase.ShapeUI.State;
using Telogis.GeoBase.Geometry;

namespace PolygonEditor {
    public partial class Form1 : Form {
        public ShapeLayer myLayer;
        public int priority = 0;
        public class MyShapeLayer : ShapeLayer {
            public MyShapeLayer(IMap map)
                : base(map) {
            }

            public override IState GetDefaultState() {
                return new SelectState(this, null);
            }
        }

        public Form1() {
            InitializeComponent();
            this.KeyDown += new KeyEventHandler(Form1_KeyDown);
        }

        private void Form1_Load(object sender, EventArgs e) {
            // create new shapelayer
            myLayer = new MyShapeLayer(mapMain);

            // add an event for when the layer needs to redraw
            myLayer.RedrawRequired += OnRedrawRequired;

            // enter selection mode
            myLayer.EnterSelectMode();

            // create events when the layer mode changes
            myLayer.ModeChanged += new EventHandler<EventArgs>(myLayer_ModeChanged);
            myLayer.SelectionChanged += new EventHandler<EventArgs>(myLayer_SelectionChanged);
            myLayer.ShapeAdded += new EventHandler<ShapeEntityEventArgs>(myLayer_ShapeAdded);
            myLayer.ShapeRemoved += new EventHandler<ShapeEntityEventArgs>(myLayer_ShapeRemoved);

            // set the renderer for the layer
            mapMain.Renderer = myLayer;

            // create events for mouse movements
            mapMain.MouseMove += OnMouseMove;
            mapMain.MouseUp += OnMouseUp;
            mapMain.MouseDown += OnMouseDown;
            mapMain.MouseClick += OnMouseClick;
            mapMain.MouseLeave += OnMouseLeave;

            // set up the map
            mapMain.ClickToCenter = false;
            mapMain.AllowEdgePan = false;
            mapMain.Zoom = 40;
            mapMain.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right;

            // set up the visibility of the edit and delete buttons
            UpdateButtons();

            // hide the buttons in the upper-left corner of the map
            priorityButtonUp.Visible = false;
            priorityButtonDown.Visible = false;
            deleteAllButton.Visible = false;

            // show a hint to users in the text box
            statusBox.Text = "Right-click the map to add a new shape.";
        }

        void myLayer_ShapeRemoved(object sender, ShapeEntityEventArgs e) {
            // update the text box when a shape is removed
            statusBox.Text = "A shape has been deleted.";
        }

        void myLayer_ShapeAdded(object sender, ShapeEntityEventArgs e) {
            // update the text box when a shape is added
            statusBox.Text = "A shape has been added.";
        }

        // update the text box when the shape selection changes
        void myLayer_SelectionChanged(object sender, EventArgs e) {
            // if there is a selection
            if (myLayer.Selection != null) {
                // advise that a polygon is selected
                statusBox.Text = "A shape has been selected.";
            } else {
                // or if not, advise that there is no selection
                statusBox.Text = "No shapes on the canvas are currently selected.";
            }
        }

        void myLayer_ModeChanged(object sender, EventArgs e) {
            // update the text box when the selection mode changes
            statusBox.Text = "Mode has changed to " + myLayer.CurrentMode.ToString() + " \r\n";
            UpdateButtons();
        }

        void UpdateButtons() {
            // change the edit button text depending on the selection mode
            if (myLayer.CurrentMode == ShapeLayer.Mode.Edit) {
                editButton.Text = "Done";
            } else {
                editButton.Text = "Edit";
            }
            // change the edit and deleted button state depending on the
            // selection state
            if (myLayer.Selection != null) {
                editButton.Enabled = true;
                deleteButton.Enabled = true;
                priorityButtonUp.Visible = true;
                priorityButtonDown.Visible = true;
                deleteAllButton.Visible = true;
            } else {
                editButton.Enabled = false;
                deleteButton.Enabled = false;
                priorityButtonUp.Visible = false;
                priorityButtonDown.Visible = false;
                deleteAllButton.Visible = false;
            }
        }

        // gather information when the mouse is used
        void OnMouseMove(object sender, MouseEventArgs e) {
            myLayer.OnMouseMove(sender, e);
        }

        void OnMouseUp(object sender, MouseEventArgs e) {
            myLayer.OnMouseUp(sender, e);
        }

        void OnMouseDown(object sender, MouseEventArgs e) {
            myLayer.OnMouseDown(sender, e);
        }

        // when the mouse is clicked
        void OnMouseClick(object sender, MouseEventArgs e) {
            if (!myLayer.OnMouseClick(sender, e)) {
                // get the location of the mouse click
                LatLon loc = mapMain.XYtoLatLon(e.X, e.Y);
                // list all shapes within a boundingbox
                // created using the current location
                List<ShapeEntity> entities = myLayer.QueryShapes(new BoundingBox(loc));
                entities.RemoveAll(shape => !shape.HitTest(loc));

                // if the left mouse button was clicked 
                if (e.Button == System.Windows.Forms.MouseButtons.Left) {
                    // and there was no shape beneath
                    if (entities.Count == 0) {
                        // there is no selection
                        myLayer.Selection = null;
                    }
                }

                // if the right mouse button was clicked
                if (e.Button == System.Windows.Forms.MouseButtons.Right) {
                    // and there is one or more shapes
                    if (entities.Count > 0) {
                        // edit the shape
                        myLayer.Edit(entities[0]);                     
                    } else {
                        // otherwise create a polygon based on the map zoom
                        int size = 50;
                        Telogis.GeoBase.Geometry.Polygon myPol = new Telogis.GeoBase.Geometry.Polygon(
                            new Telogis.GeoBase.Geometry.LineString[] {
                                new Telogis.GeoBase.Geometry.LineString(
                                    mapMain.XYtoLatLon(e.X - size, e.Y - size),
                                    mapMain.XYtoLatLon(e.X - size, e.Y + size),
                                    mapMain.XYtoLatLon(e.X + size, e.Y + size),
                                    mapMain.XYtoLatLon(e.X + size, e.Y - size),
                                    mapMain.XYtoLatLon(e.X - size, e.Y - size)
                                )
                            }
                        );

                        // then create a polygon shape entity on the layer
                        PolygonEntity myEntity = new PolygonEntity(myLayer, myPol);
                        myEntity.Priority = priority;
                        priority++;

                        // give it a blue fill
                        myEntity.Fill = new SolidBrush(Color.DeepSkyBlue);

                        // create some events triggered when the shape's
                        // properties are changed
                        myEntity.IsHighlightedChanged += OnEntityChanged;
                        myEntity.GeometryChanged += OnGeometryChanged;
                        myEntity.IsSelectedChanged += OnEntityChanged;
                        OnEntityChanged(myEntity, EventArgs.Empty);

                        // add the shape to the layer
                        myLayer.AddShape(myEntity);

                        // and invalidate the map to show the shape
                        mapMain.Invalidate();
                    }
                }
            }
        }

        // when the shape is selected give it a thick
        // black border. When it isn't, a thin one
        void OnEntityChanged(object sender, EventArgs e) {
            PolygonEntity entity = (PolygonEntity)sender;
            if (entity.IsSelected) {
                entity.Outline = new Pen(Color.Black, 5.0f);
            } else {
                entity.Outline = new Pen(Color.Black, 1.0f);
            }
        }

        // if the shape's geometry is changed - a vertice added,
        // deleted or moved - notify in the text field
        void OnGeometryChanged(object sender, EventArgs e) {
            statusBox.Text = "The shape's geometry has been changed.";
        }

        void OnMouseLeave(object sender, EventArgs e) {
            myLayer.OnMouseLeave(sender, e);
        }

        // if a redraw is needed, invalidate the map
        void OnRedrawRequired(object sender, EventArgs e) {
            mapMain.Invalidate();
        }

        // zoom out when the zoom out button is clicked
        private void zoomButtonOut_Click(object sender, EventArgs e) {
            mapMain.Zoom = mapMain.Zoom + 2;
        }

        // zoom in when the zoom in button is clicked
        private void zoomButtonIn_Click(object sender, EventArgs e) {
            mapMain.Zoom = mapMain.Zoom - 2;
        }

        // if the delete button is clicked
        private void deleteButton_Click(object sender, EventArgs e) {
            // check that there is an active selection
            if (myLayer.Selection != null) {
                // if there is, remove the entity
                myLayer.RemoveShape(myLayer.Selection);
            }
        }

        // if the edit button is clicked
        private void editButton_Click(object sender, EventArgs e) {
            // and the layer is in edit mode
            if (myLayer.CurrentMode == ShapeLayer.Mode.Edit) {
                // finish editing
                myLayer.FinishEditing();
            // if it isn't in edit mode
            } else if (myLayer.Selection != null) {
                // begin editing the selection
                myLayer.Edit(myLayer.Selection);
            }
        }

        // increase the priority of the shape. If several shapes overlap, 
        // use this to adjust their rendering order
        private void priorityButtonUp_Click(object sender, EventArgs e) {
            myLayer.Selection.Priority++;
            mapMain.Invalidate();
            statusBox.Text = "The shape's priority has been increased.";
        }

        // lower the priority of the shape. If several shapes overlap, 
        // use this to adjust their rendering order
        private void priorityButtonDown_Click(object sender, EventArgs e) {
            myLayer.Selection.Priority--;
            mapMain.Invalidate();
            statusBox.Text = "The shape's priority has been reduced.";
        }

        // delete all shapes on the layer
        private void deleteAllButton_Click(object sender, EventArgs e) {
            myLayer.Clear();
            mapMain.Invalidate();
            statusBox.Text = "All shapes have been deleted from the canvas.";
            priorityButtonUp.Visible = false;
            priorityButtonDown.Visible = false;
            deleteAllButton.Visible = false;
        }

        // show the drag hand when holding down the keyboard shift key
        private void Form1_KeyDown(object sender, KeyEventArgs e) {
            if ((e.Modifiers & Keys.Shift) == Keys.Shift && 
            mapMain.DragBehavior == DragBehavior.Box) {
                mapMain.DragBehavior = DragBehavior.Hand;
            }
        }
    }
}