Drawing/Inputting a Polygon using a Virtual Earth map

by Jeff Email

Link: http://totusterra.com/VESamples/drawpoly.htm

Today I received a question from a customer about how to draw a polygon onto a Virtual Earth map, and I think this is a great topic to touch on because it gets to something I feel is under-represented when we talk about Virtual Earth. We often talk about Virtual Earth as the 'presentation layer' for surfacing data on a map -- but it's just as valuable as a data input tool.

Follow up:

So let's look at how you would go about writing the code necessary to draw a polygon on the map. It ends up being fairly simple, just a combination of responding to mouse events and drawing shapes.

Overview:

There are 4 basic steps to process (or really 3, with two possible ways of finishing):

  1. Start the drawing process
  2. Add a point once drawing has begun
  3. Completing the polygon & finishing drawing
  4. Abandoning the polygon & finishing drawing

When it comes to drawing the polygon, remember that you need at least 3 points for a polygon, so when we start drawing we will begin with a line segment before switching to a polygon.

You can see the finished behavior (and complete source) here.

For purposes of illustration, I'll assume that you already have a working map somewhere and just move from there. (Check the link above for the full HTML if you want it from scratch).

Setting up some 'drawing state':

In order to keep track of what is going on, we'll use the following variables:

var drawing = false;   // True if we're drawing a polygon
var polyPoints;   // The points in the current polyon
var startPin = null;   // Shape to hold the start pin
var drawingObject = null;   // Shape to hold the polyline/polygon

Responding to mouse events:

Responding to mouse events is covered fairly well in the MSDN documentation, so I won't go too deep into how they work and just talk about what they do in the context of this task.

We're going to have two different mouse handlers here. First, the onclick handler which will manage starting & ending drawing, and adding points to the polygon.


function OnMouseClick(e)
{
   // Get the latitude & longitude where the map was clicked
   var clickLatLong = map.PixelToLatLong( new VEPixel( e.mapX, e.mapY ) );

   // Then process according to the rules
   if (e.rightMouseButton && !drawing)
      StartDrawing(clickLatLong);
   else if ((startPin != null) && (e.elementID != null) &&
         (e.elementID.indexOf(startPin.GetID()) == 0) &&
         (polyPoints.length >= 3))
      CompletePolygon();
   else if (e.leftMouseButton && drawing)
      AddPoint(clickLatLong);
   else if (e.rightMouseButton && drawing)
      AbandonDrawing();
}

The rules here basically work out to this: Start drawing when we see a right-click. From then on, left-click will add a point. If you left-click on the starting icon, drawing is complete. If we right-click after starting, we abandon drawing.

The second function responds to the mouse moving, so that as you move between clicks, the polygon will update in real time.

// Mouse Move handler. Update the polygon everytime the mouse moves.
function OnMouseMove(e)
{
   // if we're currently drawing a polygon, then update the last data point
   // to reflect where the mouse currently is.
   if (drawing)
   {
      var mouseLatLong = map.PixelToLatLong(new VEPixel(e.mapX, e.mapY));
      drawingObject.SetPoints(polyPoints.concat([mouseLatLong]));
   }
}

And that's about all we have to do in terms of handling the mouse. The rest of the magic is in managing state.

Creating, Adding, Completing & Abandoning:

First off, we have to get the polygon created. Actually, to start it's just a line, since you need at least 3 points for a polygon. We'll switch over to a polygon in the next step. We also create a 'startPin' which will be what we click on when we're finished drawing the polygon. Here's the code:

function StartDrawing(ll)
{
   // Set the flag to true and create a new list of points and
   // initialize at the current location
   drawing = true;
   polyPoints = [ ll ];

   // create a start pin
   startPin = new VEShape(VEShapeType.Pushpin, ll);
   startPin.SetTitle("Click here to complete the polygon.");
   map.AddShape(startPin);

   // Polygons require 3 points, until we have them, just draw
   // a line
   drawingObject = new VEShape(VEShapeType.Polyline, [polyPoints[0], polyPoints[0]]);
   map.AddShape(drawingObject);
   drawingObject.HideIcon();
}

Once we're drawing, we need to be able to add a point. If we have enough points (2) to have a polygon, we switch to that.

function AddPoint(ll)
{
   // add the point to the list of points for polygon
   polyPoints.push(ll);

   // if we are up to 2 fixed points, replace the line segment
   // with a polygon.
   if (polyPoints.length == 2)
   {
      // delete the line
      map.DeleteShape(drawingObject);

      // create the polygon
      drawingObject = new VEShape(VEShapeType.Polygon, polyPoints.concat([polyPoints[1]]));
      drawingObject.HideIcon();
      map.AddShape(drawingObject);
   }
}

If we decide we made a mistake somewhere, we need a way to cancel. So here's the code to abandon the current polygon.

function AbandonDrawing()
{
   drawing = false;
   map.DeleteShape(drawingObject);
   drawingObject = null;
   map.DeleteShape(startPin);
   startPin = null;
}

And finally we need to be able to complete the drawing of the polygon. It's at this point that you would have your own custom behavior to actually do something with your fancy new polygon, and you can see in the code where that would go:

function CompletePolygon()
{
   // Complete the polygon
   drawingObject.SetPoints(polyPoints);

   // custom functionality on finishing polygon would go here.

   // remove the start icon
   map.DeleteShape(startPin);

   // and reset state to 'non-drawing'
   drawing = false;
   polyPoints = null;
   drawingObject = null;
   startPin = null;
}

And that's about it. Check out the sample page to see how it works and see the full source.