Creating Heat Maps with Bing Maps and Dynamics CRM

The majority of data in the enterprise today has a location component, including most sales, operational, or service-related data. By visualizing our business data on a map, we can identify trends by geography,
and use them to make smarter business decisions. In this blog post, we will show how you can visualize business data within Microsoft Dynamics CRM on Bing Maps in the form of a heat map, to identify hotspots of customers, leads, service requests and more. Dynamics CRM provides a powerful general purpose framework for developing line-of-business applications, and we will use Dynamics CRM Online, the Bing Maps AJAX v7 control,
and a HeatMap module to create our visualization.

What are heat maps? Wikipedia defines a heat map as “a graphical representation of data where the individual values contained in a matrix are represented as colors”. In a geographic mapping context, heat maps
can provide a visualization of the relative density of point data:

 Creating Heat Maps with Bing Maps and Dynamics CRM_第1张图片

We will now show how we can bring this method of visualization into Dynamics CRM through the use of Web Resources and Bing Maps, to show a heat map of our CRM Opportunities, weighted by the Estimated Value, to
give us a view into our potential revenue opportunity by geography.

Note that the code samples below assume that your data in CRM has been geocoded, with latitudes and longitudes populated for the entities you wish to visualize. If your data does not already have coordinates populated, you can leverage the Bing Maps Locations API to geocode individual addresses, or alternately, the Bing Maps Spatial Data Services GeocodeDataflow API to geocode your data in batches.

Creating our Heat Map Web Resource:

We will present our mapping visualization in the context of Dynamics CRM Online through the use of Web Resources. We will create one HTML Web Resource for our map, and we will also leverage a JavaScript Web Resource
to host the Heat Map module that enables the heat spot visualization.

Our HTML Web Resource will host our map, and retrieve the entity data from Dynamics CRM Online, using the REST Endpoint for Web Resources. When creating our HTML Web Resource, we must use the appropriate
DOCTYPE declaration, add a META element with the charset attribute set to "utf-8", and include the appropriate JavaScript resource files:

  • The Bing Maps AJAX v7 Map Control (we must include the ‘s=1’ parameter in the link, which is required for https access)=
  • jQuery The ClientGlobalContext.js.aspx page, for access to the server URL for our REST query

<!DOCTYPE html   PUBLIC "-//W3C//DTD   XHTML 1.0 Transitional//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html><head>

    <title>Bing Map Heatmap</title>

    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>

    <style type="text/css">

        body
         {

            margin:0 0 0 0;

        }  

    </style>

    <script type="text/javascript" charset="UTF-8"
  src="https://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0&s=1">

    </script>

    <script type="text/javascript" src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.2.js"></script>

    <script type="text/javascript" src="ClientGlobalContext.js.aspx"></script>

 Our HTML will be very basic: just a placeholder DIV for themap, which will occupy the entire page. Note that we will call our getMap() function when the page loads:

<body onload="getMap();">

    <div id="map" style="width: 100%; height: 100%;" />
 

</body>

</html>

 

Instantiating our Map:

In our Web Resource file, we will add JavaScript code to instantiate our map. First, we define a number of global variables, to be used as we load the map and retrieve our data. Note that we use the Client Global Context to retrieve the server URL, which we will use as part of our REST request for CRM entity data, to enable portability of our Web Resource

The getMap() function gets called when the page loads, and at this time, we will instantiate our map, and then register and load a module which will enable our heat map visualization. When loading the module, we define a callback function in which we construct our query to retrieve Opportunity data from the CRM REST endpoint, and then issue an AJAX request. Note how we are also extending the Microsoft.Maps.Location class by adding a multiplier property, which we will use to weight our heat map:

//CRM Server Url:

var serverUrl =   GetGlobalContext().getServerUrl();

// Configure   global variables:

var map = null;

   

// Set up arrays   to hold opportunity query results and locations for heatmapping:

var opportunityResults = new Array;

var opportunityLocations = new Array();

 

// Heat map   layer:

var heatmapLayer = null;

 

// Called on load, we instantiate the map, and retrieve all of our location data from CRM:

function getMap() {

 

    //
  Instantiate map, and show world view:

    var   mapOptions = {

        credentials: "insert key here",

        zoom: 2,

        center: new Microsoft.Maps.Location(36, -40)

    };

    map = new   Microsoft.Maps.Map(document.getElementById('map'), mapOptions);

 

    // Extend location to allow for revenue-weighted heatmaps:

   
  Microsoft.Maps.Location.prototype.multiplier = null;

 

    // Register and load the Client Side HeatMap Module

    Microsoft.Maps.registerModule("HeatMapModule", "./new_heatmap_module");

    Microsoft.Maps.loadModule("HeatMapModule", { callback: function () {

        // Once the module is loaded, retrieve the opportunity data:

        // Define REST query for Opportunity data:

        var oppQuery = "OpportunitySet?$select=EstimatedValue,"
  +

                        "opportunity_customer_accounts/Address1_Latitude,"
  +

                        "opportunity_customer_accounts/Address1_Longitude&"
  +

 
                        "$expand=opportunity_customer_accounts";

        // Load Opportunity Data:

        var requestUrl = serverUrl + "/XRMServices/2011/OrganizationData.svc/" + oppQuery;  makeAjaxRequest(requestUrl, opportunityResults, opportunityCallback);

        }

    });

 }

 } 

 

Our query against the REST endpoint retrieves the EstimatedValue of each opportunity, and also uses the $expand OData option to enable us to also retrieve the Latitude and Longitude of the Account to which this Opportunity relates. Note how we are passing three parameters to the makeAjaxRequest function:

  • The URL to use in our request to the REST endpoint
  • The Array we want to populate with our resulting Opportunity data
  • The callback function to call on completion

Retrieving our Opportunity Data:

We will retrieve all of our Opportunity data through the use of the jQuery ajax method. Note that there is a maximum of 50 records returned per request to the CRM REST endpoint. If there are more than 50 Opportunity records, there will be a JSON__next property at the end of the result set. The URL value in that node can be used to retrieve the next set of records. Once all records have been obtained, the callback function (opportunityCallback) is called:

// issue ajax request, populate specified array with results, then call specified callback function:

function makeAjaxRequest(requestUrl, resultArray, callbackFunction) {

    $.ajax(

    {

        type: "GET",

        url: requestUrl,

        contentType: "application/json; charset=utf-8",

        dataType: "json",

        error: function (request, textStatus, errorThrown) {

            alert("Error occurred: " + request.responseXML);

            return;

        },

        success: function   (data) {

            var results = data.d["results"];

            for (resultKey in results) {

                // Add results to appropriate array:

               
  resultArray.push(results[resultKey]);

            }

            //Check to see if we have all of the data:

            if (null != data.d.__next) {

                // If not, issue an additional request:

                makeAjaxRequest(data.d.__next,   resultArray, callbackFunction);

            } else
  {

                //   Call callback function:

                callbackFunction();

            }

        }

    });

}

 

Adding our Heat Map Layer:

Now that we have all of our Opportunity data, our callback function will create Microsoft.Maps.Location objects with each Opportunity, and add a multiplier property, calculated based on the Estimated Value of the Opportunity. All
Locations are added to an array, which is then used to create our Heat Map Layer with the Heatmap Module.

This module was created by Alastair Aitchison, Microsoft MVP, and is shared on CodePlex as part of the Bing Maps V7 Modules project. Note that this module is dependent on the HTML5 canvas element. For Internet
Explorer, this means IE9+ will be needed to view the heat map.

The module accepts several options when creating the heat map layer, in addition to the map and locations parameters:

  • Intensity:  a value between 0 and 1 which dictates the central opacity of each heat spot
  • Radius: the radius, in meters, of each heat spot. Note that we are altering the radius of each heat spot with our weighting based on Estimated Value
  • Unit: we are specifying a radius in meters, but it is also possible to specify a radius in pixels. By using meters, we ensure the heat spots scale with the map as we zoom
  • Colour Gradient: specifies which colors to use to represent the hottest, coolest, and intermediate areas

// Add   Opportunities heatmap layer:

var opportunityCallback = function addOpportunityHeatmap() {

     for (var j = 0; j < opportunityResults.length; j++) {

        var   item = opportunityResults[j];

         //  Retrieve item values:

        var lat = item["opportunity_customer_accounts"].Address1_Latitude;

        var lon = item["opportunity_customer_accounts"].Address1_Longitude;

        var estimatedValue = item["EstimatedValue"].Value;

         // If lat and long available, construct pushpin and add to map:

        if   (lat && lon && lat != 0 && lon != 0) {

            // Create location:

            var location = new Microsoft.Maps.Location(lat, lon);

 

            //   Add a nominal multiplier value to the location, for revenue-based   heatmapping:

            if   (estimatedValue >= 0) {

                var   locMultiplier = estimatedValue / 2000000;

                location.multiplier =   locMultiplier;

            }

 

            // Add location to appropriate array for heatmapping:           
 

opportunityLocations.push(location);

 

        }

    }

 

    //
  Construct heatmap layer, using heatmapping module:

    heatmapLayer = new HeatMapLayer(

      map,

        [],

        { intensity: 0.7,

            radius: 500000,

            unit: 'meters',

            colourgradient: {

                0.0: 'green',

                0.5: 'yellow',

                1.0: 'red'

            }

        });

       
  heatmapLayer.SetPoints(opportunityLocations);

 

}

 

Minor Update to Heat Map Module:

In this example, I have made a minor modification to the heat map module, to allow the multiplier we set for each Location to weight the radius used for each heat spot.

// Main method   to draw the heatmap

function _createHeatMap() {

    // Ensure   the canvas matches the current dimensions of the map

    // This   also has the effect of resetting the canvas

    _canvas.height = _map.getHeight();

    _canvas.width = _map.getWidth();

 

    _canvas.style.top = -_canvas.height / 2 + 'px';

    _canvas.style.left = -_canvas.width / 2 +'px';

 

    // Calculate the pixel radius of each heatpoint at the current map zoom

    if (_options.unit == "pixels") {

        radiusInPixel = _options.radius;

    } else {

        radiusInPixel = _options.radius / _map.getMetersPerPixel();

    }

     var ctx =_canvas.getContext("2d");

 

    var shadow = 'rgba(0, 0, 0, ' + _options.intensity + ')';

 

    // Create the Intensity Map by looping through each location

    for (var i = 0, len = _locations.length; i < len; i++) {

        var loc = _locations[i];

        var pixloc = _map.tryLocationToPixel(loc, Microsoft.Maps.PixelReference.control);

        var x = pixloc.x;

        var y = pixloc.y;

 

        // MODIFICATION: Use location multiplier against the radius, if one exists:

        var weightedRadius = null;

        if (loc.multiplier != null && loc.multiplier > 0) {

            weightedRadius = loc.multiplier *  radiusInPixel;

        } else
  {

            weightedRadius = radiusInPixel;

        }

 

        // Create radial gradient centred on this point

        var grd = ctx.createRadialGradient(x, y, 0, x, y, weightedRadius);

        grd.addColorStop(0.0, shadow);

        grd.addColorStop(1.0, 'transparent');

 

        // Draw the heatpoint onto the canvas

        ctx.fillStyle = grd;

        ctx.fillRect(x - weightedRadius, y - weightedRadius, 2 * weightedRadius, 2 * weightedRadius);

    }

 

    // Apply the specified colour gradient to the intensity map

    _colouriseHeatMap();

 

    // Call the callback function, if specified

    if
  (_options.callback) {

        _options.callback();

    }

}

 

 

Uploading our Web Resources:

We can now upload our two Web Resources, and publish them both. The main HTML page will be of type ‘Web Page (HTML)’ and the heat map module will be of type ‘Script (JScript)’. We must ensure the name we choose for the Heat
Map Module JavaScript resource is used in our link to the module in our HTML Web Resource.

Creating Heat Maps with Bing Maps and Dynamics CRM_第2张图片

We can now test our heat map by clicking the ‘URL’ to our HTML Web Resource above:

You can now add a link to your heat map with the method you desire, such as using a ribbon button, or using a link from a dashboard. You can create heat maps of accounts, opportunities, leads, service cases, and any other entity that has a location component to it, to help visualize and understand trends in your data by geography, and drive smarter business decisions.

The complete code can be found here.

Geoff Innis

Bing Maps Technical Specialist