Bing Maps Concepts
(formerly Virtual Earth)
Mike Garza   
VEMaps@garzilla.net   

   
Concept: Circle Drag Handle

In this example, we will look at implementing a "drag handle" for resizing a circle. This example builds off of the Polygon Drag Handle Example (to a certain extent). Where it differs is that there is only one drag handle to adjust the size of the circle, which is really just a polygon.

In the example to the right, a circle has been placed on the map. At the bottom of the map, there is a button to toggle the “editing” mode. When the button is clicked, the map is put into edit mode and a drag handle appears on the circle. When you click (and hold) the drag handle, you can move it to a new location on the map. As you move the drag handle, a masked circle is displayed to illustrate the new circle size. Drop the handle in the desired location and the original circle is updated to reflect the new size.

Note: In dragging this circle out great distances, you will see a fair amount of distortion on the circle. This is due to the fact that the earth is a sphere and the Bing uses Mercator Projection to display the map. If you have any suggestions on how to adequately deal with this issue, I am all ears (or eyes as the case may be).

Walk Through:

As mentioned, a circle is really nothing more than a polygon (VEShape), with enough point to make it look like a circle. Zoom in close enough to the circle on this map and you will see a polygon with 36 points.

The creation of the circle starts with a given point on the map. Then a radius is chosen and the 36 points of the circle are calculated. In the application code, there is a JavaScript function named “buildCiricle”. The implementation is as follows.
    function buildCircle(latin, lonin, radius) {
        var locs = new Array();
        var lat1 = latin * Math.PI / 180.0;
        var lon1 = lonin * Math.PI / 180.0;
        var d = radius / 3956;
        var x;
        for (x = 0; x <= 360; x+=10) {
            var tc = (x / 90) * Math.PI / 2;
            var lat = Math.asin(Math.sin(lat1) * Math.cos(d) + Math.cos(lat1) * Math.sin(d) * Math.cos(tc));
            lat = 180.0 * lat / Math.PI;
            var lon;
            if (Math.cos(lat1) == 0) {
                lon = lonin; // endpoint a pole 
            }
            else {
                lon = ((lon1 - Math.asin(Math.sin(tc) * Math.sin(d) / Math.cos(lat1)) + Math.PI) % (2 * Math.PI)) - Math.PI;
            }
            lon = 180.0 * lon / Math.PI;
            var loc = new VELatLong(lat, lon);
            locs.push(loc);
        }
        return locs;
    }
    

The function accepts the latitude and longitude of the center of the circle as well as the desired radius and returns an array of VELatLong objects that are all of the points of the circle. The “buildCircle” function is not a chunk of code that I wrote, so I am not going to try to explain the math. My apologies, but I don’t recall where I got this from. If you are the author or happen to know the author, please let me know so I can give the appropriate credit.

Now that we know how the circle is created, let’s look at the code to set up the map. As always, we need to get the map set up. That is handled by the “load” function and the implementation is as follows:
    function load() {
        //Create Map
        map = new VEMap('MapDiv');
        map.LoadMap();
        map.SetMapStyle(VEMapStyle.Shaded);
        map.SetMouseWheelZoomToCenter(false);
        map.SetCenterAndZoom(centerPoint, 9);
        
        // Attach the event handlers to the mouse 
        map.AttachEvent("onmousemove", mouseMoveHandler);
        map.AttachEvent("onmousedown", mouseDownHandler);
        map.AttachEvent("onmouseup", mouseUpHandler);
        
        //Get initial circle points
        var circlePoints = buildCircle(centerPoint.Latitude, centerPoint.Longitude, 5);
        
        //Build circle
        circle = new VEShape(VEShapeType.Polygon, circlePoints);
        circle.HideIcon();
        circle.SetLineWidth(2);
        map.AddShape(circle);
        
        //Build mask
        circleMask = new VEShape(VEShapeType.Polygon, circlePoints);
        circleMask.HideIcon();
        circleMask.Hide();
        circleMask.SetLineColor(new VEColor(0, 0, 0, 0.5));
        circleMask.SetFillColor(new VEColor(0, 0, 0, 0.0));
        circleMask.Primitives[0].symbol.stroke_dashstyle = "Dash";
        map.AddShape(circleMask);
        
        //Add drag handle
        dragHandle = new VEShape(VEShapeType.Pushpin, circlePoints[10]);
        dragHandle.SetCustomIcon('images/DragHandle.gif');
        dragHandle.Hide();
        map.AddShape(dragHandle);
    }
    
First the map is created in the typical fashion (we won’t go through that). Next three events are attached to map to handle the necessary mouse functions (Mouse Move, Mouse Down & Mouse Up). The initial points of the circle are then defined using the “buildCircle” function, previously touched on. Those points are used to create the initial circle on the map as well as the masked version of the circle, used when dragging occurs. Lastly, the drag handle is created and added to the map. Please note that the circle mask as well as the drag handle is initially set to “hidden” as they are only needed when dragging occurs.

Now that the initial map has been built, there needs to be a mechanism that puts the map in “edit” mode so the circle can be change. That mechanism, in this sample, is the “Click to start edit” button at the bottom of the map. When this button is clicked, the following code is executed:
    //toggle edit mode
    function toggleEdit() {
	    if (!dragHandle.GetVisibility()) {
		    dragHandle.Show();
		    document.getElementById("cmdEdit").value = 'Click to end edit';
	    } else {
		    dragHandle.Hide();
		    document.getElementById("cmdEdit").value = 'Click to start edit';
	    }
    }
    
