The Bing Spatial Data Services (SDS) allow you to submit large amounts of addresses for batch-geocoding as well as GPS-coordinates for reverse geocoding. You can download the results or keep them in our data centers and retrieve your points of interest (POI) within a distance of a specific location or along a route through the SDS Query API. Aside from your own POI which you can manage through the SDS, you can also query POI from our public data sources which are grouped into categories.
These API and data are useful for a variety of scenarios and are most often used in typical “locator” scenarios where you want to find stores, dealerships, etc.
Today we are beginning to preview a new capability of the SDS which allows you to not only retrieve points but also polygons. Initially, this includes boundaries for countries, administrative levels and more. This new capability is exposed through the GeoData API and could be useful to highlight areas of interest…
…or create “thematic maps” where you color-code regions based on key performance indicators (KPI) such as the revenue, number of customers, crime statistics, environmental data, etc.
You’ll find the preliminary documentation for the preview here. So, rather than talking about different parameters let’s go straight into a sample where we retrieve a polygon and visualize it in Windows Store app.
Prerequisites
Since we are developing a Windows Store App, we require access to a Windows 8 machine as well as Visual Studio 2012. A free version of Visual Studio Express 2012 for Windows 8 is available here.
The “Bing Maps SDK or Windows Store Apps” can be installed directly from Visual Studio by selecting “Extensions and Updates” from the menu “Tools” and searching in the online gallery.
We will also require a Bing Maps Key. If you don’t have one, you can follow these instructions to get a free trial or basic key.
Getting Started
We start by creating a new project. For this example, we select the template for JavaScript applications in the Windows Store and create a ‘Blank App’.
Next we add a reference to the ‘Bing Maps for JavaScript’ control.
Let’s code
In the head of default.html we add script-references to load the Bing Maps core as well as additional modules.
<!-- Bing Maps --><scriptsrc="/Bing.Maps.JavaScript/js/veapicore.js"></script><scriptsrc="/Bing.Maps.JavaScript/js/veapiModules.js"></script>
In the body of default.html we just add a div-element that will host our map.
<divid="divMap"></div>
Moving on to the JavaScript default.js we add a few global variables for the map, the base-URL for the GeoData API as well as a list of safe characters. We will come back to these safe characters later.
var map = null;var baseUrl = "http://platform.bing.com/geo/spatial/v1/public/geodata?SpatialFilter=";var safeCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";
Just underneath the line…
app.start();
…we add an event-handler that fires when the entire DOM content is loaded…
document.addEventListener("DOMContentLoaded", initialize, false);
…and executes the function initialize. The following code will be in between those two lines.
We start by loading the Bing Maps core and once this is done, we execute a function initMap.
function initialize() {
Microsoft.Maps.loadModule('Microsoft.Maps.Map', { callback: initMap });
}
The function initMap defines center-point and zoom-level of the map as well as a few other options and renders the map in the div-element we reserved for it. Then we move on to load the AdvancedShapes-module.
function initMap() {
try {
var mapOptions =
{
credentials: "Your Bing Maps Key",
mapTypeId: "r",
enableClickableLogo: false,
enableSearchLogo: false,
center: new Microsoft.Maps.Location(47.490860, -121.835747),
zoom:9
};
map = new Microsoft.Maps.Map(document.getElementById("divMap"), mapOptions); loadAdvancedShapeModule();
}
catch (e) {
var md = new Windows.UI.Popups.MessageDialog(e.message);
md.showAsync();
} }
Loading the AdvancedShape-module may not be necessary, but it will be useful whenever you want to render polygons with holes. If you wanted to render, for example, the polygon that represents Italy, but exclude the enclosed areas representing San Marino and Vatican City, you would require the AdvancedShape-module.
When the module is loaded, we execute a function that calls the GeoData API and retrieves the polygon.
function loadAdvancedShapeModule() {
Microsoft.Maps.loadModule('Microsoft.Maps.AdvancedShapes', { callback: getBoundary
});
}
Notice that we retrieve the credentials from the map rather than just re-using the same key that we specified for the map. By doing so we generate a session-key which will indicate to the transaction counting and reporting system in our backend that this call originated within a map control. Such transactions appear as “non-billable” in your usage reports. For more information on this particular aspect, see Viewing Bing Maps Usage Reports.
When we call the GeoData API, we can provide either an address string or a latitude and longitude as parameter. In this case, we use the string “King County”. We also specify a few more parameters which you will find explained in more detail in the documentation before we execute the request.
function getBoundary() {
map.entities.clear(); map.getCredentials(function (credentials) {
var boundaryUrl = baseUrl
+ "GetBoundary('King County',1,'AdminDivision2',0,0,'en','US')&$format=json&key="
+ credentials;
WinJS.xhr({ url: boundaryUrl }).then(boundaryCallback);
});
}
To optimize for performance, the response is highly compressed and therefore we call a function ParseEncodedValue for each polygon to decompress it. We also define stroke- and fill-color before we add the polygon to the map.
function boundaryCallback(result) {
result = JSON.parse(result.responseText);var entity = result.d.results[0];
var entityMetadata = entity.EntityMetadata;
var entityName = entity.Name.EntityName;
var primitives = entity.Primitives;var polygoncolor = null;
var strokecolor = null;
var boundaryVertices = null;
var numOfVertices = 0; polygoncolor = new Microsoft.Maps.Color(100, 128, 128, 128);
strokecolor = new Microsoft.Maps.Color(255, 128, 128, 128);var polygonArray = new Array();
for (var i = 0; i < primitives.length; i++) {
var ringStr = primitives[i].Shape;var ringArray = ringStr.split(",");for (var j = 1; j < ringArray.length; j++) {
var array = ParseEncodedValue(ringArray[j]);if (array.length > numOfVertices) {
numOfVertices = array.length;
boundaryVertices = array;
}
polygonArray.push(array); }var polygon = new Microsoft.Maps.Polygon(polygonArray,
{ fillColor: polygoncolor, strokeColor: strokecolor });
map.entities.push(polygon)
}
}
The compression algorithm is the same as the one we documented for the Elevations API.
function ParseEncodedValue(value) {
var list = new Array();
var index = 0;
var xsum = 0;
var ysum = 0;var max = 4294967296;while (index < value.length) {
var n = 0;var k = 0;while (1) {
if (index >= value.length)
{
return null;
}
var b = safeCharacters.indexOf(value.charAt(index++));
if (b == -1) {
return null;
}
var tmp = ((b & 31) * (Math.pow(2, k)));var ht = tmp / max;var lt = tmp % max; var hn = n / max;var ln = n % max; var nl = (lt | ln) >>> 0;
n = (ht | hn) * max + nl;
k += 5;
if (b < 32) break; }var diagonal = parseInt((Math.sqrt(8 * n + 5) - 1) / 2);
n -= diagonal * (diagonal + 1) / 2;
var ny = parseInt(n);
var nx = diagonal - ny;
nx = (nx >> 1) ^ -(nx & 1);
ny = (ny >> 1) ^ -(ny & 1);
xsum += nx;
ysum += ny;
var lat = ysum * 0.00001;
var lon = xsum * 0.00001
list.push(new Microsoft.Maps.Location(lat, lon));
}
return list;
}
And that’s it.
We have a lot of ideas on what we want to add in the future, but we always welcome your feedback on what works well and what else you would like to see. Please let us know via the forum.