Bing Maps Ajax Control 7.0 Widget – Drawing Lines and Polygons

I recently explored Project Silk and experimented with Bing Maps Ajax Control 7.0 SDK. During the process I got an idea of creating a jQuery Widget for the latest version of Bing Maps SDK. Bing Maps Ajax Control 7.0 is a very lightweight version of its predecessor. You can explore an interactive demo of the SDK here or you can study the APIs in depth here.

The best tutorials about jQuery Widgets that I found are: Project Silk – Chapter 3 and Project Silk – Chapter 14 and of course jQuery Widget Tutorial on a jQuery site.

The Bing Maps Ajax Control Widget enhances the control by adding line and polygon drawing capabilities. In the next post I will expand drawing capabilities with rectangle and circle drawing.

HTML Page to Host the Widget

Making a widget out of Bing Map ontrol is easy. This is the bare bones html page that includes the Bing Maps Control.

<!doctype html>
<html>
    <head>
        <title>Bings Map Widget Demo</title>
        <script type="text/javascript" src="scripts/jquery-1.7.1.js"></script>
        <script src="http://ajax.aspnetcdn.com/ajax/jquery.ui/1.8.10/jquery-ui.min.js"></script>
    </head>
<body>
    <button type="button" id="btnLineDrawCustom">Draw Line</button>
    <button type="button" id="btnPolyDrawCustom">Draw Polygon</button>
   <hr />
    <div id="bingMapsToolbox" />
    <div id='mapDiv' name='mapDiv' style="position:relative; float: left;"/>
<script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0"></script>
<script src="scripts/cm-utils.js"></script>
<script src="scripts/cm-widgets.bingMaps7.js"></script>
<script src="scripts/startup.js"></script>
</body>
</html>

Buttons on the top are for testing the functionality and can be replaced with something else.

Listig of the included Javascript files

  • jQuery
  • jQuery Ui
  • Microsoft Bing Maps AJax Control
  • Helper utility for logging
  • Bing Maps Control Drawing Widget
  • Start-up script

Helper utility for logging

var cmUtils = {
    logType: { log: "log", info: "info", warn: "warn", error: "error" },
    Logger: function () {
        this.enabled = true;
        this.log = function (message, severity) {
            if (!this.enabled) return;
            var reason = severity || cmUtils.logType.log;
            switch (reason) {
                case cmUtils.logType.info:
                    console.info(message);
                    return;
                case cmUtils.logType.warn:
                    console.warn(message);
                    return;
                case cmUtils.logType.error:
                    console.error(message);
                    return;
                case cmUtils.logType.log:
                    console.log(message);
                    return;
            }
        }
    }
};

This is a helper logging class. It works with all latest version of the most popular browsers(IE, FF, Chrome, Safari).

* NoteIn IE you need to run the Developer tools otherwise you will get an error if you eneabel the logging.

Start-up script

(function ($) {
    $(document).ready(function () {
        var bingMaps7 = $("#mapDiv").bingMaps7({
            appKey: "Your Bing Maps key here", zoom: 12, centerX: 38.809771, centerY: -77.0321543125, width: 1000, height: 800
        });
        $("#btnLineDrawCustom").bind('click', function () { bingMaps7.bingMaps7('drawLine'); return false; });
        $("#btnPolyDrawCustom").bind('click', function () { bingMaps7.bingMaps7('drawPolygon'); return false; });
    });
} (jQuery));

This code initializes the widget and wires-up the buttons for testing.

Bing Maps Control Drawing Widget