When the button is clicked, the toggleEdit() function is executed. This function toggles the map in and out of edit mode. The visibility of the Drag Handle, a VEShape object, is the determining factor for the mode of the map. If the handle is not visible, then the map is not in edit mode, otherwise it is. The GetVisibility() method of the VEShape, dragHandle returns a Boolean value indicating whether or not the shape is visible. If the dragHandle is not currently visible, then the Show() method on dragHandle object is called to show the shape and the text of the button is updated. This puts the map in edit mode. If the dragHandle is visible, then the Hide() method on the dragHandle object is called to hide the shape and again, the text of the button is updated. Now the map is out of edit mode.

Once the edit mode has been enabled and the dragHandle VEShape has been displayed, the previously registered events come into play for the drag handle. When the user clicks on the drag handle, the onmousedown event fires and executes the mouseDownHandler. The implementation is as follows:
    //onmousedown handler
    function mouseDownHandler(e) {
	    if (e.elementID) {
		    dragHandle = map.GetShapeByID(e.elementID);
		    if (dragHandle.GetType() == VEShapeType.Pushpin) {
		        moving = true;
		        circleMask.Show();
		        map.vemapcontrol.EnableGeoCommunity(true);
		        document.getElementById("MapDiv").style.cursor = 'crosshair';
		    }
	    }
    }
    
First we want to make sure that we have a valid drag handle to start the moving process. The two if statements at the beginning of the code facilitate this. The first test ensures that we have a valid VE object associated with the event and the second test ensures that the object is a push pin (drag handle).

Once we know we have the right type of object, we can start the move process. First we put the map in move mode by setting the value of “moving” to true. Next we display the mask for the circle mask by calling the Show() method on the VEShape object, circleMask . We need to mouse to interact with the map for dragging, so EnableGeoCommunity() is set to true to allow us to do that. Lastly we updated the mouse cursor to a “crosshair” for a visual representation of this mode.

The map remains in moving mode as long as the mouse button is held down. While held down, the drag handle can be used to resize the circle, by moving the mouse. As the mouse is moved, the onmousemove event is fired and executes the mouseMoveHandler. The implementation is as follows:
    //onmousemove handler
    function mouseMoveHandler(e) {
	    if (moving) {
		    var loc = map.PixelToLatLong(new VEPixel(e.mapX, e.mapY));
		    dragHandle.SetPoints(loc);
		        
		    //Get distance from center to new point
		    var distance = LatLon.distHaversine(centerPoint.Latitude, centerPoint.Longitude, loc.Latitude, loc.Longitude);
		    points = buildCircle(centerPoint.Latitude, centerPoint.Longitude, distance);
		    circleMask.SetPoints(points);
	    }
    }
    
Since this function will execute each time the mouse moves, we want to make sure that we are in “moving” mode. We test the value of the variable “moving” and if “true” we continue. Next we capture the location of the cursor, from the event object e, and create a VELatLong object that is assigned to the variable “loc”. The loc variable is used to update the location of the drag handle, via its SetPoints() method.

Now we need to rebuild the circleMask VEShape object with the new circle information. Using the new coordinates from the drag handle, we determine the distance from the new drag handle location to the original center point of the circle. The original center of the circle was persisted in the VELatLong object, centerPoint , that was used to create the initial circle. The coordinates of the new position of the drag handle are represented by the VELatLong object, loc. To determine the distance between these two points, the Haversine formula is used.

The Haversine formula is an equation used to calculate distances between two points on a sphere, using the latitude and longitude values from those points. Fortunately for me, there is an existing JavaScript library that handles a whole slew of geospatial functions, including the Haversine Formula. This library is available at http://www.movable-type.co.uk/scripts/latlong.html. Many Thanks!!

The latitude and longitude of the centerPoint and loc VELatLong objects are passed to the distHaversince function. This function returns the distance between the two points and that value is assigned to the variable “distance”. Next the latitude and longitude from the orginal circle center as well as the new distance is passed to the buildCircle function is called (previously touched on). The function will return an array of VELatLong object that are the points for the new circle. This array of points is passed to the SerPoints() method on the VEShape, circleMask, to update the shape with the new point information. This process is repeated as long as the mouse is moving and the map is still in moving mode.

As mentioned the map remains in “moving” mode as long as the mouse button is held down. Once the mouse button is released, the onmouseup event is fired and the mouseUpHandler is executed. The implementation is as follows:
    //onmouseup handler
	function mouseUpHandler(e) {
		if (moving) {
		    moving = false;
		    circleMask.Hide();
		    map.vemapcontrol.EnableGeoCommunity(false);
		    document.getElementById("MapDiv").style.cursor = '';
		    circle.SetPoints(points);
		}
	}
    
The variable "moving" is tested to determine this and if we are in moving mode, the remainder of the code is executed. The remainder of the code essentially undoes what the onmousedown event did. That being the case, “moving” is set to “false”, the circle mask is hidden, EnableGeoCommunity is set to false and the mouse cursor is restored.

One additional step that is needed is the last line of code. This code updates the original circle with the new points that were established during the move process. The points array is passed to the SetPoints() method in the original circle, updating it with the new points that represent the resized circle.

Wrap-up:

The previous was an overview of how you could use a drag handle to resize a circle (which is actually a 36 point polygon). As usual, there are a lot of different ways this concept could be implemented. This is just one of many approaches that can be used and is intended to illustrate this concept. Hopefully this will provide a building block for the implementation that is most appropriate for your needs.

As always, feedback, comments or questions are welcome. I look forward to hearing from you.

Return to Home Page

©2007-2014, Mike Garza, All Rights Reserved