//http://www.jslint.com/
(function ($) {

    var map,
       mapDiv,
       jqMapDiv,
       that,
       actionType,
       step,
       points,
       lineDrawingStyle,
       polyDrawingStyle,
       polyFillStyle,
       pushpins,
       polylines,
       shapes,
    //mapClickEventHandler = null,
       mapMouseDownEventHandler = null,
       mapMouseUpEventHandler = null,
       mapMouseMoveEventHandler = null,
    //mapClickEventName = 'click',
       mapMousedownEventName = 'mousedown',
       mapMouseupEventName = 'mouseup',
       mapMousemoveEventName = 'mousemove',
       logger;

    //console.log("Start up");
    var MM = Microsoft.Maps;

    $.widget('cm-widgets.bingMaps7', {

        options: {
            width: 1000,
            height: 800,
            centerX: 38.809771,
            centerY: -77.0321543125,
            zoom: 12,
            appKey: null,
            dataUrl: '',
            step: 20,
            lineDrawingStyle: { strokeColor: new MM.Color(255, 0, 0, 255), strokeThickness: 8 },
            polyDrawingStyle: { strokeColor: new MM.Color(255, 0, 0, 255), strokeThickness: 1 },
            polyFillStyle: { strokeColor: new MM.Color(255, 0, 0, 255), strokeThickness: 1, fillColor: new MM.Color(80, 0, 255, 0) },
            logger: new cmUtils.Logger()
        },

        _create: function () {
            logger = this.options.logger;
            logger.enabled = false;
            that = this;
            var name = this.name, options = this.options, elem = this.element.context;
            step = options.step;
            lineDrawingStyle = options.lineDrawingStyle;
            polyDrawingStyle = options.polyDrawingStyle;
            polyFillStyle = options.polyFillStyle;
            var mapOptions = { credentials: options.appKey, zoom: options.zoom, center: new MM.Location(options.centerX, options.centerY) };
            mapDiv = document.getElementById(elem.id);
            mapDiv.style.height = options.height + "px";
            mapDiv.style.width = options.width + "px";
            map = new MM.Map(mapDiv, mapOptions);
            jqMapDiv = $('#' + mapDiv.id);

            pushpins = new MM.EntityCollection();
            polylines = new MM.EntityCollection();
            shapes = new MM.EntityCollection();

            map.entities.push(shapes);
            map.entities.push(polylines);
            map.entities.push(pushpins);

            logger.log("created " + name);
        },

        drawLine: function () {
            logger.log("drawLine");

            map.setOptions({ disablePanning: true, disableZooming: true });
            that._detachEventHandlers();

            points = new Array();

            var handleLineDrawingMousedown = function (e) {
                if (e.targetType !== "map") return;
                logger.log(e.eventName);
                var point = new MM.Point(e.getX(), e.getY());
                points.push(point);
                that._createPushpin(point);
                that._addHandler(mapMousemoveEventName, handleLineDrawingMousemove);
            }

            var handleLineDrawingMouseup = function (e) {
                logger.log(e);
                logger.log(e.eventName);
                //fix for chrome, ff and safari
                //if (e.targetType !== "map") return;
                if (e.eventName !== 'mouseup') {
                    logger.log("unbinding fixer");
                    jqMapDiv.unbind('mouseup', fixerHandler); return;
                }
                //logger.log(e.eventName);
                var point = new MM.Point(e.getX(), e.getY());
                if (!that._arePointsEqual(point, points[0])) {
                    logger.log("distance between points ok, adding second pushpin...");
                    if (points.lenght > 1) { points.pop(); }
                    points.push(point);
                    that._createPushpin(point);
                }
                else {
                    logger.log("distance between points too short, removing first pushpin...");
                    points.pop();
                    that._popPushpin();
                }
                that._detachEventHandlers();
                map.setOptions({ disablePanning: false, disableZooming: false });
            }

            var handleLineDrawingMousemove = function (e) {
                if (e.targetType !== "map") return;
                logger.log(e.eventName);
                var point = new MM.Point(e.getX(), e.getY());
                var lastPoint = (points.length == 1) ? points[0] : points[points.length - 2];
                logger.log(points.length);
                logger.log(point + "," + lastPoint);
                if (!that._arePointsEqual(point, lastPoint)) {
                    var withReplace = points.length > 1;
                    if (withReplace) points.pop();
                    points.push(point);
                    that._createLine(points[0], point, withReplace);
                }
            }

            that._addHandler(mapMousedownEventName, handleLineDrawingMousedown);
            that._addHandler(mapMouseupEventName, handleLineDrawingMouseup);
            //fix for chrome, ff and safari
            var fixerHandler = function () { handleLineDrawingMouseup(event); };
            jqMapDiv.bind('mouseup', fixerHandler);

        },

        drawPolygon: function () {
            logger.log("drawPolygon");
            map.setOptions({ disablePanning: true, disableZooming: true });
            that._detachEventHandlers();

            points = new Array();
            var firstPoint = null;
            var lastPoint = null;
            var linePoints = new Array();
            var isCapturingMouse = false;

            var handleLineDrawingMousedown = function (e) {
                if (e.targetType !== "map") return;
                logger.log(e.eventName);
                var point = new MM.Point(e.getX(), e.getY());
                if (firstPoint === null) {
                    points.push(point);
                    linePoints = new Array();
                    linePoints.push(point);
                    firstPoint = point;
                    that._createPushpin(point);
                    that._addHandler(mapMousemoveEventName, handleLineDrawingMousemove);
                    isCapturingMouse = true;
                }
                else if (that._arePointsEqual(point, lastPoint)) {
                    logger.log("clicked on the last point");
                    linePoints = new Array();
                    linePoints.push(lastPoint);
                    that._addHandler(mapMousemoveEventName, handleLineDrawingMousemove);
                    isCapturingMouse = true;
                }

            }

            var handleLineDrawingMouseup = function (e) {
                if (!isCapturingMouse) return;
                //fix for chrome, ff and safari
                //if (e.targetType !== "map") return;
                logger.log(e);
                logger.log(e.eventName);
                if (mapMouseMoveEventHandler !== null) MM.Events.removeHandler(mapMouseMoveEventHandler);
                if (e.eventName !== 'mouseup') {
                    logger.log("unbinding fixer");
                    jqMapDiv.unbind('mouseup', fixerHandler);
                    return;
                }
                var point = new MM.Point(e.getX(), e.getY());
                if (!that._arePointsEqual(point, linePoints[0])) {
                    if (linePoints.length > 1) { linePoints.pop(); }
                    if (points.length > 2 && that._arePointsEqual(firstPoint, point)) {
                        point = new MM.Point(firstPoint.x, firstPoint.y);
                        points.push(point);
                        that._detachEventHandlers();
                        map.setOptions({ disablePanning: false, disableZooming: false });
                        logger.log("Closing Poly....");
                        that._createShape(points);
                        return; //done

                    }
                    lastPoint = point;
                    points.push(lastPoint);
                    that._createPushpin(lastPoint);
                    isCapturingMouse = false;
                }
                else {
                    logger.log("distance between points too short, removing first pushpin...");
                    linePoints.pop();
                    isCapturingMouse = false;
                    if (lastPoint === null) {
                        points.pop();
                        that._popPushpin();
                        that._detachEventHandlers();
                        map.setOptions({ disablePanning: false, disableZooming: false });
                        return;
                    }
                }
            }

            var handleLineDrawingMousemove = function (e) {
                if (!isCapturingMouse) return;
                if (e.targetType !== "map") return;
                logger.log(e.eventName);
                var point = new MM.Point(e.getX(), e.getY());
                var lastLinePoint = (linePoints.length == 1) ? linePoints[0] : linePoints[linePoints.length - 2];
                logger.log(linePoints.length);
                logger.log(point + "," + lastLinePoint);
                if (!that._arePointsEqual(point, lastLinePoint)) {
                    var withReplace = linePoints.length > 1;
                    if (withReplace) linePoints.pop();
                    linePoints.push(point);
                    that._createLine(lastLinePoint, point, withReplace, polyDrawingStyle);
                }
            }

            that._addHandler(mapMousedownEventName, handleLineDrawingMousedown);
            that._addHandler(mapMouseupEventName, handleLineDrawingMouseup);
            //fix for chrome, ff and safari
            var fixerHandler = function () { handleLineDrawingMouseup(event); };
            jqMapDiv.bind('mouseup', fixerHandler);
        },

        _addHandler: function (evtName, handler) {
            logger.log("_addHandler " + evtName);
            switch (evtName) {
                case 'mousedown':
                    mapMouseDownEventHandler = MM.Events.addHandler(map, evtName, handler);
                    break;
                case 'mouseup':
                    mapMouseUpEventHandler = MM.Events.addHandler(map, evtName, handler);
                    break;
                case 'mousemove':
                    mapMouseMoveEventHandler = MM.Events.addHandler(map, evtName, handler);
                    break;
                //                case 'click':
                //                    mapClickEventHandler = MM.Events.addHandler(map, evtName, handler);
                //                    break;
            }
        },

        _arePointsEqual: function (point1, point2) {
            var deltaX = point1.x - point2.x;
            var deltaY = point1.y - point2.y;
            var distance = 0.5 * (Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)));
            logger.log("distance - dx=" + deltaX + ", dy=" + deltaY + ": distance=" + distance + ", step=" + step);
            return distance < step;
        },

        _createPushpin: function (point) {
            logger.log("_createPushpin");
            var loc = map.tryPixelToLocation(point);
            pushpins.push(new MM.Pushpin(loc));
        },

        _popPushpin: function () {
            pushpins.pop();
        },

        _createLine: function (point1, point2, withReplace, drawingStyle) {
            logger.log("_createLine");
            var lineVertices = new Array(map.tryPixelToLocation(point1), map.tryPixelToLocation(point2));
            if (withReplace) polylines.pop();
            polylines.push(new MM.Polyline(lineVertices, (!drawingStyle) ? lineDrawingStyle : drawingStyle));
        },

        _createShape: function (shapePoints, shapeStyle) {
            logger.log("_createShape");
            var locations = new Array();
            for (var i = 0; i < shapePoints.length; i++) {
                var loc = map.tryPixelToLocation(shapePoints[i]);
                locations.push(loc);
                logger.log("Point: " + shapePoints[i].x + ", " + shapePoints[i].y);
                logger.log("Loc: " + loc.longitude + ", " + loc.latitude);
            }
            shapes.push(new MM.Polygon(locations, (!shapeStyle) ? polyFillStyle : shapeStyle));
        },

        _detachEventHandlers: function () {
            logger.log("_detachEventHandlers");
            //if (mapClickEventHandler !== null) MM.Events.removeHandler(mapClickEventHandler);
            if (mapMouseDownEventHandler !== null) MM.Events.removeHandler(mapMouseDownEventHandler);
            if (mapMouseUpEventHandler !== null) MM.Events.removeHandler(mapMouseUpEventHandler);
            if (mapMouseMoveEventHandler !== null) MM.Events.removeHandler(mapMouseMoveEventHandler);
        },
        destroy: function () {
            logger.log("destroy");
            $.Widget.prototype.destroy.call(this);
            this.mapDiv.remove();
        }
    });
} (jQuery));

The code should be self-explanatory (at leats I hope so).

Bing Maps Ajax Control jQuery Widget in Action

Bing Maps Ajax Control jQuery Widget in Action

In the next post I will add rectangle and circle drawing.

This entry was posted in Ajax, Bing Maps, Javascript, jQuery, Widget and tagged , , , . Bookmark the permalink.

Comments are closed.