Quantcast
Channel: Maps Blog
Viewing all 102 articles
Browse latest View live

Spatial Data Services: Job History & Data Source Options

$
0
0

New job history! Have you ever wanted to check status of your geocode and data source jobs and see what jobs you’ve run recently? Bing Spatial Data Services now makes it easy to view your job history. With the new ListJobs API , you can return a list of all geocode jobs and jobs that made changes to data sources in the last 15 days for any Bing Maps Account. Pending jobs are listed first. All you need to do is provide a Bing Maps Key from your Bing Maps Account with the following URL.

http://spatial.virtualearth.net/REST/v1/dataflows/listjobs?key=Insert_Your_Bing_Maps_Key_Here

A JSON response is returned by default. Add “output(o)=xml” to return XML. Jobs that download data source data do not make changes to your data source and therefore are not part of this list.

More __Distance query parameter options. Want to simply get all your entity data plus the distance to each entity? Bing Spatial Data Services now accepts the wildcard character with the __Distance propertywhen you Query by Area. Just specify $select=*,__Distance in your request. The distance option has also been expanded to support bounding box (rectangular region) queries. For bounding box queries, the distance returned for each entity is the distance from the center of the bounding box.

Make your data source public! Want to share your data with the world? Bing Spatial Data Services now gives you the option to make your data source public so that anyone with a Bing Maps Key can access. All you have to do is a Make a Data Source Public request as shown below. Set the setpublic parameter to true, and your data is publicly shared.

http://spatial.virtualearth.net/REST/v1/data/accessID/dataSourceName/$updateDataSource?setPublic=trueOrFalse&key= Insert_Your_Master_Key_Here

You can always make a data source private again by making the request again and setting the setpublic parameter to false. This parameter is also available when you Create or update your data source.

Delete selected entities with the new deleteEntity property. Want the ability to delete specific entities when you update your data source while leaving the rest of your data source intact? Bing Spatial Data Services can now incrementally delete entities when you use the __deleteEntityproperty in your schema and set it to 1 or true for each entity you want to remove when you update your data source.

We hope you enjoy these new features!

-The Bing Maps Team


Venue Maps in Windows Store Apps (JavaScript)

$
0
0

Within Bing Maps you have the ability to view Venue Maps. Venue Maps are often thought of as maps of indoor structures such as malls and airports; however, venue maps can be opened up to so much more. A few examples of other types of venue maps include: the layout of shopping districts, stadiums, race courses, and universities.

Using Venue Maps in Bing Maps requires loading in the Venue Map module. If you are not familiar with modules in Bing Maps take a look at the blog post on Bing Maps Modules in Windows Store Apps.

In this blog post, we are going to create a simple application that when zoomed in, will load in nearby venues into Bing Maps in your Windows Store App as you pan the map around.

Setting up the project

To set up the project, open Visual Studios 2012 and create a new project. In the window that opens select JavaScript -> Windows Store. Select the Blank App template, and call the application VenueMaps_JS and press OK.

To keep things clean we are going to create a separate JavaScript file for our application. Right click on the js folder and select Add -> New Item. Create a new JavaScript file called VenueMaps.js.

At this point your Solution should look something like this:

 

clip_image001

 

Next, add a reference to the Bing Maps JavaScript library. To do this, right click on the References folder and press Add Reference. Select Windows -> Extensions select Bing Maps for JavaScript. If you do not see this option ensure that you have installed the Bing Maps Windows Store App SDK.

Now we can open up the default.html file and add references to the Bing Maps SDK along with references to the Bing Maps modules API and our JavaScript file. While we are at it, we will also add a div to the body of the page where we want the map control to load. Your default.html file should look like this:

 

<!DOCTYPEhtml>

<html>

<head>

    <metacharset="utf-8"/>

    <title></title>

 

    <!-- WinJS references -->

    <linkhref="//Microsoft.WinJS.1.0/css/ui-dark.css"rel="stylesheet"/>

    <scriptsrc="//Microsoft.WinJS.1.0/js/base.js"></script>

    <scriptsrc="//Microsoft.WinJS.1.0/js/ui.js"></script>

 

    <!-- VenueMaps_JS references -->

    <linkhref="/css/default.css"rel="stylesheet"/>

    <scriptsrc="/js/default.js"></script>

 

    <!-- Bing Maps references -->

    <scripttype="text/javascript"

            src="ms-appx:///Bing.Maps.JavaScript//js/veapicore.js"></script>

    <scripttype="text/javascript"

        src="ms-appx:///Bing.Maps.JavaScript//js/veapimodules.js"></script>

 

    <!-- Our Venue Maps JavaScript Code -->

    <scriptsrc="/js/VenueMaps.js"></script>

 

    <styletype="text/css">

        div {

            color:#000;

        }

    </style>

</head>

<body>

    <divid="myMap"></div>

</body>

</html>

Loading the Bing Maps Control

We will add the functionality to load the Bing Maps control to the VenueMap.js file. To start, we are going to create six global variables called: map, _mapBounds, _zoom, venueFactory, loadedVenues, and loadedVenueIds. Then, we will create a function called GetMap which will load the Bing Maps control for us. When loading the Bing Maps control we will also turn on the breadcrumb. The breadcrumb allows us to select different floors when viewing a venue. Inside of this function we will load in the Venue Maps module and initialize the venueFactory object. Now we will add a throttled event handler for when the map has finished moving that will get nearby venues. The GetMap function will be loaded after the Microsoft.Maps.Map module has completed loading. Your code should look like this:

 

var map,

    loadedVenues = [],

    loadedVenueIds = [],

    _mapBounds,

    _zoom,

    venueFactory;

 

function GetMap() {

    var mapOptions =

    {

        credentials: "YOUR_BING_MAPS_KEY",

        zoom: 2,

        showBreadcrumb : true

    };

 

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

 

    //Load the venue map module

    Microsoft.Maps.loadModule('Microsoft.Maps.VenueMaps', {

        callback: function () {

            venueFactory = new Microsoft.Maps.VenueMaps.VenueMapFactory(map);

 

            //Use a throttled event to reduce the number of unwanted events being fired.

            Microsoft.Maps.Events.addThrottledHandler(map, 'viewchangeend', GetNearbyVenues, 250);

        }

    });

}

 

//Initialization logic for loading the map control

(function () {

    function initialize() {

        Microsoft.Maps.loadModule('Microsoft.Maps.Map', { callback: GetMap });

    }

 

    document.addEventListener("DOMContentLoaded", initialize, false);

})();

Adding the Application Logic

When the veiwchangeend event handler is fired, it will trigger a function called GetNearbyVenues. This function will first loop through all loaded venues and dispose those that are out of view or all of them if the zoom level is less than 14. When disposing a venue we will also delete the stored venue id in the loadedVenueIds array. If the zoom level is greater or equal to 14 we will do a search for all venues that are within 4000 meters of the center of the map.

 

function GetNearbyVenues() {

    _mapBounds = map.getBounds();

    _zoom = map.getZoom();

 

    //Dispose a venue if it is outside of the map view or if the zoom  level is too high

    var venuesInView = [], venue;

 

    for (var i = 0; i < loadedVenues.length; i++) {

        venue = loadedVenues[i];

 

        if (_zoom < 14 || !_mapBounds.contains(venue.center)) {

            delete loadedVenueIds[venue.id];

            venue.dispose();

        } else {

            venuesInView.push(venue);

        }

    }

 

    loadedVenues = venuesInView;

 

    if (_zoom >= 14) {

        // Search for venues that are within 4km of the center of the map

        venueFactory.getNearbyVenues({ map: map, location: map.getCenter(), radius: 4000, callback: DisplayNearbyVenues });

    }

}

 

When the search for nearby venues completes, a function called DisplayNearbyVenues will be fired. This function will loop through the venues that are returned and check if the venue has been loaded by checking the loadedVenueIds array. If the venue hasn’t been loaded we will create the venue and check to see if it is in the current map view and add both the venue and the venue id to the loadVenues and loadedVenueIds arrays. If it was loaded, we will then show it on the map. If it hasn't, we will dispose it. We will also attach a close event handler to the venue so that we can properly dispose it and remove any reference from our arrays.

 

function DisplayNearbyVenues(venues) {

    if (venues) {

        //Load new venues that are in view

        for (var i = 0; i < venues.length; i++) {

            if (loadedVenueIds[venues[i].metadata.MapId] == undefined) {

                venueFactory.create({

                    venueMapId: venues[i].metadata.MapId,

                    success: function (v) {

                        if (_mapBounds.contains(v.center)) {

 

                            //Handle close event of venue

                            Microsoft.Maps.Events.addHandler(v, 'close', function (e) {

                                delete loadedVenueIds[v.id];

                               

                                //Remove venue from loadedVenues array

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

                                    if (loadedVenues[j].id == v.id) {

                                        loadedVenues.splice(j ,ji+1);

                                        break;

                                    }

                                }

 

                                v.dispose();

                            });

 

                            loadedVenues.push(v);

                            loadedVenueIds[v.id] = true;

                            v.show();

                        } else {

                            v.dispose();

                        }

                    }

                });

            }

        }

    }

}

Running the App

At this point we have created all the functionality needed for nearby venues to be loaded as we navigate the map. If we run the app, zoom in and pan around in an area with the venue maps we should see the footprint of the venue load on the map. If you click on a venue footprint the map will zoom in on that venue and show the inside of the venue map. Here is a screen shot of the app with the venue map of Alder Hey Children’s Hospital located in Liverpool, UK.

 

Win8_Venue

- Ricky Brundritt, EMEA Bing Maps Technology Solution Professional

Geocoding and Routing in Bing Maps Windows Store Apps (JavaScript)

$
0
0

In this blog post we are going to look at how implement Geocoding and Routing using the Bing Maps Windows Store App JavaScript SDK. If you are new to JavaScript development with the Bing Maps Windows Store App SDK I recommend reading the Getting started with Bing Maps Windows Store Apps blog post first. We will also be making use of Bing Maps Modules. If you are unfamiliar with modules, I recommend reading the blog post: Modules in Bing Maps Windows Store Apps.

Geocoding is one of the most common tasks done by users of online maps. Geocoding is the process of taking an address or query and returning its equivalent coordinate on the map. Routing is the task of calculating the directions between two or more locations. In Bing Maps there are a lot of different options around routing such as routing by different modes of transportation; driving, walking, or transit. In Bing Maps there are several ways to carry out these tasks. The most common method is to use the Bing Maps REST services which are very easy to use from just about any programming language. In the JavaScript version of Bing Maps there a modules which wraps the REST services and exposes it as an easy to use JavaScript library. We will be making use of the Search module for geocoding and the Directions module for routing.

Setting up the project

To set up the project open Visual Studios 2012 and create a new project. In the window that opens select JavaScript -> Windows Store. Select the Blank App template, call the application BingMapsSearch_WinRTJS and press OK.

To keep things clean, we are going to create a separate CSS Style Sheet and JavaScript file for our application. To do this, right click on the js folder and select Add -> New Item. Create a new JavaScript file called BingMapsSearch.js. Then, right click on the css folder and select Add -> New Item and create a new Style Sheet called BingMapsSearch.css.

At this point your Solution should look something like this:

Win8_Project_Geo

Next, you’ll want to add a reference to the Bing Maps JavaScript library. Right click on the References folder and press Add Reference. Then select Windows -> Extensions and select Bing Maps for JavaScript. If you do not see this option, you may want to verify that you have installed the Bing Maps for Windows Store App SDK .

We can now open up the default.html file and add references to the Bing Maps SDK along with references to our CSS Style Sheet and JavaScript file. To do this, simply add the following in the head of the page:

<!-- Bing Maps references -->

<scripttype="text/javascript"src="ms-appx:///Bing.Maps.JavaScript//js/veapicore.js"></script>

<scripttype="text/javascript"src="ms-appx:///Bing.Maps.JavaScript//js/veapimodules.js"></script>

 

<!-- Our Bing Maps CSS & JavaScript Code -->

<linkhref="/css/BingMapsSearch.css"rel="stylesheet"/>

<scriptsrc="/js/BingMapsSearch.js"></script>


Creating the UI Layout

Before we worry about how to do all the programming of the logic required for this application, let’s first focus on creating the UI. For this app we are going to have a panel on the left side where all our input controls will reside and the map will fill up the rest of the space to the right. In the left frame we will have Clear Map button, an area for inputting geocode and route requests, and a div for rendering routing itineraries. The geocode input area will consist of a single textbox and a button. The routing input area will have two textboxes and a button. The HTML for the default.html file should look like this:

<!DOCTYPEhtml>

<html>

<head>

    <metacharset="utf-8"/>

    <title>BingMapsSearch_WinRTJS</title>

 

    <!-- WinJS references -->

    <linkhref="//Microsoft.WinJS.1.0/css/ui-dark.css"rel="stylesheet"/>

    <scriptsrc="//Microsoft.WinJS.1.0/js/base.js"></script>

    <scriptsrc="//Microsoft.WinJS.1.0/js/ui.js"></script>

 

    <!-- BingMapsSearch_WinRTJS references -->

    <linkhref="/css/default.css"rel="stylesheet"/>

    <scriptsrc="/js/default.js"></script>

 

    <!-- Bing Maps references -->

    <scripttype="text/javascript"

            src="ms-appx:///Bing.Maps.JavaScript//js/veapicore.js"></script>

    <scripttype="text/javascript"

            src="ms-appx:///Bing.Maps.JavaScript//js/veapimodules.js"></script>

 

    <!-- Our Bing Maps CSS & JavaScript Code -->

    <linkhref="/css/BingMapsSearch.css"rel="stylesheet"/>

    <scriptsrc="/js/BingMapsSearch.js"></script>

</head>

<body>

    <divclass="leftFrame">

        <!-- Clear Map Button-->

        <divstyle="height:30px;">

            <buttononclick="ClearMap();"class="greenBtn">Clear Map</button>

        </div>

 

        <!-- Geocoding Input -->

        <divstyle="height:100px;">

            <h2>Geocode</h2>

            <inputid="searchTbx"type="text"style="width:240px;"/>

            <buttononclick="Geocode();"class="greenBtn">Go</button>

        </div>

 

        <!-- Routing Input -->

        <divstyle="height:150px;">

            <h2>Route</h2>

            From: <inputid="fromTbx"type="text"style="width:200px;"/>

            To: <inputid="toTbx"type="text"style="margin-left:17px;width:200px;"/>

            <buttononclick="GetRoute();"class="greenBtn">Get Directions</button>

        </div>    

 

        <!-- Route Itinerary Area -->

        <divid="itineraryDiv"></div>

    </div>

 

    <!-- Map Area-->

    <divid="myMap"></div>

</body>

</html>

To keep things clean we will add some of the styling information into the BingMapsSearch.css file. Add the following to this file:

.leftFrame {

    position:absolute;

    height:100%;

    width:260px;

    padding:10px;

    background-color:#808080;

    overflow-y:auto;

}

 

.greenBtn {

    background-color:green;

    float:right;

    margin-right:15px;

}

 

#myMap {

    position:absolute;

    width:100%;

    height:100%;

    margin-left:280px;

}

 

#itineraryDiv {

    position:absolute;

    width:240px;

}

At this point we can run the application and see what it looks like. You should end up with something like this:

Win8_Geo_layout

Note that the map does not appear as we have not yet added the logic to load it. Also, since we haven’t wired up any of the functionality for the buttons, clicking on them now would throw an error.

Loading the Bing Maps Control

We will add the functionality to load the Bing Maps control to the BingMapsSearch.js file. We are now going to create three global variables called: map, searchManager and directionsManager. Next, we will create a function called GetMap which will load the Bing Maps control for us. The GetMap function will be loaded after the Microsoft.Maps.Map module has completed loading. Your code should look like this:

var map, searchManager, directionsManager;

 

function GetMap() {

    var mapOptions =

    {

        credentials: "YOUR_BING_MAPS_KEY",

        zoom: 2

    };

 

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

}

 

//Initialization logic for loading the map control

(function () {

    function initialize() {

        Microsoft.Maps.loadModule('Microsoft.Maps.Map', { callback: GetMap });

    }

 

    document.addEventListener("DOMContentLoaded", initialize, false);

})();


Adding the Geocoding Logic

When a user adds content to the searchTbx textbox and presses the Go button a request will be fired to a Geocode function. For this functionality we will be making use of the Search module. The first thing we will do in this function is check to see if the searchManager variable has been initialized. If it hasn’t, we will load the Search module and initialize this variable and then process the search request. If this variable has been initialized, we will then create a geocode request passing in the users input value and requests a maximum of 1 result. We will also pass in the names of callback functions that get called if the request is successful or not.

If the request is successful we will display the result on the map with a pushpin and zoom into the location. If it is not successful, we will alert the user. The request will then be geocoded against the search manager. I would like to take a moment to point out that the alert JavaScript function that is commonly used in web development is not supported in Windows Store Apps. Instead, we need to make use of the Windows.UI.Popups.MessageDialog class. And since this is likely something we will want to use regularly, we will create a simple function for displaying messages to the user called ShowMessage. Add the following code to the BingMapsSearch.js file.

function GeocodeModule() {

    ClearMap();

 

    if (searchManager) {

        var request = {

            where: document.getElementById('searchTbx').value,

            count:1,

            callback: geocodeCallback,

            errorCallback: geocodeError

        };

 

        searchManager.geocode(request);

    } else {

        //Load the Search module and create a search manager.

        Microsoft.Maps.loadModule('Microsoft.Maps.Search', {

            callback: function () {

                //Create the search manager

                searchManager = new Microsoft.Maps.Search.SearchManager(map);

 

                //Perfrom search logic

                Geocode();

            }

        });

    }

}

 

function geocodeCallback(response, userData) {

    if (response &&

        response.results &&

        response.results.length > 0) {

        var r = response.results[0];

        var l = new Microsoft.Maps.Location(r.location.latitude, r.location.longitude);

 

        //Display result on map       

        var p = new Microsoft.Maps.Pushpin(l);

        map.entities.push(p);

 

        //Zoom to result

        map.setView({ center: l, zoom : 15 });

    } else {

        ShowMessage("Geocode Response", "Not results found.");

    }

}

 

function geocodeError(request) {

    ShowMessage("Geocode Error", "Unable to Geocode request.");

}

 

function ShowMessage(title, msg) {

    var m = new Windows.UI.Popups.MessageDialog(title, msg);

    m.showAsync();

}


Adding the Routing Logic

When a user adds content to the fromTbx and toTbx textboxes and presses the Get Directions button a request will be fired to a GetRoute function. For this functionality we will be making use of the Directions module. The first thing we want to do in this function is to verify that the directionsManager variable has been initialized. If it hasn’t, we will load the Directions module and initialize this variable and then process the route request. If this variable has been initialized, we will then pass the start and end points to the directions manager and pass in the itinerary div id as the location to display the directions. We will then have the directions manager calculate the directions. Add the following code to the BingMapsSearch.js file.

function GetRoute() {

    ClearMap();

 

    if (directionsManager) {

        // Set Route Mode to driving

        directionsManager.setRequestOptions({ routeMode: Microsoft.Maps.Directions.RouteMode.driving });

 

        // Create start and end waypoints

        var startWaypoint = new Microsoft.Maps.Directions.Waypoint({ address: document.getElementById('fromTbx').value });

        var endWaypoint = new Microsoft.Maps.Directions.Waypoint({ address: document.getElementById('toTbx').value });

 

        directionsManager.addWaypoint(startWaypoint);

        directionsManager.addWaypoint(endWaypoint);

 

        // Set the id of the div to use to display the directions

        directionsManager.setRenderOptions({ itineraryContainer: document.getElementById('itineraryDiv') });

 

        // Calculate directions, which displays a route on the map

        directionsManager.calculateDirections();

    } else {

        //Load the Directions module and create a directions manager.

        Microsoft.Maps.loadModule('Microsoft.Maps.Directions', {

            callback: function () {

                //Create the directions manager

                directionsManager = new Microsoft.Maps.Directions.DirectionsManager(map);

 

                //Perfrom route logic

                GetRoute();

            }

        });

    }

}

The directions manager takes care of notifying the user if there is an issue calculating the route such as one of the route points having ambiguous results. This saves use a lot of time as we won’t have to worry about handling and developing this functionality. Additionally, this gives us the ability to easily drag the route if we want to customize it or avoid an area.

Clearing the Map

Whenever we process a new geocode or route request we will want to clear the map. We will also need to create a function named ClearMap as this is needed for the Clear Map button. When this function is called we will want to remove any entities from the map and reset the directions manager. This is rather simple to do, just add the following code to the BingMapsSearch.js file.

function ClearMap() {

    map.entities.clear();

 

    if (directionsManager) {

        directionsManager.resetDirections();

    }

}

Running the App

At this point we have created all the functionality needed for the UI we created originally. If you run the app now you can make geocode and routing requests. Here is a screen shot of the app with a route from New York to Toronto.

Win8_Geo_complete

- Ricky Brundritt, EMEA Bing Maps Technology Solution Professional

3D Elevation Models with Bing Maps WPF

$
0
0

With the release of the Bing Maps REST Elevations service I started looking into cool and interesting things that can be done with the service. While doing some searching around, I stumbled across an interesting blog post titled Examining 3D Terrain of Bing Maps Tiles with SQL Server 2008 and WPF by one of our Bing Maps MVP’s which inspired me to see if I could make something similar using this new Elevations service. So with that, I’ve put together this blog posts which demonstrates how to create a tool for generating a 3D model of elevation data and then overlay static imagery over the top. As a teaser, here is a screenshot of a 3D model of Niagara Falls created using this code.

3DNiagraFalls

Setting up the Visual Studio Project

To start, we will create a WPF Application project in Visual Studios called BingMaps3DModel_WPF. Once this is done we will want to add references to the following libraries:

  • System.Runtime.Serialization
  • Microsoft.Maps.MapControl.WPF

Adding support for the REST based Elevation service

Since we will be accessing the Bing Maps REST Elevation service from .NET code we will need to add in a library to parse the responses from the service. Rather than writing these from scratch I’ll be making use of some code I helped put together in a previous MSDN article on using Bing Maps REST Service with .NET Libraries. To include this library into the project we will right click on the project and select Add -> New Item. Add a class file called BingMapsRESTServices.cs. Remove any content that’s in this file and copy and paste in the complete code from the bottom of the previous blog post. At this point your project should look something like this:

clip_image004

Creating the User Interface

For this application we will want to have two tabs. The first tab will have a map that the user will be able to use to select what area the 3D model should be created for. Once the user has selected the area they are interested in they will be able to press a button to generate the 3D model. Once the model is created the user will be taken to the second tab which will allow the user to view and interact with the 3D model. To make things a bit cleaner we will create a separate user control for the 3D models tab. To do this, right click on the project and select Add -> New Item. Select “User Control (WPF)” and call it ViewerPanel3D.xaml.

With this, we can create the markup for the MainWindow.xaml file. The XAML should look like this.

<Windowx:Class="BingMaps3DModel_WPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:m="clr-namespace:Microsoft.Maps.MapControl.WPF;assembly=Microsoft.Maps.MapControl.WPF"
xmlns:local="clr-namespace:BingMaps3DModel_WPF"
Title="3D Map Generator"Height="700"Width="800">
<Grid>
<TabControlName="MyTabs">
<TabItemHeader="Map">
<Grid>
<m:MapName="MyMap"CredentialsProvider="YOUR_BING_MAPS_KEY"Mode="AerialWithLabels"/>
<ButtonContent="Generate 3D Map Model"Click="Generate3DModel_Click"
Width="150"Height="25"Margin="10"
HorizontalAlignment="Right"VerticalAlignment="Top"/>
</Grid>
</TabItem>
<TabItemHeader="3D Model">
<local:ViewerPanel3Dx:Name="viewerPanel"/>
</TabItem>
</TabControl>
</Grid>
</Window>

If you run the application now, it should result in an application that looks like this:

3DWPFMap

Note: you may get an error if you haven’t created an instance of the click event for the button. Simply right click on the event name and press “Navigate to Event Handler” to generate the click event code that is needed.

The ViewerPanel3D User Control

We can now turn our attention to the ViewerPanel3D user control that we created and add the needed markup to that. When a model is loaded into this control we will want to give the user several sliders which they can use to rotate and move the model around with. I came across a really nice example in this blog post that I thought I would use as a starting point. The nice thing about this example is that it binds the sliders to the rotation transform which means there is no code needed in the background for this functionality. In addition to the sliders for rotating the model we will add a set of sliders for translating (moving in different directions) the model. We will also add a mouse wheel event for zooming in and out of the model. Putting this all together the markup for the ViewerPanel3D.xaml file should look like this:

<UserControlx:Class="BingMaps3DModel_WPF.ViewerPanel3D"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300"d:DesignWidth="300">

<UserControl.Resources>
<StyleTargetType="{x:Type TextBlock}">
<SetterProperty="HorizontalAlignment"Value="Center"/>
<SetterProperty="VerticalAlignment"Value="Center"/>
</Style>
<Stylex:Key="slider">
<SetterProperty="Slider.Orientation"Value="Vertical"/>
<SetterProperty="Slider.Height"Value="130.0"/>
<SetterProperty="Slider.HorizontalAlignment"Value="Center"/>
<SetterProperty="Slider.VerticalAlignment"Value="Center"/>
</Style>
</UserControl.Resources>

<GridBackground="Gray"MouseWheel="OnViewportMouseWheel">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinitionWidth="100"/>
</Grid.ColumnDefinitions>
<Viewport3DName="viewport"Grid.Row="0"Grid.Column="0">
<Viewport3D.Camera>
<PerspectiveCamerax:Name="camera"FarPlaneDistance="50"
NearPlaneDistance="0"LookDirection="0,0,-10"UpDirection="0,1,0"
Position="0,0,5"FieldOfView="45">
<PerspectiveCamera.Transform>
<Transform3DGroup>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D
Axis="1.0, 0.0, 0.0"
Angle="{Binding ElementName=sliderX, Path=Value}"/>
</RotateTransform3D.Rotation>
</RotateTransform3D>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D
Axis="0.0, 1.0, 0.0"
Angle="{Binding ElementName=sliderY, Path=Value}"/>
</RotateTransform3D.Rotation>
</RotateTransform3D>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D
Axis="0.0, 0.0, 1.0"
Angle="{Binding ElementName=sliderZ, Path=Value}"/>
</RotateTransform3D.Rotation>
</RotateTransform3D>
<TranslateTransform3D
OffsetX="{Binding ElementName=transSliderX, Path=Value}"
OffsetY="{Binding ElementName=transSliderY, Path=Value}"
OffsetZ="{Binding ElementName=transSliderZ, Path=Value}"/>
<ScaleTransform3D
ScaleX="{Binding ElementName=sliderZoom, Path=Value}"
ScaleY="{Binding ElementName=sliderZoom, Path=Value}"
ScaleZ="{Binding ElementName=sliderZoom, Path=Value}"/>
</Transform3DGroup>
</PerspectiveCamera.Transform>
</PerspectiveCamera>
</Viewport3D.Camera>
<ModelVisual3D>

</ModelVisual3D>
<ModelVisual3Dx:Name="model">
<ModelVisual3D.Content>
<Model3DGroupx:Name="group">
<AmbientLightColor="DarkGray"/>
<DirectionalLightColor="DarkGray"Direction="10,10,5"/>
</Model3DGroup>
</ModelVisual3D.Content>
</ModelVisual3D>
</Viewport3D>

<StackPanelGrid.Column="1"Width="100"Background="LightGray">
<GroupBoxHeader="Rotation"Margin="4.0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlockText="X"Grid.Column="0"Grid.Row="0"/>
<TextBlockText="Y"Grid.Column="1"Grid.Row="0"/>
<TextBlockText="Z"Grid.Column="2"Grid.Row="0"/>
<Sliderx:Name="sliderX"Grid.Column="0"Grid.Row="1"Minimum="0.0"Maximum="360.0"Value="230"Style="{StaticResource slider}">
<Slider.ToolTip>
<TextBlockText="Rotate around X-Axis"/>
</Slider.ToolTip>
</Slider>
<Sliderx:Name="sliderY"Grid.Column="1"Grid.Row="1"Minimum="-180.0"Maximum="180.0"Style="{StaticResource slider}">
<Slider.ToolTip>
<TextBlockText="Rotate around Y-Axis"/>
</Slider.ToolTip>
</Slider>
<Sliderx:Name="sliderZ"Grid.Column="2"Grid.Row="1"Minimum="-180.0"Maximum="180.0"Style="{StaticResource slider}">
<Slider.ToolTip>
<TextBlockText="Rotate around Z-Axis"/>
</Slider.ToolTip>
</Slider>
</Grid>
</GroupBox>

<GroupBoxHeader="Translate"Margin="4.0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlockText="X"Grid.Column="0"Grid.Row="0"/>
<TextBlockText="Y"Grid.Column="1"Grid.Row="0"/>
<TextBlockText="Z"Grid.Column="2"Grid.Row="0"/>
<Sliderx:Name="transSliderX"Grid.Column="0"Grid.Row="1"Minimum="-10"Maximum="10"Style="{StaticResource slider}">
<Slider.ToolTip>
<TextBlockText="Translate along the X-Axis"/>
</Slider.ToolTip>
</Slider>
<Sliderx:Name="transSliderY"Grid.Column="1"Grid.Row="1"Minimum="-10"Maximum="10"Style="{StaticResource slider}">
<Slider.ToolTip>
<TextBlockText="Translate along the Y-Axis"/>
</Slider.ToolTip>
</Slider>
<Sliderx:Name="transSliderZ"Grid.Column="2"Grid.Row="1"Minimum="-10"Maximum="10"Style="{StaticResource slider}">
<Slider.ToolTip>
<TextBlockText="Translate along the Z-Axis"/>
</Slider.ToolTip>
</Slider>
</Grid>
</GroupBox>

<GroupBoxHeader="Zoom"Margin="4.0">
<Sliderx:Name="sliderZoom"IsDirectionReversed="True"Minimum="0.01"Maximum="1"Value="0.8"Style="{StaticResource slider}"/>
</GroupBox>
</StackPanel>
</Grid>
</UserControl>

We can now add the method that will handle the mouse wheel event to zoom the model accordingly. To do this, we can right click on the mouse wheel event name and press “Navigate to Event Handler.” This will generate the mouse wheel event code that is needed. We can then add in a bit of logic for setting the value of the zoom slider which will in turn zoom the model as there is a binding connected to the slider. The code for the file ViewerPanel3D.xaml.cs should look like this:
using System.Windows.Controls;
using System.Windows.Input;

namespace BingMaps3DModel_WPF
{
/// <summary>
/// Interaction logic for ViewerPanel3D.xaml
/// </summary>
publicpartialclass ViewerPanel3D : UserControl
{
public ViewerPanel3D()
{
InitializeComponent();
}

privatevoid OnViewportMouseWheel(object sender, MouseWheelEventArgs e)
{
sliderZoom.Value -= (double)e.Delta / 1000;
}
}
}

If we run the application and go to the 3D model tab we should see something that looks like this.

3DWPFFrame

Generating the Model

Having a nice panel for viewing the model is a good start but doesn’t really do us much good without having a 3D model to view. To create the 3D model we will need to do the following:

(1) Get a static map image for the based on the center point and zoom level of the map. To keep things easy, we will make keep the width and height of the image equal to 800 pixels.

(2) Based on the center point, zoom level and imagery size we will then need to calculate the bounding box of the image as we will need it to request the elevation data.

(3) Make a request for the elevation data for the bounding box we created. Again, to keep things simple we will specify that the data points be evenly distributed over 30 rows and columns. This will result in 900 elevation data points being returned which is under the 1000 elevation data point limit.

(4) We need to loop through all the elevation data, calculate the relative coordinate, and then convert this coordinate to a pixel coordinate. We will also need to convert the elevation into a pixel length and then scale these values down relative to the size of the map.

(5) Now we can create a 3D Mesh Geometry out of the data. To do this, we will need to specify all the data points as 3 dimensional coordinates, and then specify the texture coordinates used to map the static map image to the mesh. We will also need to specify the point indices used to create the triangles needed for the mesh.

(6) As a final step, we will create a Geometry Model out of the 3D Mesh and set the static image as the material to be overlaid on top of it. This model can then be passed into our ViewerPanel3D user control.

Most of the math used to work with the pixel coordinates are based off of these two articles: Bing Maps Tile System, and VE Imagery Service and Custom Icons. Putting all the above tasks together and adding them to the MainWindow.xaml.cs file you should end up with a code for the MainWindow.xaml file that looks like this:

using System;
using System.IO;
using System.Net;
using System.Runtime.Serialization.Json;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using BingMapsRESTService.Common.JSON;

namespace BingMaps3DModel_WPF
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
publicpartialclass MainWindow : Window
{
#region Private Properties

privatestring sessionBingMapsKey;

private GeometryModel3D mGeometry;

privateconstdouble mapSize = 800;

privatedouble topLeftX;
privatedouble topLeftY;
privatedouble zoom;

#endregion

#region Constructor

public MainWindow()
{
InitializeComponent();

MyMap.CredentialsProvider.GetCredentials((x) =>
{
sessionBingMapsKey = x.ApplicationId;
});
}

#endregion

#region Button Event Handler

privatevoid Generate3DModel_Click(object sender, RoutedEventArgs e)
{
double cLat = MyMap.Center.Latitude;
double cLon = MyMap.Center.Longitude;

//Round off the zoom level to the nearest integer as an integer zoom level is needed for the REST Imagery service
zoom = Math.Round(MyMap.ZoomLevel);

//Only generate models when the user is zoomed in at a decent zoom level, otherwise the model will just be a flat sheet.
if (zoom < 8)
{
MessageBox.Show("This zoom level is not supported. Please zoom in closer (>8).");
return;
}

//Clear current model from the viewer panel
if (mGeometry != null)
{
viewerPanel.group.Children.Remove(mGeometry);
}

//Open up the 3D model tab
MyTabs.SelectedIndex = 1;

//Calculate bounding box of image for specified zoom level and center point for map dimensions

//Retrieve image of map dimensions for the specified zoom level and center point.
string imgUrl = string.Format("http://dev.virtualearth.net/REST/v1/Imagery/Map/Aerial/{0},{1}/{2}?mapSize={3},{4}&key={5}",
cLat,
cLon,
zoom,
mapSize,
mapSize,
sessionBingMapsKey);

ImageBrush imgBrush = new ImageBrush();
imgBrush.ImageSource = new BitmapImage(new Uri(imgUrl));

DiffuseMaterial material = new DiffuseMaterial(imgBrush);

//calcuate pixel coordinates of center point of map
double sinLatitudeCenter = Math.Sin(cLat * Math.PI / 180);
double pixelXCenter = ((cLon + 180) / 360) * 256 * Math.Pow(2, zoom);
double pixelYCenter = (0.5 - Math.Log((1 + sinLatitudeCenter) / (1 - sinLatitudeCenter)) / (4 * Math.PI)) * 256 * Math.Pow(2, zoom);

//calculate top left corner pixel coordiates of map image
topLeftX = pixelXCenter - (mapSize / 2);
topLeftY = pixelYCenter - (mapSize / 2);

//Calculate bounding coordinates of map view
double brLongitude, brLatitude, tlLongitude, tlLatitude;

PixelToLatLong(new System.Windows.Point(900, 800), out brLatitude, out brLongitude);
PixelToLatLong(new System.Windows.Point(0, 0), out tlLatitude, out tlLongitude);

//Retrieve elevation data for the specified bounding box
//Rows * Cols <= 1000 -> Let R = C = 30

string elevationUrl = string.Format("http://dev.virtualearth.net/REST/v1/Elevation/Bounds?bounds={0},{1},{2},{3}&rows=30&cols=30&key={4}",
brLatitude,
tlLongitude,
tlLatitude,
brLongitude,
sessionBingMapsKey);

WebClient wc = new WebClient();
wc.OpenReadCompleted += (s, a) =>
{
using (Stream stream = a.Result)
{
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(Response));
Response response = ser.ReadObject(stream) as Response;

if (response != null&&
response.ResourceSets != null&&
response.ResourceSets.Length > 0 &&
response.ResourceSets[0] != null&&
response.ResourceSets[0].Resources != null&&
response.ResourceSets[0].Resources.Length > 0)
{
ElevationData elevationData = response.ResourceSets[0].Resources[0] as ElevationData;

//Map elevation data to 3D Mesh
MeshGeometry3D mesh = new MeshGeometry3D();

double dLat = Math.Abs(tlLatitude - brLatitude) / 30;
double dLon = Math.Abs(tlLongitude - brLongitude) / 30;

double x, y, m2p;

for (int r = 0; r < 30; r++)
{
y = tlLatitude + (dLat * r);

for (int c = 0; c < 30; c++)
{
int idx = r * 30 + c;

x = tlLongitude + (dLon * c);

double z = -elevationData.Elevations[idx];

m2p = 156543.04 * Math.Cos(y * Math.PI / 180) / Math.Pow(2, zoom);

System.Windows.Point p = LatLongToPixel(y, x);
p.X = (p.X - 400) / mapSize;
p.Y = (p.Y + 400) / mapSize;

mesh.Positions.Add(new Point3D(p.X, p.Y, z / mapSize / m2p));

mesh.TextureCoordinates.Add(p);

//Create triangles for model
if (r < 29 && c < 29)
{
mesh.TriangleIndices.Add(idx);
mesh.TriangleIndices.Add(idx + 1);
mesh.TriangleIndices.Add(idx + 30);

mesh.TriangleIndices.Add(idx + 1);
mesh.TriangleIndices.Add(idx + 31);
mesh.TriangleIndices.Add(idx + 30);
}
}
}

//Add 3D mesh to view panel
mGeometry = new GeometryModel3D(mesh, material);
mGeometry.Transform = new Transform3DGroup();
viewerPanel.group.Children.Add(mGeometry);
}
}
};

wc.OpenReadAsync(new Uri(elevationUrl));
}

#endregion

#region Helper Methods

private System.Windows.Point LatLongToPixel(double latitude, double longitude)
{
//Formulas based on following article:
//http://msdn.microsoft.com/en-us/library/bb259689.aspx

//calculate pixel coordinate of location
double sinLatitude = Math.Sin(latitude * Math.PI / 180);
double pixelX = ((longitude + 180) / 360) * 256 * Math.Pow(2, zoom);
double pixelY = (0.5 - Math.Log((1 + sinLatitude) / (1 - sinLatitude)) / (4 * Math.PI)) * 256 * Math.Pow(2, zoom);

//calculate relative pixel cooridnates of location
double x = pixelX - topLeftX;
double y = pixelY - topLeftY;

returnnew System.Windows.Point((int)Math.Floor(x), (int)Math.Floor(y));
}

privatevoid PixelToLatLong(System.Windows.Point pixel, outdouble lat, outdouble lon)
{
double x = topLeftX + pixel.X;
double y = topLeftY + pixel.Y;

lon = (x * 360) / (256 * Math.Pow(2, zoom)) - 180;

double efactor = Math.Exp((0.5 - y / 256 / Math.Pow(2, zoom)) * 4 * Math.PI);

lat = Math.Asin((efactor - 1) / (efactor + 1)) * 180 / Math.PI;
}

#endregion
}
}

You should now be able to run the application. Give it a go and zoom into an area where there is a good chance of being varying elevations. I find using a zoom level between 15 and 19 works best. If you don’t see a model generated, try zooming out or translating along the Z axis.

Here is a screenshot of my finished application with the generated 3D model of a section of the Grand Canyon. Nice, right?

GrandCanyon

- Ricky Brundritt, EMEA Bing Maps Technology Solution Professional

Bing Maps REST Service Tips & Tricks

$
0
0

I’ve been a big fan of the Bing Maps REST services since they were first released several years ago and highly recommend trying them out if you haven’t already. The REST services are much newer than the SOAP services, have more features, are faster and can be consumed by non .NET languages with greater ease. If you have the Bing Maps REST services return JSON the response size of a request is significantly smaller than the response size from the same request using the Bing Maps SOAP services. This blog post will go through and highlight some tips for getting the most out of the Bing Maps REST services.

Using the REST services in managed code

I want to take the time to provide a useful resource for those who have had issues consuming the Bing Maps REST Services APIs using .NET. The documentation on how to consume the JSON responses from the Bing Maps REST services can be found on MSDN here.

If you are using Java then take a look at how the REST services are handled in the Bing Maps Android SDK.

Geocoding

·         Unless you are geocoding English addresses in the US you should specify a culture parameter to help ensure you get the most relevant results. By default, the culture for all requests is set to en-US. If geocoding addresses in the UK, you will find that in some cases using the culture parameter en-GB will return better results. This becomes even more important when geocoding addresses that are not in English. To specify a culture in a REST request simply use “&c=cultureCode” – you can take a look at the complete list of Supported Cultures.

·        Encode address and query parameters. This is especially important when working with non-English languages as special characters often will not work correctly. This is also important if you want to use an ampersand (&) in your query. By encoding an ampersand it will appear in the URL as “%26”. Here are the methods that can be used in different programming languages to encode URL parameters.

Language

Method

Example

JavaScript

encodeURI

encodeURI(query)

C#/VB

Uri

Uri.EscapeDataString (query)

 

·         When geocoding free form queries use the unstructured URL format rather than the structured format. The unstructured URL format tends to be much more successful for these types of queries. Note: the structured format actually overlaps with the reverse geocoding URL request format and can return odd results if your query is just made up of numbers.

Unstructured URL

http://dev.virtualearth.net/REST/v1/Locations?query=locationQuery&key=BingMapsKey

 

X Structured URL

http://dev.virtualearth.net/REST/v1/Locations/locationQuery?key=BingMapsKey

·         The Bing Maps geocoder will attempt to find the closest match as possible to your query. In some cases, it will not be able to find an exact match. This is where the match code parameter of the returned results becomes useful. The match code parameter is an array of values and can have any combination of the following three values; Good, Ambiguous, and UpHierarchy. If you are only interested in exact matches then keep a look out for UpHierachy as this indicates that your exact query was not found but an upper level address value was found. For example, you attempt to geocode a postal code but instead the associated country is returned as the postal code was not found.

·        If using the REST services from server side code you may find that the results on in your application may differ from the results found when using the services locally. The reason for this is that the REST services take your IP address into consideration when making a request. To help reduce this issue you can set theuserIpparameter of the request to 127.0.0.1. This will trick the service into thinking you are making the call from a local application and cause it to ignore your IP address.

·         If you have a lot of data you want to geocode at once consider using the Bing Spatial Data Servicesand the batch geocoding functionality. This service allows you to geocode up to 200,000 addresses in a single request.

Reverse Geocoding

·         Limit your coordinates to 6 decimal places. At 6 decimal places you have an accuracy of approximately 10cm on the ground. Any more than 6 decimal places just makes for a longer URL and can confuse the reverse geocoder into thinking this is a free form query search.

·         Ensure that your numbers are not being turned into scientific notation format when converting them to string. This occurs quite often when working with small numbers. For example, it is not uncommon for some languages to convert 0.00005 to 5E-5.  Scientific notation is not supported by the service and will be interpreted as a free form query.

·         Ensure that when converting your coordinates to string that you use an invariant culture. Coordinates that use commas for decimal places will not work correctly.

·         Like the batch geocoder you can also do batch reverse geocoding using the Bing Spatial Data Services if you need to reverse geocode a large number of coordinates in one go.

Routing

·         Many of the tips in the geocoding service apply for the routing service, like being sure to encode your address locations. However, don’t encode coordinate based locations.

·         The default distance units are in Kilometers. Use thedistanceUnitparameter to change this to miles if that is your preferred unit of measurement.

·        You can now have up to 3 possible routes returned by the routing engine for transit and driving directions in certain countries. This may be desirable in some applications but it is best to make this optional to your users. Although the calculation on Bing Maps end is fast, the response from Bing Maps is much bigger when returning 3 routes, rather than one. This could become an issue for users with slow internet connections (i.e. mobile users).

·         If using Bing Maps in areas where geocoding coverage is limited consider allowing the user to select their start and end point on the map via a click event or by dragging a pushpin. This will allow you to pass in coordinates for your end points rather than an address. The routing engine is capable of calculating routes anywhere there is road data, even if there is no geocoding coverage.

·         If you want to retrieve the coordinates that make up the route line along the roads use the routePathOutputparameter.

Imagery Service

·         When requesting a static map image from Bing Maps the imagery service the service will automatically choose the best image format to return the image in for best resolution. Note: this may not be the preferred image type in some cases. For example, the service may return Ordnance Survey maps in PNG format; you may find you prefer these maps returned as JPG of GIF format. You can specify the image type using the formatparameter.

·         The Imagery service can return two different types of metadata. The first typeof metadata gives you information about the imagery in Bing Maps for a specific location, zoom level and map type. This is useful if you want to find out the age of the imagery or want to know is a specific type of imagery is available for a certain location. The second typeof metadata is for a static image generated from the imagery service. This second metadata may include information such as pixel coordinates of pushpins on your image. This is useful if you want to be able to tie events to the generated image or create an HTML image mapout of it.

Reducing Usage Transactions

Are you are using the Bing Maps Rest services in conjunction with one of the Bing Maps map controls? If so, you can significantly reduce the number of transactions your application incurs against your Bing Maps account if you request the Bing Maps key from the map control rather than using your normal Bing Maps key. This is quite often an overlooked feature. When getting the credentials from the map you do not get back your original Bing Maps key. Instead, you get a special session key which you can use as a Bing Maps key to make requests to the Bing Maps services. By doing this all transactions incurred by this session key will be non-billable. Here are some examples of how to properly use a session key.

 

Bing Maps V7 AJAX API

function ClickGeocode()

 {

                map.getCredentials(MakeBingMapsRESTRequest);

 }

 

 function MakeBingMapsRESTRequest(sessionKey)

 {

                //Generate a request URL for the Bing Maps REST services.

                //Use the session key in the request as the Bing Maps key

 }


Full working sample using JavaScript can be found here.

Bing Maps Silverlight, WPF, WP7, and WinForm API’s

Map.CredentialsProvider.GetCredentials((c) =>

{

                string sessionKey = c.ApplicationId;

               

                //Generate a request URL for the Bing Maps REST services.

                //Use the session key in the request as the Bing Maps key

});


Full working samples using .NET can be found

here.

- Ricky Brundritt, EMEA Bing Maps Technology Solution Professional

Attend the Bing Maps and SharePoint Solutions Webcast

$
0
0
 

You are invited to attend this webcast to learn how solutions that integrate Bing Maps and SharePoint help organizations gain greater insight from their business data to make smarter business decisions. We will also explore new native geospatial support for SharePoint 2013 and Office 365 (SharePoint in the cloud) which makes it much easier to geocode address data and display with Bing Maps. We’ll explore solution scenarios and tools that illustrate the value of geospatial support in SharePoint when combined with Bing Maps, and look at examples of customers extending their investment in the SharePoint platform by leveraging location and Bing Maps.


You’ll learn what’s included in the Bing Maps platform, and how to take advantage of the data services & imagery and incorporate them into line of business solutions within your organization. We will explore the development options with the platform, and how to get started with Bing Maps and SharePoint 2013/Office 365 development.

Webcast Details

Bing Maps and SharePoint Solutions for the Enterprise (EPG382CAL)

Date/Time:

Tue Feb 26, 2013, 9:00 AM, USA Pacific

Duration:

1 Hour

Presenters:

Mark Merchant, Bing Maps Technical Solution Professional

Ricky Brundritt, Bing Maps Technical Solution Professional

Scott Caulk, VP of Product Management for IDV Solutions

Steve Milroy, CEO OnTerra Systems

Modules in Bing Maps for Windows Store Apps (JavaScript)

$
0
0

The Bing Maps V7 AJAX control is designed as a modular framework and the Bing Maps for Windows Store App JavaScript control follows the same design. Modules allow users to load only the features and functionalities they need, rather than loading everything up when the application starts. You can save yourself a lot of development time by using modules and avoid reinventing the wheel. Out of the box, Bing Maps provides the following modules:

Name

Description

Microsoft.Maps.AdvanceShapes

Adds support for complex polygons. i.e. polygons with holes.

Microsoft.Maps.Directions

Allows you to calculate a route and display it on the map. The route is draggable by default for easy customization. The instructions will also be nicely formatted.

Microsoft.Maps.Search

Provides an easy method for geocoding address and searching for points of interest from JavaScript.

Microsoft.Maps.Themes.BingTheme

Modifies the navigation bar, pushpin and infobox look and feel to match the Bing Maps consumer site.

Microsoft.Maps.Traffic

Adds a traffic flow tile layer to the map.

Microsoft.Maps.VenueMaps

Exposes the Venue Map functionality and can be used to find nearby venue maps and load them. Venue Maps are interactive buildings on the map that often show the layout of a building. For instance, you can load a venue map of a mall and see were all the stores are located.


In addition to the modules available through the Bing Maps control, there are a large number of community created modules also available on the Bing Maps V7 Modules CodePlex project. However, not all community created modules are designed to work with Windows Store applications; you may need to make some modifications to get them to work with your code. You can also create custom modules, which is a really great way to promote code reuse. For the purpose of this post, we will build on top of the code we created in the Getting started with Bing Maps Windows Store Apps blog post.

Implementing Modules

There are three steps:

  1. Add a reference to the veapimodules.js file from the Bing Maps SDK
  2. Register the module if it isn’t already registered
  3. Load the module and run any post load logic

To add a reference to the veapimodules.js file in the Bing Maps SDK, open the default.html file and update the Bing Maps references to the following:

<!-- Bing Maps references -->

<scripttype="text/javascript"

        src="ms-appx:///Bing.Maps.JavaScript//js/veapicore.js"></script>

<scripttype="text/javascript"

        src="ms-appx:///Bing.Maps.JavaScript//js/veapimodules.js"></script>


Now, before you can load a module, it must first be registered. All modules built into Bing Maps are already registered, however, if you are using a custom module you will need to register it. If you have used custom modules before, be warned, registeringthe web version of the Bing Maps V7 AJAX control and registering modules in the Windows Store App version of the Bing Maps JavaScript control are done differently. Registering custom modules is pretty straightforward; simply add a line of code to register the name and load in the JavaScript file containing your module code. You can do this by adding code, similar to the example below, into the head of the default.html page.

<!-- Register and load the code to our Custom Module -->

<script>Microsoft.Maps.registerModule('MyModule');</script>

<scripttype="text/javascript"src="/js/myModule.js"></script>


One benefit of this approach is that the JavaScript file for our module will be cached as byte code by the Windows 8 framework, which makes for fast loading. However, it doesn’t have the same benefit of being able to load the code for the module on demand which we were are able to do with the built in modules. This is useful when you want the fastest possible startup for your application. With a bit of code we can use JavaScript to dynamically load our custom modules. Here is a simple function we can use to accomplish this:

function RegisterModule(name, url) {

    //Register the module

    Microsoft.Maps.registerModule(name);

 

   //Load the JavaScript File

   var script = document.createElement("script");

    script.setAttribute("type", "text/javascript");

    script.setAttribute("src", url);

    document.body.appendChild(script);

}


Now that the module is registered, we can load it. When to load a module really depends on the type of module you are using. For instance, the Bing Theme module needs to be loaded before the map is loaded, however the directions module doesn’t need to be loaded until the user wants to get directions. Here is the basic way of loading a module:

Microsoft.Maps.loadModule(“MyModule”);


With some modules you may want to run code after the module has loaded. In this case, you can pass in a callback function as an option when loading the module. Doing the following will trigger a function called myModuleLoaded when the module has completed loading.

Microsoft.Maps.loadModule("MyModule", { callback: myModuleLoaded });


Loading the Traffic Module

Now that we understand how to use the module framework in the Bing Maps control, lets implement the traffic module in our application. To start, we will add a checkbox on the map for toggling the traffic layer on and off. First, open the default.html file and the checkbox to the same floating div that has our GPS checkbox. This time we are going to give the checkbox an id property.

<!DOCTYPEhtml>

<html>

<head>

   <metacharset="utf-8"/>

   <title>BingMapsJSIntro</title>

 

   <!-- WinJS references -->

   <linkhref="//Microsoft.WinJS.1.0/css/ui-dark.css"rel="stylesheet"/>

   <scriptsrc="//Microsoft.WinJS.1.0/js/base.js"></script>

   <scriptsrc="//Microsoft.WinJS.1.0/js/ui.js"></script>

 

   <!-- BingMapsJSIntro references -->

   <linkhref="/css/default.css"rel="stylesheet"/>

   <scriptsrc="/js/default.js"></script>

 

   <!-- Bing Maps references -->

   <scripttype="text/javascript"

            src="ms-appx:///Bing.Maps.JavaScript//js/veapicore.js"></script>

   <scripttype="text/javascript"

            src="ms-appx:///Bing.Maps.JavaScript//js/veapimodules.js"></script>

 

   <!-- Our Bing Maps JavaScript Code -->

   <scriptsrc="/js/bingMapsIntro.js"></script>

</head>

<body>

   <divid="myMap"></div>

    

   <divstyle="position:absolute;right:10px;top:10px;background-color:#808080;padding:5px;">

        <inputtype="checkbox"onclick="ToggleGPS(this);"/> GPS

        <inputtype="checkbox"onclick="ToggleTraffic();"id="TrafficChbx"/> Traffic

   </div>    

</body>

</html>


Since the traffic module is a built in module in the Bing Maps SDK we do not need to worry about registering it. We also don’t need to load the traffic module until the user presses the Traffic checkbox. To add the logic for traffic checkbox open the bingMapsIntro.js file and add a global variable called trafficLayer. Next, add a function called ToggleTraffic. In this function we will check to see the trafficLayer variable is implemented. If it is, then we can get the checked state from the traffic checkbox and show or hide the traffic layer accordingly. If the trafficLayer variable is not implemented we will need to load the traffic module and create an instance of the Microsoft.Maps.Traffic.TrafficLayer class after the module has loaded. By default, the traffic tile layer has no opacity to it and it ends up covering up the names of roads. To make for a better user experience we will get the base tile layer of the traffic layer and set it’s opacity to 0.5. Finally, after we created an instance of the TrafficLayer class we will want to rerun our logic for toggling the traffic layer. To do all this, add the following code into the bingMapsIntro.js file.

var map, geoLocationProvider, gpsLayer, trafficLayer;

 

function ToggleTraffic() {

   //Check to see if the traffic layer exists

   if (trafficLayer) {

        //Get a reference to the traffic checkbox

        var chbx = document.getElementById('TrafficChbx');

 

        //Hide or Show the tile layer based on checked state

        if (chbx.checked) {

            trafficLayer.show();

        } else {

            trafficLayer.hide();

        }

    } else {

        //Load the traffic module and create the traffic layer.

        Microsoft.Maps.loadModule('Microsoft.Maps.Traffic', {

            callback: function () {

                //Create the traffic layer

                trafficLayer = new Microsoft.Maps.Traffic.TrafficLayer(map);

 

                //Get the base tile layer and set the opacity

                var layer = trafficLayer.getTileLayer();

                layer.setOptions({ opacity : 0.5 });

 

                //Toggle the traffic layer to the current state of the checkbox.

                ToggleTraffic();

            }});

    }

}


If you run the application now and press the traffic button you may not notice anything if you are zoomed out. To make things easy, I’m going to toggle on both the GPS and the Traffic functionalities. Doing this I end up with the following map:

temp2

For more information on where traffic data is available see the Bing Maps Traffic Coverage section of the MSDN documentation.

Loading a Custom Module

We have seen how to load one of the built in modules to our application, now we will look at how to implement a custom module. To do this we will make use of one of the community created modules called Point Based Clustering. This module will group pushpins together when zoomed out to make the map less crowded. This particular module uses a point based algorithm rather than a traditional grid based one. The point based algorithm gives use a much nicer user experience than the grid based algorithm, but there is a trade off in performance. With that said, this algorithm can easily handle 2,000+ pushpins.

Since this module is not really related to what we have put together in our Bing Maps Intro project we will start fresh with a new project. In Visual Studios create a new project and use the JavaScript -> Store -> Blank App template. Call the project PointBasedClustering_WinRT. Once the application is loaded, right click on the js folder and add a new JavaScript file called bingMapsClustering.js. Next, right click on the References folder and add a reference to the Bing Maps SDK. Now, go to the Point Based Clustering page and download the code. Unzip the file and copy the scripts/minified/PointBasedClustering.min.js and the scripts/TestDataGenerator.js files into the js folder of the project. After you do this your project should look like this:

clip_image003

Now we can get started on implementing this custom module. To start, we will open up the default.html file and add a textbox and a button to a floating div in the top, right hand corner of our map where the user can enter the number of pushpins to add to the map. When the button is clicked it will generate the mock pushpin data and display it on the map and clustering it automatically. We will also need to add a reference to the TestDataGenerator.js file for this example. In a production application you would not need this as you would use real data with the Point Based Clustering module. Adding this to the default.html file we end up with the following HTML:

<!DOCTYPEhtml>

<html>

<head>

   <metacharset="utf-8"/>

   <title>BingMapsJSIntro</title>

 

   <!-- WinJS references -->

   <linkhref="//Microsoft.WinJS.1.0/css/ui-dark.css"rel="stylesheet"/>

   <scriptsrc="//Microsoft.WinJS.1.0/js/base.js"></script>

   <scriptsrc="//Microsoft.WinJS.1.0/js/ui.js"></script>

 

   <!-- BingMapsJSIntro references -->

   <linkhref="/css/default.css"rel="stylesheet"/>

   <scriptsrc="/js/default.js"></script>

 

   <!-- Bing Maps references -->

   <scripttype="text/javascript"

            src="ms-appx:///Bing.Maps.JavaScript//js/veapicore.js"></script>

   <scripttype="text/javascript"

            src="ms-appx:///Bing.Maps.JavaScript//js/veapimodules.js"></script>

 

   <!-- Our Bing Maps JavaScript Code -->

   <scriptsrc="/js/bingMapsClustering.js"></script>z

 

   <!-- Add a reference to class for generating test data. Not needed in production apps. -->

   <scripttype="text/javascript"src="/js/TestDataGenerator.js"></script>

</head>

<body>

   <divid="myMap"></div>

    

   <divstyle="position:absolute;right:10px;top:10px;background-color:#808080;padding:5px;">

        Data Size: <inputtype="text"id="dataSize"style="width:30px;"/>

        <inputtype="button"value="Get Mock Data"onclick="RequestData();"/>

   </div>    

</body>

</html>


Now, open the bingMapsClustering.js file, add the following code to load the map, and register and load the Point Based Clustering Module. We will register our module right after loading the map in the GetMap function.

var map, clusterLayer;

 

function GetMap() {

   var mapOptions =

    {

        credentials: "YOUR_BING_MAPS_KEY",

        zoom: 2

    };

 

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

 

   //Register and load the Point Based Clustering Module

    RegisterModule("PointBasedClusteringModule", "/js/PointBasedClustering.min.js");

 

    Microsoft.Maps.loadModule("PointBasedClusteringModule", {

        callback: function () {

            clusterLayer = new PointBasedClusteredEntityCollection(map);

        }

    });

}

 

//Initialization logic for loading the map control

(function () {

   function initialize() {

        Microsoft.Maps.loadModule('Microsoft.Maps.Map', { callback: GetMap });

    }

 

    document.addEventListener("DOMContentLoaded", initialize, false);

})();

 

We also need to add the RegisterModule functions so that we can dynamically load our custom module. This can easily be done by adding the following after the GetMap function.

function RegisterModule(name, url) {

   //Register the module

    Microsoft.Maps.registerModule(name);

 

   //Load the JavaScript File

   var script = document.createElement("script");

    script.setAttribute("type", "text/javascript");

    script.setAttribute("src", url);

    document.body.appendChild(script);

}


The last item here is to add the logic for handling the button click event by creating a function called RequestData. In this function we will need to get the users input value and pass it to the test data generator to generate our mock data. To simulate an asynchronous call to get our data, which we would likely do for a production application, our test generator takes in a callback function which it will call after it generates our mock data. We will name this callback function RequestDataCallback and have it populate our cluster layer with the mock data. The following can be added after the RegisterModule function in the bingMapsClustering.js file.

//Makes a request for data

function RequestData() {

   var size = parseInt(document.getElementById('dataSize').value);

    TestDataGenerator.GenerateData(size, RequestDataCallback);

}

 

//Handle the data response

function RequestDataCallback(response) {

   if (response != null) {

        clusterLayer.SetData(response);

    }

}


Let’s run the application. In the textbox, enter the number of pushpins you would like to generate on the map and then press the button. The following is a screenshot with 1,000 pushpins being clustered and rendered on the map. As you zoom in, you will notice that the clusters begin to break apart into their individual pushpins.

temp1

- Ricky Brundritt, EMEA Bing Maps Technology Solution Professional

Geocoding and Routing in Bing Maps Windows Store Apps (Native)

$
0
0

In this blog post we are going to look at how implement Geocoding and Routing using the Native Bing Maps Windows Store App SDK. If you are new to developing with the Native Bing Maps Windows Store App SDK I recommend reading the Getting started with Bing Maps Windows Store Apps blog post first. We will also be making use of the Bing Maps REST Services, if you are unfamiliar with using this in native code take a look at this MSDN documentation on Using the REST services with .NET. The methods covered in this blog post can be used as an example of how to access other REST services from Windows Store applications.

Geocoding is one of the most common tasks by users of online maps. Geocoding is the process of taking an address or query and returning its equivalent coordinate on the map. Routing is the task of calculating the directions between two or more locations. In Bing Maps, there are a lot of different options around routing such as routing by different modes of transportation; driving, walking, or transit.

Setting up the project

To set up the project, open Visual Studios 2012 and create a new project. In the window that opens, select Visual C# -> Windows Store. Select the Blank App template and call the application BingMapsSearch_WinRT_CS and press OK.

Next, we will add in the libraries needed for serializing the response from the Bing Maps REST services. To do this, right-click on the project and select Add -> New Item. Create a class file called BingMapsRESTServices.cs. Open up this class file and delete the contents. Go to the MSDN documentation on Using the REST services with .NET and copy and paste the C# Data Contracts into this file. At this point, your Solution should look something like this:

image

Adding Bing Maps to the App

To get started we will need to add a reference to the Bing Maps SDK. To do this, right click on the References folder and press Add Reference. Select Windows -> Extensions select Bing Maps for C#, C++ and Visual Basic. If you do not see this option, take a moment to verify that you have installed the Bing Maps SDK for Windows 8 style apps. While you are here, you should add a reference to the Microsoft Visual C++ Runtime Package as it is required by the Bing Maps SDK when developing using C# or Visual Basic.

image

You may notice that there is a little yellow indicator on the references that you just added. This is because the Bing Maps SDK requires that you set the Active solution platform in Visual Studio to one of the following options.

  • > C#, Visual Basic: ARM, x86 or x64
  • > C++: ARM, Win32 or x64

To do this, right click on the Solution folder and select Properties. Then go to Configuration Properties -> Configuration. Find your project and under the Platform column set the target platform. For this blog post I’m going to select x86. Press Ok and the yellow indicator should disappear from our references.

image

Now we can add a map to our application. To do this, open the MainPage.xaml file. We will first need to add the Bing Maps SDK as a namespace at the top of the file. After we do this we can add a Map object to the Grid control and add our Bing Maps key to the credentials properties of the map. We will also give the map a name of MyMap. While we are at it, we will create a side panel that has input controls for geocoding and routing and a button for clearing the map. In the side panel we will also add two ListBox objects. We will use these to display the geocode results and route itinerary to the user. The XAML for file will look like this:

<Page

   x:Class="BingMapsSearch_WinRT_CS.MainPage"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   xmlns:local="using:BingMapsSearch_WinRT_CS"

   xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

   xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

   xmlns:m="using:Bing.Maps">

 

    <Page.Resources>

        <Style TargetType="Button">

            <Setter Property="Background" Value="Green"/>

        </Style>

    </Page.Resources>

   

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">

        <Grid.ColumnDefinitions>

            <ColumnDefinition Width="350"/>

            <ColumnDefinition/>

        </Grid.ColumnDefinitions>

 

        <!-- Left Side Panel -->

        <ScrollViewer Background="Gray">

            <StackPanel Margin="10,10,20,10">

                <!-- Clear Map Button -->

                <Button Content="Clear Map" Click="ClearMapBtn_Click"

                       HorizontalAlignment="Right"/>

 

                <!-- Geocode Input -->

                <TextBlock Text="Geocode" FontSize="24"/>

                <Grid>

                    <TextBox Name="GeocodeTbx" HorizontalAlignment="Left"

                            Width="220" Height="25"/>

                    <Button Content="Geocode" HorizontalAlignment="Right"

                           Click="GeocodeBtn_Click"/>

                </Grid>

 

                <!-- Route Input -->

                <TextBlock Text="Route" FontSize="24" Margin="0,10,0,0"/>

                <StackPanel Orientation="Horizontal">

                    <TextBlock Text="From:" FontSize="18"/>

                    <TextBox Name="FromTbx" Width="220" Margin="10,0,0,0"/>

                </StackPanel>

                <StackPanel Orientation="Horizontal">

                    <TextBlock Text="To:" FontSize="18"/>

                    <TextBox Name="ToTbx" Width="220" Margin="33,10,0,10"/>

                </StackPanel>

                <Button Content="Get Directions" Click="RouteBtn_Click"

                       HorizontalAlignment="Right"/>

 

                <!-- Geocode Results Panel -->

                <ListBox Name="GeocodeResults"

                        SelectionChanged="GeocodeResultSelected" Margin="0,10,0,0">

                    <ListBox.ItemTemplate>

                        <DataTemplate>

                            <TextBlock Text="{Binding Name}"/>

                        </DataTemplate>

                    </ListBox.ItemTemplate>

                </ListBox>

 

                <!-- Route Itinerary Panel -->

                <StackPanel Name="RouteResults">

                    <ListBox ItemsSource="{Binding RouteLegs}">

                        <ListBox.ItemTemplate>

                            <DataTemplate>

                                <ListBox ItemsSource="{Binding ItineraryItems}">

                                    <ListBox.ItemTemplate>

                                        <DataTemplate>

                                            <StackPanel Orientation="Horizontal">

                                                <TextBlock Text="{Binding Instruction.Text}"

                                                          TextWrapping="Wrap" Width="200"/>

                                                <TextBlock Text="{Binding TravelDistance}"

                                                          Margin="10,0,0,0" />

                                                <TextBlock Text="km"/>

                                            </StackPanel>

                                        </DataTemplate>

                                    </ListBox.ItemTemplate>

                                </ListBox>

                            </DataTemplate>

                        </ListBox.ItemTemplate>

                    </ListBox>

                </StackPanel>

            </StackPanel>

        </ScrollViewer>

 

        <!-- Map Area -->

        <m:Map Name="MyMap" Grid.Column="1" Credentials="YOUR_BING_MAPS_KEY"/>

    </Grid>

</Page>

If you create the event handlers for the buttons and the GeocodeResultsListBox you can then run the application to see what it looks like. You should end up with something like this:

wsLayout

Adding the Application Logic

Open up the MainPage.xaml.cs file. In here we will create a global MapShapeLayer variable called routeLayer inside the MainPage class. We will use this shape layer to display the route line on the map. In the constructor of the MainPage class we will initialize this variable and add it to the map. At this point, the MainPage.xaml.cs file will look like this:

using Bing.Maps;

using BingMapsRESTService.Common.JSON;

using System;

using System.Runtime.Serialization.Json;

using System.Threading.Tasks;

using Windows.UI;

using Windows.UI.Popups;

using Windows.UI.Xaml;

using Windows.UI.Xaml.Controls;

using Windows.UI.Xaml.Media;

using Windows.UI.Xaml.Navigation;

 

namespace BingMapsSearch_WinRT_CS

{

    publicsealedpartialclassMainPage : Page

    {

        privateMapShapeLayer routeLayer;

 

        public MainPage()

        {

            this.InitializeComponent();

 

            routeLayer = newMapShapeLayer();

            MyMap.ShapeLayers.Add(routeLayer);

        }

 

        protectedoverridevoid OnNavigatedTo(NavigationEventArgs e)

        {

        }

    }

}

There are a few other common methods that we will want to add to this file to make things a bit easier and keep the code clean. The first common task we will want to do is notify the user if there is an error with their request. In .NET we would normally use the MessageBox class, but this is not available to Windows Store applications. Instead, we have to use the MessageDialog class. This class has a lot more options than we need, so to keep things simple, we will create a method that takes in a string message and displays it to the user. We will call this method ShowMessage and use the following code:

privateasyncvoid ShowMessage(string message)

{

    MessageDialog dialog = newMessageDialog(message);

    await dialog.ShowAsync();

}

The second common task is to call the Bing Maps REST service and then serialize the response into a BingMapsRESTService.Common.JSON.Response object. This class comes from the data contracts we copied in from MSDN documentation on Using the REST services with .NET. In that blog post we made use of the WebClient class to make our request to the service. In Windows Store applications we do not have access to this class and instead make use of the System.Net.Http.HttpClient class. We will call this common method GetResponse and use the following code:

privateasyncTask<Response> GetResponse(Uri uri)

{

    System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();

    var response = await client.GetAsync(uri);

 

    using (var stream = await response.Content.ReadAsStreamAsync())

    {

        DataContractJsonSerializer ser = newDataContractJsonSerializer(typeof(Response));

        return ser.ReadObject(stream) asResponse;

    }

}

In this last common method, we will clear the map and the results panels of our application. The map has to be cleared before we process any new requests. We will also need this functionality for the clear map button. We will call this method ClearMap and use the following code:

privatevoid ClearMap()

{

    MyMap.Children.Clear();

    routeLayer.Shapes.Clear();

 

    //Clear the geocode results ItemSource

    GeocodeResults.ItemsSource = null;

 

    //Clear the route instructions

    RouteResults.DataContext = null;

}

We can now add the button handler for the Clear Map button. All this button handler will do is call our ClearMap method. The handler will look like this:

privatevoid ClearMapBtn_Click(object sender, RoutedEventArgs e)

{

    ClearMap();

}

Adding the Geocoding Logic

We can now add the geocoding logic to the MainPage.xaml.cs file. When the user presses the geocode button we will get their input and check that it is a valid string. If it is, we will then create the URL to geocode the query against the Bing Maps REST Location service and then pass this URL to our GetResponse method. Next, we will display all the locations results on the map as pushpins and add a tap event that will trigger the name of the location to be displayed to the user. We will also pass the results to the GeocodeResults ListBox which will list the location results in the side panel. The GeocodeBtn_Click event handler will look like this:

privateasyncvoid GeocodeBtn_Click(object sender, RoutedEventArgs e)

{

    ClearMap();

 

    string query = GeocodeTbx.Text;

 

    if (!string.IsNullOrWhiteSpace(query))

    {

        //Create the request URL for the Geocoding service

        Uri geocodeRequest = newUri(

            string.Format("http://dev.virtualearth.net/REST/v1/Locations?q={0}&key={1}",

            query, MyMap.Credentials));

 

        //Make a request and get the response

        Response r = await GetResponse(geocodeRequest);

 

        if (r != null&&

            r.ResourceSets != null&&

            r.ResourceSets.Length > 0 &&

            r.ResourceSets[0].Resources != null&&

            r.ResourceSets[0].Resources.Length > 0)

        {

            LocationCollection locations = newLocationCollection();

 

            int i = 1;

 

            foreach (BingMapsRESTService.Common.JSON.Location l

                     in r.ResourceSets[0].Resources)

            {

                //Get the location of each result

                Bing.Maps.Location location =

                      new Bing.Maps.Location(l.Point.Coordinates[0], l.Point.Coordinates[1]);

 

                //Create a pushpin each location

                Pushpin pin = newPushpin(){

                    Tag = l.Name,

                    Text = i.ToString()

                };

 

                i++;

 

                //Add a tapped event that will display the name of the location

                pin.Tapped += (s, a) =>

                {

                    var p = s asPushpin;

                    ShowMessage(p.Tag asstring);

                };

 

                //Set the location of the pushpin

                MapLayer.SetPosition(pin, location);

 

                //Add the pushpin to the map

                MyMap.Children.Add(pin);                     

 

                //Add the coordinates of the location to a location collection

                locations.Add(location);

            }

 

            //Set the map view based on the location collection

            MyMap.SetView(newLocationRect(locations));

 

            //Pass the results to the item source of the GeocodeResult ListBox

            GeocodeResults.ItemsSource = r.ResourceSets[0].Resources;

        }

        else

        {

            ShowMessage("No Results found.");

        }

    }

    else

   {

        ShowMessage("Invalid Geocode Input.");

    }

}

Next, we will add a SelectionChanged event to the ListBox. When triggered, it will call a method named GeocodeResultSelected. This method will zoom in to the selected location. The following code will be used for this method:

privatevoid GeocodeResultSelected(object sender, SelectionChangedEventArgs e)

{

    var listBox = sender asListBox;

 

    if (listBox.SelectedItems.Count > 0)

    {

        //Get the Selected Item

        var item = listBox.Items[listBox.SelectedIndex]

                   as BingMapsRESTService.Common.JSON.Location;

 

        //Get the items location

        Bing.Maps.Location location =

               new Bing.Maps.Location(item.Point.Coordinates[0], item.Point.Coordinates[1]);

 

        //Zoom into the location

        MyMap.SetView(location, 18);

    }

}

If you run the application and do a search for “London” your application should look like this:

wsGeo

Adding the Routing Logic

We can now add the routing logic to the MainPage.xaml.cs file. When the user presses the “Get Directions” button we will first get their input locations and check that they are valid strings. If they are valid, we will create the URL to get the driving route between the two locations and pass this URL to our GetResponse method. Next, we will display the route line on the map along with a pushpin for the start and end points. We will also display the route itinerary items in a ListBox in the side panel. The RouteBtn_Click event handler will look like this:

privateasyncvoid RouteBtn_Click(object sender, RoutedEventArgs e)

{

    ClearMap();

 

    string from = FromTbx.Text;

    string to = ToTbx.Text;           

 

    if (!string.IsNullOrWhiteSpace(from))

    {

        if (!string.IsNullOrWhiteSpace(to))

        {

            //Create the Request URL for the routing service

            Uri routeRequest = newUri(                string.Format("http://dev.virtualearth.net/REST/V1/Routes/Driving?wp.0={0}&wp.1={1}&rpo=Points&key={2}", from, to, MyMap.Credentials));

                   

            //Make a request and get the response

            Response r = await GetResponse(routeRequest);

 

            if (r != null&&

                r.ResourceSets != null&&

                r.ResourceSets.Length > 0 &&

                r.ResourceSets[0].Resources != null&&

                r.ResourceSets[0].Resources.Length > 0)

            {

                Route route = r.ResourceSets[0].Resources[0] asRoute;

 

                //Get the route line data

                double[][] routePath = route.RoutePath.Line.Coordinates;

                LocationCollection locations = newLocationCollection();

 

                for (int i = 0; i < routePath.Length; i++)

                {

                    if (routePath[i].Length >= 2)

                    {

                        locations.Add(new Bing.Maps.Location(routePath[i][0],

                                      routePath[i][1]));

                    }

                }

 

                //Create a MapPolyline of the route and add it to the map

                MapPolyline routeLine = newMapPolyline()

                {

                    Color = Colors.Blue,

                    Locations = locations,

                    Width = 5

                };

 

                routeLayer.Shapes.Add(routeLine);

 

                //Add start and end pushpins

                Pushpin start = newPushpin()

                {

                    Text = "S",

                    Background = newSolidColorBrush(Colors.Green)

                };

 

                MyMap.Children.Add(start);

                MapLayer.SetPosition(start,

                    new Bing.Maps.Location(route.RouteLegs[0].ActualStart.Coordinates[0],

                        route.RouteLegs[0].ActualStart.Coordinates[1]));

 

                Pushpin end = newPushpin()

                {

                    Text = "E",

                    Background = newSolidColorBrush(Colors.Red)

                };

 

                MyMap.Children.Add(end);

                MapLayer.SetPosition(end,

                    new Bing.Maps.Location(route.RouteLegs[0].ActualEnd.Coordinates[0],

                    route.RouteLegs[0].ActualEnd.Coordinates[1]));

 

                //Set the map view for the locations

                MyMap.SetView(newLocationRect(locations));

 

                //Pass the route to the Data context of the Route Results panel

                RouteResults.DataContext = route;

            }

            else

            {

                ShowMessage("No Results found.");

            }

        }

        else

        {

            ShowMessage("Invalid 'To' location.");

        }

    }

    else

    {

        ShowMessage("Invalid 'From' location.");

    }

}

At this point, we have accomplished all that we set out to do in this blog post. If you run the application now and calculate a route from “New York” to “Miami” you should end up with something like this:

wsRoute

- Ricky Brundritt, EMEA Bing Maps Technology Solution Professional


Overview of Bing Maps SDK for Windows Store apps in just one minute

$
0
0

Check out the new One dev minute video and tutorial that takes you through all the steps you need to display your location on a map in a C# Windows Store app using the Bing Maps for Windows Store apps SDK. Simple clear instructions take you from signing up for a Trial or Basic Windows Store app Bing Maps key [also see http://www.microsoft.com/maps for more details about key types] to creating the project and adding the map – it’s all there and you can rewind and review as much as you need as well as check out the detailed written tutorial steps. And if you want to go straight to the code, you can get it here.

-The Bing Maps Team

Geocoding Dynamics CRM Data with Bing Maps

$
0
0

The majority of data in the enterprise today has a location component, and this includes much of the entity data in Dynamics CRM. We can leverage the location attributes of our CRM data to provide a wide variety of location-based functionality, including geospatial visualization, finding nearest service agents to jobs, optimizing routes for mobile sales people, analyzing our data in heat maps and thematic maps, and much more. One of the fundamentals required to be able to leverage this location data is the “geocoding” of our location data. Geocoding is the process of taking text-based location data such as addresses or place names, and turning them into geographic coordinates. With accurate geographic coordinates (or latitudes and longitudes) for our entities, we can visualize them on a map, and analyze them spatially. In this blog post, we will review options available for geocoding Dynamics CRM data, including:

● Batch geocoding with Spatial Data Services

● Geocoding via Dynamics CRM Plug-ins

We will be working with:

Dynamics CRM Online

● Excel 2013

● Visual Studio 2010

● The Developer Toolkit for Microsoft Dynamics CRM 2011 and Microsoft Dynamics CRM Online

● The Plug-in Registration Tool from the Microsoft Dynamics CRM SDK download

● A Bing Maps Account that has access to the Bing Maps Spatial Data Services

Batch Geocoding with Spatial Data Services:

We will first walk through the steps involved in geocoding entity data that is already contained within our CRM instance with the use of the Bing Maps Spatial Data Services Geocode Dataflow API. At a high level, we will be exporting entity data using the Dynamics CRM Web Client, geocoding the data using the Geocode Dataflow API, and then re-importing the data with latitudes and longitudes appended to each record.

In this example, we will geocode all Account records which have not already been geocoded. We start by logging into Dynamics CRM Online, selecting Accounts from the left navigation, and then selecting Advanced Find from the Data group on the Accounts tab of the ribbon.

In the resulting window, create a New query, and select Account records in which either the address Latitude or Longitude do not have data:

CRM 1

Now select Edit Columns for the View, and choose those representing the Street, Town, State/Province, ZIP/Postal Code, Country/Region, Latitude, and Longitude of the account address you wish to geocode:

CRM 2

Rearrange the order of the columns as shown below by selecting a column header and using the green arrow buttons:

CRM 3

Now execute your query to view the results by selecting Results. Once the results are displayed, Choose ‘Data… Export Accounts’ from the ribbon:

CRM 4

Choose to export to a ‘Static worksheet’, with data from all pages in the current view, if applicable. Make sure you select the checkbox to ‘Make the data available for re-importing’

CRM 5

When prompted, save the resulting XML Spreadsheet file to your machine, rather than opening it directly. Now open the file in Excel. Note that there are several hidden columns, including column A, which contains a unique ID for each record, along with the columns D through H which contain the address data needed for geocoding. Unhide all columns:

CRM 6

We will now extract the relevant data from this spreadsheet, and create a second spreadsheet which we will use with the Bing Maps Spatial Data Services Geocode Dataflow API.

Since the console application we will use conforms to the Geocode Dataflow API Data Schema v1, we will arrange our data to conform to that. Our mapping of columns will be:

● Column A: Unique Account ID

● Column D: Street

● Column E: State / Province

● Column F: Country / Region

● Column I: Town

● Column J: ZIP / Postal Code

We will now save the spreadsheet as Text (Tab-delimited), ready for use with the sample console application.

Now use Visual Studio to create a Visual C# Console Application, and copy the code from the Geocode Dataflow Sample Code. If necessary, make two small tweaks to the sample code. To make it easy to open our geocoding output data in Excel, we will use the WriteLine() method of the StreamWriter class, instead of the Write() method, to include line breaks in the output data (successfile and failedfile). Build the application, in preparation for geocoding the data.

The Geocode Dataflow API uses a REST API to enable you to:

● HTTP POST up to 200,000 records per job

● Check the status of your job, to determine when it has completed

● Download the results

Our Console Application will handle the interactions with the Geocode Dataflow API for us.

The Console Application takes in four parameters:

dataFilePath: The path to the file that contains the spatial data to geocode

dataFormat: The format of the input data. Possible values are xml, csv, tab and pipe. We are using tab format

key: The Bing Maps Key to use for this job. The same key is used to get job status and download results.

description: Text that is used to describe the geocode dataflow job.

Open up a Command Prompt, and execute the Console Application as shown below:

CRM 7

Upon completion, our geocoding output should be in a file named Success.txt in the current directory. Import this tab-delimited file into Excel, using UTF-8 encoding. The format of this file will conform to the Geocode Dataflow Schema v1.

The fields which will be of main interest to us will be these ones:

● GeocodeEntity/GeocodeResponse/RooftopLocation/@Latitude (Excel Column U)

● GeocodeEntity/GeocodeResponse/RooftopLocation/@Longitude (Excel Column V)

● GeocodeEntity/GeocodeResponse/InterpolatedLocation/@Latitude (Excel Column W)

● GeocodeEntity/GeocodeResponse/InterpolatedLocation/@Longitude (Excel Column X)

However, if you wish to be more selective about the geocoding results that you import into Dynamics CRM, you could use other fields as well. For example, you could choose to import only those results which returned an Address EntityType, with a High Confidence.

Generally speaking, unless we are using our latitudes and longitudes for routing purposes, we will want the Rooftop coordinates when available, and taking the Interpolated coordinates when Rooftop coordinates are not available.

We can now retrieve the most appropriate coordinates from our Success.txt file, and bring them into the Latitude and Longitude columns of our original spreadsheet. There are a number of options for doing this, but in this case, we use the VLOOKUP function in Excel to retrieve the coordinates from the appropriate record in the Success.txt file. If Rooftop coordinates are available, we use them, and if they are not, we use the Interpolated coordinates. If neither is available, or if the VLOOKUP fails, we leave the cell blank. The actual Excel formula used to populate the Latitude column for row 2 is:

=IF(ISERROR(VLOOKUP(A2,Success.txt!$A$1:$AB$6,21,FALSE)),"",IF(VLOOKUP(A2,Success.txt!$A$1:$AB$6,21,FALSE)<>"",VLOOKUP(A2,Success.txt!$A$1:$AB$6,21,FALSE),IF(VLOOKUP(A2,Success.txt!$A$1:$AB$6,23,FALSE)<>"",VLOOKUP(A2,Success.txt!$A$1:$AB$6,23,FALSE),"")))

 

For longitude, we simply add one to the Column Index Number values of each VLOOKUP.

Now convert the formulas to values by highlighting the Latitude and Longitude cells, copying the data, and then Paste Values in the same cells.

After saving, we now have an updated Excel file ready for re-import to Dynamics CRM, with coordinates for our records:

CRM 8

Now we log back into Dynamics CRM Online, and select Accounts from the navigation. From the Accounts tab of the ribbon, choose Import Data from the Data group. In the resulting window, select the location of the XML Spreadsheet file containing our updates.

When prompted, select No for Allow Duplicates, and click Submit:

CRM 9

The data will now be imported, with Latitudes and Longitudes appended to the relevant records in Dynamics CRM.

Geocoding via Plugins

We will now walk through the process of creating a Dynamics CRM Plug-in which will enable CRM entity data to be geocoded whenever a new entity is added or updated. Our MSDN documentation describes a plug-in as custom business logic (code) that you can integrate with Microsoft Dynamics CRM 2011 and Microsoft Dynamics CRM Online to modify or augment the standard behavior of the platform. In our case, our plug-in will leverage the REST Locations API to geocode the address of any new accounts that are added or existing accounts that are updated, adding the resulting Latitude and Longitude to the record.

In Visual Studio, we will create a new Project, and will select a Dynamics CRM 2011 Package using Visual C#:

CRM 10

We are now prompted to Connect to Dynamics CRM Server. Since we are using Dynamics CRM Online, we enter dev.crm.dynamics.com as our CRM Discovery Server Name. We also enter our Microsoft account credentials for authentication:

CRM 11

We now add a new C# Dynamics CRM 2011 Plug-in Library project to our package:

CRM 12

We will now add a Visual C# Class to our GeocodeAccountsPlugin, called GeocodeAccounts.cs. In this class, we will add our code to read the address data from an Account record, geocode the address data using the REST Locations API, and then update the Latitude and Longitude of the Account record if a successful geocoding result is obtained.

When we register our plug-in, we will have execution take place post-operation, and asynchronously, for performance reasons. We will register and make use of a Post Image of our entity, which is effectively a reflection of the Account attributes once the Account Create or Update operation has completed.

The logic of our plug-in can be summarized as:

● The Account address details are retrieved from the Post Image

● The address details are used to prepare a URL for a request to the Bing Maps Locations API, specifying an XML response format

● The request is executed and the response retrieved with the use of the HttpWebRequest class

● The XML response is parsed and the Latitude and Longitude values of the first geocoding result are retrieved using XPath

● The Latitude and Longitude of the Account record are updated using IOrganizationService.Update()

namespace GeocodeAccountsPackage.GeocodeAccountsPlugin

{

    using System;

    using System.ServiceModel;

    using Microsoft.Xrm.Sdk;

    using System.Xml;

    using System.Net;

    using System.Xml.XPath;

 

    ///<summary>

    /// Geocode Account Plugin.

    /// Fires on Create of Account, or Update of Account address properties

    ///</summary>   

    publicclassGeocodeAccounts : IPlugin

    {

        privatereadonlystring postImageAlias = "PostImage";

        privatereadonlystring BingMapsKey = "Insert Key Here";

 

        publicvoid Execute(IServiceProvider serviceProvider)

        {

            // Obtain the execution context from the service provider.

            Microsoft.Xrm.Sdk.IPluginExecutionContext context = (Microsoft.Xrm.Sdk.IPluginExecutionContext)

                serviceProvider.GetService(typeof(Microsoft.Xrm.Sdk.IPluginExecutionContext));

 

            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));

            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

 

            // Obtain the post entity image:

            Entity postImage = (Entity)context.PostEntityImages[postImageAlias];

 

            // The InputParameters collection contains all the data passed in the message request.

            if (context.InputParameters.Contains("Target") &&

                context.InputParameters["Target"] isEntity)

            {

                // Obtain the target entity from the input parmameters.

                Entity entity = (Entity)context.InputParameters["Target"];

 

                // Verify that the target entity represents an account.

                if (entity.LogicalName == "account")

                {

                    try

                    {

 

                        // Grab the postImage address attributes for our geocoding request:

                        string addressLine = (postImage.Attributes.ContainsKey("address1_line1")) ? (string)postImage.Attributes["address1_line1"] : "";

                        string locality = (postImage.Attributes.ContainsKey("address1_city")) ? (string)postImage.Attributes["address1_city"] : "";

                        string adminDistrict = (postImage.Attributes.ContainsKey("address1_stateorprovince")) ? (string)postImage.Attributes["address1_stateorprovince"] : "";

                        string postalCode = (postImage.Attributes.ContainsKey("address1_postalcode")) ? (string)postImage.Attributes["address1_postalcode"] : "";

                        string countryRegion = (postImage.Attributes.ContainsKey("address1_country")) ? (string)postImage.Attributes["address1_country"] : "";

 

                        //Get latitude and longitude coordinates for specified location

                        XmlDocument searchResponse = Geocode(addressLine, locality, adminDistrict, postalCode, countryRegion);

 

                        //Create namespace manager

                        XmlNamespaceManager nsmgr = newXmlNamespaceManager(searchResponse.NameTable);

                        nsmgr.AddNamespace("rest", "http://schemas.microsoft.com/search/local/ws/rest/v1");

 

                        //Get all geocode locations in the response

                        XmlNodeList locationElements = searchResponse.SelectNodes("//rest:Location", nsmgr);

                        if (locationElements.Count == 0)

                        {

                            // No geocoding results, so do nothing

                        }

                        else

                        {

                            //Get the geocode location points that are used for display (UsageType=Display)

                            XmlNodeList displayGeocodePoints =

                                    locationElements[0].SelectNodes(".//rest:GeocodePoint/rest:UsageType[.='Display']/parent::node()", nsmgr);

                            string latitude = displayGeocodePoints[0].SelectSingleNode(".//rest:Latitude", nsmgr).InnerText;

                            string longitude = displayGeocodePoints[0].SelectSingleNode(".//rest:Longitude", nsmgr).InnerText;

 

                            Entity account = newEntity("account");

                            account.Attributes.Add("accountid", entity.Attributes["accountid"]);

                            account.Attributes.Add("address1_latitude", double.Parse(latitude));

                            account.Attributes.Add("address1_longitude", double.Parse(longitude));

                            service.Update(account);

 

                        }

 

                    }

                    catch (FaultException<OrganizationServiceFault> ex)

                    {

                        thrownewInvalidPluginExecutionException("An error occurred in the Account Update plug-in.", ex);

                    }

                }

            }

        }

 

 

        // Submit a REST Services or Spatial Data Services request and return the response

        protectedXmlDocument GetXmlResponse(string requestUrl)

        {

 

            HttpWebRequest request = WebRequest.Create(requestUrl) asHttpWebRequest;

            using (HttpWebResponse response = request.GetResponse() asHttpWebResponse)

            {

                if (response.StatusCode != HttpStatusCode.OK)

                    thrownewException(String.Format("Server error (HTTP {0}: {1}).",

                    response.StatusCode,

                    response.StatusDescription));

                XmlDocument xmlDoc = newXmlDocument();

                xmlDoc.Load(response.GetResponseStream());

                return xmlDoc;

            }

        }

 

        // Geocode an address and return the XML response:

        protectedXmlDocument Geocode(string addressLine, string locality, string adminDistrict, string postalCode, string countryRegion)

        {

            //Create REST Services geocode request using Locations API

            string geocodeRequest = String.Format("https://dev.virtualearth.net/REST/v1/Locations?countryRegion={0}&adminDistrict={1}&locality={2}&postalCode={3}&addressLine={4}&o=xml&key={5}",

                System.Uri.EscapeDataString(countryRegion),

                System.Uri.EscapeDataString(adminDistrict),

                System.Uri.EscapeDataString(locality),

                System.Uri.EscapeDataString(postalCode),

                System.Uri.EscapeDataString(addressLine),

                BingMapsKey);

 

            //Make the request and get the response

            XmlDocument geocodeResponse = GetXmlResponse(geocodeRequest);

 

            return (geocodeResponse);

        }

 

 

    }

}

 

 

After successfully building, we will sign the assembly, through the plug-in Properties…Signing (shown below) and then build in preparation for registering our plug-in:

CRM 13

We will now register our plug-in, using the process outlined in the Walkthrough: Register a Plug-in Using the Plug-in Registration Tool on MSDN (you can refer to this article for additional step-by-step information).

We run the Plug-in Registration tool, connect to the Dynamics CRM Online server, and then register our plug-in assembly:

CRM 14

We can follow the step-by-step instructions in the MSDN walkthrough to Register a Plug-in Assembly.

Next, we will register the plug-in for two events: Account Create and Account Update.

In each case, we will select (Assembly) GeocodeAccountsPackage.GeocodeAccountsPlugin in the tree view, and then select Register New Step from the Register menu.

For the Account Create event, we will select the following options:

CRM 15

For the Account Update event, we will select very similar options, adding in Filtering Attributes to ensure the plug-in is only executed on update when the address details for the account change. The Filtering Attributes used will be:

● Street 1 (address1_line1)

● City (address1_city)

● State/Province (address1_stateorprovince)

● ZIP/Postal Code (address1_postalcode)

● Country/Region (address1_country)

CRM 16

We will now Register a New Image for each step, to give us access to the address details of the Account after the Create and Update operations have executed. We specify an Entity Alias (‘PostImage’) which matches the Post Image alias we referenced in our plug-in code. Note how we specify only the address attributes for the Parameters, as these will be used when geocoding the address:

CRM 17

We register images for both the Create and Update steps.

Our Tree View in the Plug-in Registration Tool should now look like this:

CRM 18

We are now ready to add new account records, and update existing account records through the Dynamics CRM Online Web Application, to verify that our plug-in is geocoding the respective addresses. Note that because our plug-in is executing asynchronously after execution of the creation or update of our Accounts, the new Latitude and Longitude values may not necessarily be reflected immediately. After we reopen a newly created or updated record, we should see the new Latitude and Longitude values reflected.

CRM 19

Other Geocoding Options

In addition to geocoding via batch jobs, and via plug-ins, some other options that could be explored include:

● Geocoding individual addresses through HTML Web Resources in forms with the AJAX v7 map control and Search module

● Geocoding individual addresses through Form Scripting using the Bing Maps Locations API

Once our Dynamics CRM Accounts and other entities are geocoded, we can start to take advantage of your location data to find nearby accounts, create optimized itineraries, visualize data in specific territories or areas, and much more. For information on how to create a heat map of our data in Dynamics CRM, see my previous post on this topic.

The code for the GeocodeAccounts.cs class and the Geocode Dataflow console application can be found here

-Geoff Innis, Bing Maps Technical Specialist

New to REST Services: Elevations API

$
0
0

We are happy to announce the release of Elevations API as the latest addition to the Bing Maps REST Services offerings. The Elevations API will enable you to query for elevation information for a set of points, polyline or a region on the Earth described by latitude and longitude pairs.

Here are some example use cases for this API. Let’s say you have a sports GPS tracking device that tracks your distance, speed and elevation while you are on a hike. In this scenario, the Elevations API can be used to provide you with elevation information at specific points during the hike. In another scenario, you can visualize the elevation profile along a pre-determined route. And, of course, if you want to get fancy with 3D, you now have the opportunity to develop 3D elevation models for an area on the Earth.

Currently elevation information is available with the following resolutions: Global Coverage (including poles): 900m resolution (Globe), Global Coverage (56⁰S to 60⁰N):90m resolution (SRTM).US coverage: 10m resolution (NED).

One of the interesting features of this API is the ability to provide elevation data for a region on the Earth or bounding box. With this feature, you can fine-tune the number of elevation points you get back for the bounding box. To give you even more flexibility for your scenarios, the elevation values can be calculated using two different Earth models -- the ellipsoid model and the geoid sea level model. The ellipsoid model uses the World Geodetic System (WGS84) which is an ellipsoidal approximation of the Earth. The geoid sea level model uses the Earth Gravitational Model 2008 (EGM2008 2.5’) and computes a sea level based on the local value of gravity. The ellipsoid model is equivalent to GPS and the geoid sea level model is equivalent to what is commonly known as the height above sea level.

The detailed documentation for this service can be found here.

John O’Brien, director of Soul Solutions, is a Bing Maps MVP and was part of the first look into the new service. It was really cool to see John explore and provide an early preview into the applicable use of our elevation service API which also provided new solutions for his clients. Below are just two examples of how the elevation API is helping John address the needs of his clients.

Firstly, a simple example where you can drag two pins around the map and see the profile of the terrain between them

Elevation 1

The second, using the existing direction module to plot a course by car then visualize the elevation change.

Elevation 2

The V7 AJAX module John built provides an option for developers to add this functionality with a few lines of code. You can download the module plus the example code for the images here.

-The Bing Maps Team

Bing Maps for Windows Store Apps Training Kit

$
0
0

Bing Maps for Windows Store apps combine the power of Windows 8 and Bing Maps to provide an enhanced mapping experience for Windows Store apps. Developers can use this Bing Maps control to incorporate the latest road maps, aerial views, and low-angle high-resolution images into a Windows Store app.

Today we are happy to announce the release of the Bing Maps for Windows Store Apps Training Kit. This training kit is made up of a PowerPoint slide deck which serves as an overview to create a Windows Store App using Bing Maps. The slide deck also includes information on existing applications which use Bing Maps that are in the Windows Store. In addition to the training deck there are 2 labs. The first has been designed for the JavaScript developer and the second, for the Native code developer. Each lab is made up of multiple exercises and should take around 90 minutes to complete.

JavaScript Lab:

* Exercise 1: Creating a Bing Maps Account and Key

* Exercise 2: Loading the Bing Maps control

* Exercise 3: Integrating Location Services

* Exercise 4: Implement Bing Maps Modules

* Exercise 5: Implementing Geocoding and Routing

* Exercise 6: Access the Bing Maps REST Services

Native Lab:

* Exercise 1: Creating a Bing Maps Account and Key

* Exercise 2: Loading the Bing Maps control

* Exercise 3: Integrating Location Services

* Exercise 4: Implementing Geocoding and Routing

You can download the Bing Maps for Windows Store Apps Training Kit here.

- Ricky Brundritt, EMEA Bing Maps Technology Solution Professional

Spatial Data Services: Job History & Data Source Options

$
0
0

New job history! Have you ever wanted to check status of your geocode and data source jobs and see what jobs you’ve run recently? Bing Spatial Data Services now makes it easy to view your job history. With the new ListJobs API , you can return a list of all geocode jobs and jobs that made changes to data sources in the last 15 days for any Bing Maps Account. Pending jobs are listed first. All you need to do is provide a Bing Maps Key from your Bing Maps Account with the following URL.

http://spatial.virtualearth.net/REST/v1/dataflows/listjobs?key=Insert_Your_Bing_Maps_Key_Here

A JSON response is returned by default. Add “output(o)=xml” to return XML. Jobs that download data source data do not make changes to your data source and therefore are not part of this list.

More __Distance query parameter options. Want to simply get all your entity data plus the distance to each entity? Bing Spatial Data Services now accepts the wildcard character with the __Distance propertywhen you Query by Area. Just specify $select=*,__Distance in your request. The distance option has also been expanded to support bounding box (rectangular region) queries. For bounding box queries, the distance returned for each entity is the distance from the center of the bounding box.

Make your data source public! Want to share your data with the world? Bing Spatial Data Services now gives you the option to make your data source public so that anyone with a Bing Maps Key can access. All you have to do is a Make a Data Source Public request as shown below. Set the setpublic parameter to true, and your data is publicly shared.

http://spatial.virtualearth.net/REST/v1/data/accessID/dataSourceName/$updateDataSource?setPublic=trueOrFalse&key= Insert_Your_Master_Key_Here

You can always make a data source private again by making the request again and setting the setpublic parameter to false. This parameter is also available when you Create or update your data source.

Delete selected entities with the new deleteEntity property. Want the ability to delete specific entities when you update your data source while leaving the rest of your data source intact? Bing Spatial Data Services can now incrementally delete entities when you use the __deleteEntityproperty in your schema and set it to 1 or true for each entity you want to remove when you update your data source.

We hope you enjoy these new features!

-The Bing Maps Team

Venue Maps in Windows Store Apps (JavaScript)

$
0
0

Within Bing Maps you have the ability to view Venue Maps. Venue Maps are often thought of as maps of indoor structures such as malls and airports; however, venue maps can be opened up to so much more. A few examples of other types of venue maps include: the layout of shopping districts, stadiums, race courses, and universities.

Using Venue Maps in Bing Maps requires loading in the Venue Map module. If you are not familiar with modules in Bing Maps take a look at the blog post on Bing Maps Modules in Windows Store Apps.

In this blog post, we are going to create a simple application that when zoomed in, will load in nearby venues into Bing Maps in your Windows Store App as you pan the map around.

Setting up the project

To set up the project, open Visual Studios 2012 and create a new project. In the window that opens select JavaScript -> Windows Store. Select the Blank App template, and call the application VenueMaps_JS and press OK.

To keep things clean we are going to create a separate JavaScript file for our application. Right click on the js folder and select Add -> New Item. Create a new JavaScript file called VenueMaps.js.

At this point your Solution should look something like this:

 

clip_image001

 

Next, add a reference to the Bing Maps JavaScript library. To do this, right click on the References folder and press Add Reference. Select Windows -> Extensions select Bing Maps for JavaScript. If you do not see this option ensure that you have installed the Bing Maps Windows Store App SDK.

Now we can open up the default.html file and add references to the Bing Maps SDK along with references to the Bing Maps modules API and our JavaScript file. While we are at it, we will also add a div to the body of the page where we want the map control to load. Your default.html file should look like this:

 

<!DOCTYPEhtml>

<html>

<head>

    <metacharset="utf-8"/>

    <title></title>

 

    <!-- WinJS references -->

    <linkhref="//Microsoft.WinJS.1.0/css/ui-dark.css"rel="stylesheet"/>

    <scriptsrc="//Microsoft.WinJS.1.0/js/base.js"></script>

    <scriptsrc="//Microsoft.WinJS.1.0/js/ui.js"></script>

 

    <!-- VenueMaps_JS references -->

    <linkhref="/css/default.css"rel="stylesheet"/>

    <scriptsrc="/js/default.js"></script>

 

    <!-- Bing Maps references -->

    <scripttype="text/javascript"

            src="ms-appx:///Bing.Maps.JavaScript//js/veapicore.js"></script>

    <scripttype="text/javascript"

        src="ms-appx:///Bing.Maps.JavaScript//js/veapimodules.js"></script>

 

    <!-- Our Venue Maps JavaScript Code -->

    <scriptsrc="/js/VenueMaps.js"></script>

 

    <styletype="text/css">

        div {

            color:#000;

        }

    </style>

</head>

<body>

    <divid="myMap"></div>

</body>

</html>

Loading the Bing Maps Control

We will add the functionality to load the Bing Maps control to the VenueMap.js file. To start, we are going to create six global variables called: map, _mapBounds, _zoom, venueFactory, loadedVenues, and loadedVenueIds. Then, we will create a function called GetMap which will load the Bing Maps control for us. When loading the Bing Maps control we will also turn on the breadcrumb. The breadcrumb allows us to select different floors when viewing a venue. Inside of this function we will load in the Venue Maps module and initialize the venueFactory object. Now we will add a throttled event handler for when the map has finished moving that will get nearby venues. The GetMap function will be loaded after the Microsoft.Maps.Map module has completed loading. Your code should look like this:

 

var map,

    loadedVenues = [],

    loadedVenueIds = [],

    _mapBounds,

    _zoom,

    venueFactory;

 

function GetMap() {

    var mapOptions =

    {

        credentials: "YOUR_BING_MAPS_KEY",

        zoom: 2,

        showBreadcrumb : true

    };

 

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

 

    //Load the venue map module

    Microsoft.Maps.loadModule('Microsoft.Maps.VenueMaps', {

        callback: function () {

            venueFactory = new Microsoft.Maps.VenueMaps.VenueMapFactory(map);

 

            //Use a throttled event to reduce the number of unwanted events being fired.

            Microsoft.Maps.Events.addThrottledHandler(map, 'viewchangeend', GetNearbyVenues, 250);

        }

    });

}

 

//Initialization logic for loading the map control

(function () {

    function initialize() {

        Microsoft.Maps.loadModule('Microsoft.Maps.Map', { callback: GetMap });

    }

 

    document.addEventListener("DOMContentLoaded", initialize, false);

})();

Adding the Application Logic

When the veiwchangeend event handler is fired, it will trigger a function called GetNearbyVenues. This function will first loop through all loaded venues and dispose those that are out of view or all of them if the zoom level is less than 14. When disposing a venue we will also delete the stored venue id in the loadedVenueIds array. If the zoom level is greater or equal to 14 we will do a search for all venues that are within 4000 meters of the center of the map.

 

function GetNearbyVenues() {

    _mapBounds = map.getBounds();

    _zoom = map.getZoom();

 

    //Dispose a venue if it is outside of the map view or if the zoom  level is too high

    var venuesInView = [], venue;

 

    for (var i = 0; i < loadedVenues.length; i++) {

        venue = loadedVenues[i];

 

        if (_zoom < 14 || !_mapBounds.contains(venue.center)) {

            delete loadedVenueIds[venue.id];

            venue.dispose();

        } else {

            venuesInView.push(venue);

        }

    }

 

    loadedVenues = venuesInView;

 

    if (_zoom >= 14) {

        // Search for venues that are within 4km of the center of the map

        venueFactory.getNearbyVenues({ map: map, location: map.getCenter(), radius: 4000, callback: DisplayNearbyVenues });

    }

}

 

When the search for nearby venues completes, a function called DisplayNearbyVenues will be fired. This function will loop through the venues that are returned and check if the venue has been loaded by checking the loadedVenueIds array. If the venue hasn’t been loaded we will create the venue and check to see if it is in the current map view and add both the venue and the venue id to the loadVenues and loadedVenueIds arrays. If it was loaded, we will then show it on the map. If it hasn't, we will dispose it. We will also attach a close event handler to the venue so that we can properly dispose it and remove any reference from our arrays.

 

function DisplayNearbyVenues(venues) {

    if (venues) {

        //Load new venues that are in view

        for (var i = 0; i < venues.length; i++) {

            if (loadedVenueIds[venues[i].metadata.MapId] == undefined) {

                venueFactory.create({

                    venueMapId: venues[i].metadata.MapId,

                    success: function (v) {

                        if (_mapBounds.contains(v.center)) {

 

                            //Handle close event of venue

                            Microsoft.Maps.Events.addHandler(v, 'close', function (e) {

                                delete loadedVenueIds[v.id];

                               

                                //Remove venue from loadedVenues array

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

                                    if (loadedVenues[j].id == v.id) {

                                        loadedVenues.splice(j ,ji+1);

                                        break;

                                    }

                                }

 

                                v.dispose();

                            });

 

                            loadedVenues.push(v);

                            loadedVenueIds[v.id] = true;

                            v.show();

                        } else {

                            v.dispose();

                        }

                    }

                });

            }

        }

    }

}

Running the App

At this point we have created all the functionality needed for nearby venues to be loaded as we navigate the map. If we run the app, zoom in and pan around in an area with the venue maps we should see the footprint of the venue load on the map. If you click on a venue footprint the map will zoom in on that venue and show the inside of the venue map. Here is a screen shot of the app with the venue map of Alder Hey Children’s Hospital located in Liverpool, UK.

 

Win8_Venue

- Ricky Brundritt, EMEA Bing Maps Technology Solution Professional

Geocoding and Routing in Bing Maps Windows Store Apps (JavaScript)

$
0
0

In this blog post we are going to look at how implement Geocoding and Routing using the Bing Maps Windows Store App JavaScript SDK. If you are new to JavaScript development with the Bing Maps Windows Store App SDK I recommend reading the Getting started with Bing Maps Windows Store Apps blog post first. We will also be making use of Bing Maps Modules. If you are unfamiliar with modules, I recommend reading the blog post: Modules in Bing Maps Windows Store Apps.

Geocoding is one of the most common tasks done by users of online maps. Geocoding is the process of taking an address or query and returning its equivalent coordinate on the map. Routing is the task of calculating the directions between two or more locations. In Bing Maps there are a lot of different options around routing such as routing by different modes of transportation; driving, walking, or transit. In Bing Maps there are several ways to carry out these tasks. The most common method is to use the Bing Maps REST services which are very easy to use from just about any programming language. In the JavaScript version of Bing Maps there a modules which wraps the REST services and exposes it as an easy to use JavaScript library. We will be making use of the Search module for geocoding and the Directions module for routing.

Setting up the project

To set up the project open Visual Studios 2012 and create a new project. In the window that opens select JavaScript -> Windows Store. Select the Blank App template, call the application BingMapsSearch_WinRTJS and press OK.

To keep things clean, we are going to create a separate CSS Style Sheet and JavaScript file for our application. To do this, right click on the js folder and select Add -> New Item. Create a new JavaScript file called BingMapsSearch.js. Then, right click on the css folder and select Add -> New Item and create a new Style Sheet called BingMapsSearch.css.

At this point your Solution should look something like this:

Win8_Project_Geo

Next, you’ll want to add a reference to the Bing Maps JavaScript library. Right click on the References folder and press Add Reference. Then select Windows -> Extensions and select Bing Maps for JavaScript. If you do not see this option, you may want to verify that you have installed the Bing Maps for Windows Store App SDK .

We can now open up the default.html file and add references to the Bing Maps SDK along with references to our CSS Style Sheet and JavaScript file. To do this, simply add the following in the head of the page:

<!-- Bing Maps references -->

<scripttype="text/javascript"src="ms-appx:///Bing.Maps.JavaScript//js/veapicore.js"></script>

<scripttype="text/javascript"src="ms-appx:///Bing.Maps.JavaScript//js/veapimodules.js"></script>

 

<!-- Our Bing Maps CSS & JavaScript Code -->

<linkhref="/css/BingMapsSearch.css"rel="stylesheet"/>

<scriptsrc="/js/BingMapsSearch.js"></script>


Creating the UI Layout

Before we worry about how to do all the programming of the logic required for this application, let’s first focus on creating the UI. For this app we are going to have a panel on the left side where all our input controls will reside and the map will fill up the rest of the space to the right. In the left frame we will have Clear Map button, an area for inputting geocode and route requests, and a div for rendering routing itineraries. The geocode input area will consist of a single textbox and a button. The routing input area will have two textboxes and a button. The HTML for the default.html file should look like this:

<!DOCTYPEhtml>

<html>

<head>

    <metacharset="utf-8"/>

    <title>BingMapsSearch_WinRTJS</title>

 

    <!-- WinJS references -->

    <linkhref="//Microsoft.WinJS.1.0/css/ui-dark.css"rel="stylesheet"/>

    <scriptsrc="//Microsoft.WinJS.1.0/js/base.js"></script>

    <scriptsrc="//Microsoft.WinJS.1.0/js/ui.js"></script>

 

    <!-- BingMapsSearch_WinRTJS references -->

    <linkhref="/css/default.css"rel="stylesheet"/>

    <scriptsrc="/js/default.js"></script>

 

    <!-- Bing Maps references -->

    <scripttype="text/javascript"

            src="ms-appx:///Bing.Maps.JavaScript//js/veapicore.js"></script>

    <scripttype="text/javascript"

            src="ms-appx:///Bing.Maps.JavaScript//js/veapimodules.js"></script>

 

    <!-- Our Bing Maps CSS & JavaScript Code -->

    <linkhref="/css/BingMapsSearch.css"rel="stylesheet"/>

    <scriptsrc="/js/BingMapsSearch.js"></script>

</head>

<body>

    <divclass="leftFrame">

        <!-- Clear Map Button-->

        <divstyle="height:30px;">

            <buttononclick="ClearMap();"class="greenBtn">Clear Map</button>

        </div>

 

        <!-- Geocoding Input -->

        <divstyle="height:100px;">

            <h2>Geocode</h2>

            <inputid="searchTbx"type="text"style="width:240px;"/>

            <buttononclick="Geocode();"class="greenBtn">Go</button>

        </div>

 

        <!-- Routing Input -->

        <divstyle="height:150px;">

            <h2>Route</h2>

            From: <inputid="fromTbx"type="text"style="width:200px;"/>

            To: <inputid="toTbx"type="text"style="margin-left:17px;width:200px;"/>

            <buttononclick="GetRoute();"class="greenBtn">Get Directions</button>

        </div>    

 

        <!-- Route Itinerary Area -->

        <divid="itineraryDiv"></div>

    </div>

 

    <!-- Map Area-->

    <divid="myMap"></div>

</body>

</html>

To keep things clean we will add some of the styling information into the BingMapsSearch.css file. Add the following to this file:

.leftFrame {

    position:absolute;

    height:100%;

    width:260px;

    padding:10px;

    background-color:#808080;

    overflow-y:auto;

}

 

.greenBtn {

    background-color:green;

    float:right;

    margin-right:15px;

}

 

#myMap {

    position:absolute;

    width:100%;

    height:100%;

    margin-left:280px;

}

 

#itineraryDiv {

    position:absolute;

    width:240px;

}

At this point we can run the application and see what it looks like. You should end up with something like this:

Win8_Geo_layout

Note that the map does not appear as we have not yet added the logic to load it. Also, since we haven’t wired up any of the functionality for the buttons, clicking on them now would throw an error.

Loading the Bing Maps Control

We will add the functionality to load the Bing Maps control to the BingMapsSearch.js file. We are now going to create three global variables called: map, searchManager and directionsManager. Next, we will create a function called GetMap which will load the Bing Maps control for us. The GetMap function will be loaded after the Microsoft.Maps.Map module has completed loading. Your code should look like this:

var map, searchManager, directionsManager;

 

function GetMap() {

    var mapOptions =

    {

        credentials: "YOUR_BING_MAPS_KEY",

        zoom: 2

    };

 

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

}

 

//Initialization logic for loading the map control

(function () {

    function initialize() {

        Microsoft.Maps.loadModule('Microsoft.Maps.Map', { callback: GetMap });

    }

 

    document.addEventListener("DOMContentLoaded", initialize, false);

})();


Adding the Geocoding Logic

When a user adds content to the searchTbx textbox and presses the Go button a request will be fired to a Geocode function. For this functionality we will be making use of the Search module. The first thing we will do in this function is check to see if the searchManager variable has been initialized. If it hasn’t, we will load the Search module and initialize this variable and then process the search request. If this variable has been initialized, we will then create a geocode request passing in the users input value and requests a maximum of 1 result. We will also pass in the names of callback functions that get called if the request is successful or not.

If the request is successful we will display the result on the map with a pushpin and zoom into the location. If it is not successful, we will alert the user. The request will then be geocoded against the search manager. I would like to take a moment to point out that the alert JavaScript function that is commonly used in web development is not supported in Windows Store Apps. Instead, we need to make use of the Windows.UI.Popups.MessageDialog class. And since this is likely something we will want to use regularly, we will create a simple function for displaying messages to the user called ShowMessage. Add the following code to the BingMapsSearch.js file.

function GeocodeModule() {

    ClearMap();

 

    if (searchManager) {

        var request = {

            where: document.getElementById('searchTbx').value,

            count:1,

            callback: geocodeCallback,

            errorCallback: geocodeError

        };

 

        searchManager.geocode(request);

    } else {

        //Load the Search module and create a search manager.

        Microsoft.Maps.loadModule('Microsoft.Maps.Search', {

            callback: function () {

                //Create the search manager

                searchManager = new Microsoft.Maps.Search.SearchManager(map);

 

                //Perfrom search logic

                Geocode();

            }

        });

    }

}

 

function geocodeCallback(response, userData) {

    if (response &&

        response.results &&

        response.results.length > 0) {

        var r = response.results[0];

        var l = new Microsoft.Maps.Location(r.location.latitude, r.location.longitude);

 

        //Display result on map       

        var p = new Microsoft.Maps.Pushpin(l);

        map.entities.push(p);

 

        //Zoom to result

        map.setView({ center: l, zoom : 15 });

    } else {

        ShowMessage("Geocode Response", "Not results found.");

    }

}

 

function geocodeError(request) {

    ShowMessage("Geocode Error", "Unable to Geocode request.");

}

 

function ShowMessage(title, msg) {

    var m = new Windows.UI.Popups.MessageDialog(title, msg);

    m.showAsync();

}


Adding the Routing Logic

When a user adds content to the fromTbx and toTbx textboxes and presses the Get Directions button a request will be fired to a GetRoute function. For this functionality we will be making use of the Directions module. The first thing we want to do in this function is to verify that the directionsManager variable has been initialized. If it hasn’t, we will load the Directions module and initialize this variable and then process the route request. If this variable has been initialized, we will then pass the start and end points to the directions manager and pass in the itinerary div id as the location to display the directions. We will then have the directions manager calculate the directions. Add the following code to the BingMapsSearch.js file.

function GetRoute() {

    ClearMap();

 

    if (directionsManager) {

        // Set Route Mode to driving

        directionsManager.setRequestOptions({ routeMode: Microsoft.Maps.Directions.RouteMode.driving });

 

        // Create start and end waypoints

        var startWaypoint = new Microsoft.Maps.Directions.Waypoint({ address: document.getElementById('fromTbx').value });

        var endWaypoint = new Microsoft.Maps.Directions.Waypoint({ address: document.getElementById('toTbx').value });

 

        directionsManager.addWaypoint(startWaypoint);

        directionsManager.addWaypoint(endWaypoint);

 

        // Set the id of the div to use to display the directions

        directionsManager.setRenderOptions({ itineraryContainer: document.getElementById('itineraryDiv') });

 

        // Calculate directions, which displays a route on the map

        directionsManager.calculateDirections();

    } else {

        //Load the Directions module and create a directions manager.

        Microsoft.Maps.loadModule('Microsoft.Maps.Directions', {

            callback: function () {

                //Create the directions manager

                directionsManager = new Microsoft.Maps.Directions.DirectionsManager(map);

 

                //Perfrom route logic

                GetRoute();

            }

        });

    }

}

The directions manager takes care of notifying the user if there is an issue calculating the route such as one of the route points having ambiguous results. This saves use a lot of time as we won’t have to worry about handling and developing this functionality. Additionally, this gives us the ability to easily drag the route if we want to customize it or avoid an area.

Clearing the Map

Whenever we process a new geocode or route request we will want to clear the map. We will also need to create a function named ClearMap as this is needed for the Clear Map button. When this function is called we will want to remove any entities from the map and reset the directions manager. This is rather simple to do, just add the following code to the BingMapsSearch.js file.

function ClearMap() {

    map.entities.clear();

 

    if (directionsManager) {

        directionsManager.resetDirections();

    }

}

Running the App

At this point we have created all the functionality needed for the UI we created originally. If you run the app now you can make geocode and routing requests. Here is a screen shot of the app with a route from New York to Toronto.

Win8_Geo_complete

- Ricky Brundritt, EMEA Bing Maps Technology Solution Professional


3D Elevation Models with Bing Maps WPF

$
0
0

With the release of the Bing Maps REST Elevations service I started looking into cool and interesting things that can be done with the service. While doing some searching around, I stumbled across an interesting blog post titled Examining 3D Terrain of Bing Maps Tiles with SQL Server 2008 and WPF by one of our Bing Maps MVP’s which inspired me to see if I could make something similar using this new Elevations service. So with that, I’ve put together this blog posts which demonstrates how to create a tool for generating a 3D model of elevation data and then overlay static imagery over the top. As a teaser, here is a screenshot of a 3D model of Niagara Falls created using this code.

3DNiagraFalls

Setting up the Visual Studio Project

To start, we will create a WPF Application project in Visual Studios called BingMaps3DModel_WPF. Once this is done we will want to add references to the following libraries:

  • System.Runtime.Serialization
  • Microsoft.Maps.MapControl.WPF

Adding support for the REST based Elevation service

Since we will be accessing the Bing Maps REST Elevation service from .NET code we will need to add in a library to parse the responses from the service. Rather than writing these from scratch I’ll be making use of some code I helped put together in a previous MSDN article on using Bing Maps REST Service with .NET Libraries. To include this library into the project we will right click on the project and select Add -> New Item. Add a class file called BingMapsRESTServices.cs. Remove any content that’s in this file and copy and paste in the complete code from the bottom of the previous blog post. At this point your project should look something like this:

clip_image004

Creating the User Interface

For this application we will want to have two tabs. The first tab will have a map that the user will be able to use to select what area the 3D model should be created for. Once the user has selected the area they are interested in they will be able to press a button to generate the 3D model. Once the model is created the user will be taken to the second tab which will allow the user to view and interact with the 3D model. To make things a bit cleaner we will create a separate user control for the 3D models tab. To do this, right click on the project and select Add -> New Item. Select “User Control (WPF)” and call it ViewerPanel3D.xaml.

With this, we can create the markup for the MainWindow.xaml file. The XAML should look like this.

<Windowx:Class="BingMaps3DModel_WPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:m="clr-namespace:Microsoft.Maps.MapControl.WPF;assembly=Microsoft.Maps.MapControl.WPF"
xmlns:local="clr-namespace:BingMaps3DModel_WPF"
Title="3D Map Generator"Height="700"Width="800">
<Grid>
<TabControlName="MyTabs">
<TabItemHeader="Map">
<Grid>
<m:MapName="MyMap"CredentialsProvider="YOUR_BING_MAPS_KEY"Mode="AerialWithLabels"/>
<ButtonContent="Generate 3D Map Model"Click="Generate3DModel_Click"
Width="150"Height="25"Margin="10"
HorizontalAlignment="Right"VerticalAlignment="Top"/>
</Grid>
</TabItem>
<TabItemHeader="3D Model">
<local:ViewerPanel3Dx:Name="viewerPanel"/>
</TabItem>
</TabControl>
</Grid>
</Window>

If you run the application now, it should result in an application that looks like this:

3DWPFMap

Note: you may get an error if you haven’t created an instance of the click event for the button. Simply right click on the event name and press “Navigate to Event Handler” to generate the click event code that is needed.

The ViewerPanel3D User Control

We can now turn our attention to the ViewerPanel3D user control that we created and add the needed markup to that. When a model is loaded into this control we will want to give the user several sliders which they can use to rotate and move the model around with. I came across a really nice example in this blog post that I thought I would use as a starting point. The nice thing about this example is that it binds the sliders to the rotation transform which means there is no code needed in the background for this functionality. In addition to the sliders for rotating the model we will add a set of sliders for translating (moving in different directions) the model. We will also add a mouse wheel event for zooming in and out of the model. Putting this all together the markup for the ViewerPanel3D.xaml file should look like this:

<UserControlx:Class="BingMaps3DModel_WPF.ViewerPanel3D"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300"d:DesignWidth="300">

<UserControl.Resources>
<StyleTargetType="{x:Type TextBlock}">
<SetterProperty="HorizontalAlignment"Value="Center"/>
<SetterProperty="VerticalAlignment"Value="Center"/>
</Style>
<Stylex:Key="slider">
<SetterProperty="Slider.Orientation"Value="Vertical"/>
<SetterProperty="Slider.Height"Value="130.0"/>
<SetterProperty="Slider.HorizontalAlignment"Value="Center"/>
<SetterProperty="Slider.VerticalAlignment"Value="Center"/>
</Style>
</UserControl.Resources>

<GridBackground="Gray"MouseWheel="OnViewportMouseWheel">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinitionWidth="100"/>
</Grid.ColumnDefinitions>
<Viewport3DName="viewport"Grid.Row="0"Grid.Column="0">
<Viewport3D.Camera>
<PerspectiveCamerax:Name="camera"FarPlaneDistance="50"
NearPlaneDistance="0"LookDirection="0,0,-10"UpDirection="0,1,0"
Position="0,0,5"FieldOfView="45">
<PerspectiveCamera.Transform>
<Transform3DGroup>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D
Axis="1.0, 0.0, 0.0"
Angle="{Binding ElementName=sliderX, Path=Value}"/>
</RotateTransform3D.Rotation>
</RotateTransform3D>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D
Axis="0.0, 1.0, 0.0"
Angle="{Binding ElementName=sliderY, Path=Value}"/>
</RotateTransform3D.Rotation>
</RotateTransform3D>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D
Axis="0.0, 0.0, 1.0"
Angle="{Binding ElementName=sliderZ, Path=Value}"/>
</RotateTransform3D.Rotation>
</RotateTransform3D>
<TranslateTransform3D
OffsetX="{Binding ElementName=transSliderX, Path=Value}"
OffsetY="{Binding ElementName=transSliderY, Path=Value}"
OffsetZ="{Binding ElementName=transSliderZ, Path=Value}"/>
<ScaleTransform3D
ScaleX="{Binding ElementName=sliderZoom, Path=Value}"
ScaleY="{Binding ElementName=sliderZoom, Path=Value}"
ScaleZ="{Binding ElementName=sliderZoom, Path=Value}"/>
</Transform3DGroup>
</PerspectiveCamera.Transform>
</PerspectiveCamera>
</Viewport3D.Camera>
<ModelVisual3D>

</ModelVisual3D>
<ModelVisual3Dx:Name="model">
<ModelVisual3D.Content>
<Model3DGroupx:Name="group">
<AmbientLightColor="DarkGray"/>
<DirectionalLightColor="DarkGray"Direction="10,10,5"/>
</Model3DGroup>
</ModelVisual3D.Content>
</ModelVisual3D>
</Viewport3D>

<StackPanelGrid.Column="1"Width="100"Background="LightGray">
<GroupBoxHeader="Rotation"Margin="4.0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlockText="X"Grid.Column="0"Grid.Row="0"/>
<TextBlockText="Y"Grid.Column="1"Grid.Row="0"/>
<TextBlockText="Z"Grid.Column="2"Grid.Row="0"/>
<Sliderx:Name="sliderX"Grid.Column="0"Grid.Row="1"Minimum="0.0"Maximum="360.0"Value="230"Style="{StaticResource slider}">
<Slider.ToolTip>
<TextBlockText="Rotate around X-Axis"/>
</Slider.ToolTip>
</Slider>
<Sliderx:Name="sliderY"Grid.Column="1"Grid.Row="1"Minimum="-180.0"Maximum="180.0"Style="{StaticResource slider}">
<Slider.ToolTip>
<TextBlockText="Rotate around Y-Axis"/>
</Slider.ToolTip>
</Slider>
<Sliderx:Name="sliderZ"Grid.Column="2"Grid.Row="1"Minimum="-180.0"Maximum="180.0"Style="{StaticResource slider}">
<Slider.ToolTip>
<TextBlockText="Rotate around Z-Axis"/>
</Slider.ToolTip>
</Slider>
</Grid>
</GroupBox>

<GroupBoxHeader="Translate"Margin="4.0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlockText="X"Grid.Column="0"Grid.Row="0"/>
<TextBlockText="Y"Grid.Column="1"Grid.Row="0"/>
<TextBlockText="Z"Grid.Column="2"Grid.Row="0"/>
<Sliderx:Name="transSliderX"Grid.Column="0"Grid.Row="1"Minimum="-10"Maximum="10"Style="{StaticResource slider}">
<Slider.ToolTip>
<TextBlockText="Translate along the X-Axis"/>
</Slider.ToolTip>
</Slider>
<Sliderx:Name="transSliderY"Grid.Column="1"Grid.Row="1"Minimum="-10"Maximum="10"Style="{StaticResource slider}">
<Slider.ToolTip>
<TextBlockText="Translate along the Y-Axis"/>
</Slider.ToolTip>
</Slider>
<Sliderx:Name="transSliderZ"Grid.Column="2"Grid.Row="1"Minimum="-10"Maximum="10"Style="{StaticResource slider}">
<Slider.ToolTip>
<TextBlockText="Translate along the Z-Axis"/>
</Slider.ToolTip>
</Slider>
</Grid>
</GroupBox>

<GroupBoxHeader="Zoom"Margin="4.0">
<Sliderx:Name="sliderZoom"IsDirectionReversed="True"Minimum="0.01"Maximum="1"Value="0.8"Style="{StaticResource slider}"/>
</GroupBox>
</StackPanel>
</Grid>
</UserControl>

We can now add the method that will handle the mouse wheel event to zoom the model accordingly. To do this, we can right click on the mouse wheel event name and press “Navigate to Event Handler.” This will generate the mouse wheel event code that is needed. We can then add in a bit of logic for setting the value of the zoom slider which will in turn zoom the model as there is a binding connected to the slider. The code for the file ViewerPanel3D.xaml.cs should look like this:
using System.Windows.Controls;
using System.Windows.Input;

namespace BingMaps3DModel_WPF
{
/// <summary>
/// Interaction logic for ViewerPanel3D.xaml
/// </summary>
publicpartialclass ViewerPanel3D : UserControl
{
public ViewerPanel3D()
{
InitializeComponent();
}

privatevoid OnViewportMouseWheel(object sender, MouseWheelEventArgs e)
{
sliderZoom.Value -= (double)e.Delta / 1000;
}
}
}

If we run the application and go to the 3D model tab we should see something that looks like this.

3DWPFFrame

Generating the Model

Having a nice panel for viewing the model is a good start but doesn’t really do us much good without having a 3D model to view. To create the 3D model we will need to do the following:

(1) Get a static map image for the based on the center point and zoom level of the map. To keep things easy, we will make keep the width and height of the image equal to 800 pixels.

(2) Based on the center point, zoom level and imagery size we will then need to calculate the bounding box of the image as we will need it to request the elevation data.

(3) Make a request for the elevation data for the bounding box we created. Again, to keep things simple we will specify that the data points be evenly distributed over 30 rows and columns. This will result in 900 elevation data points being returned which is under the 1000 elevation data point limit.

(4) We need to loop through all the elevation data, calculate the relative coordinate, and then convert this coordinate to a pixel coordinate. We will also need to convert the elevation into a pixel length and then scale these values down relative to the size of the map.

(5) Now we can create a 3D Mesh Geometry out of the data. To do this, we will need to specify all the data points as 3 dimensional coordinates, and then specify the texture coordinates used to map the static map image to the mesh. We will also need to specify the point indices used to create the triangles needed for the mesh.

(6) As a final step, we will create a Geometry Model out of the 3D Mesh and set the static image as the material to be overlaid on top of it. This model can then be passed into our ViewerPanel3D user control.

Most of the math used to work with the pixel coordinates are based off of these two articles: Bing Maps Tile System, and VE Imagery Service and Custom Icons. Putting all the above tasks together and adding them to the MainWindow.xaml.cs file you should end up with a code for the MainWindow.xaml file that looks like this:

using System;
using System.IO;
using System.Net;
using System.Runtime.Serialization.Json;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using BingMapsRESTService.Common.JSON;

namespace BingMaps3DModel_WPF
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
publicpartialclass MainWindow : Window
{
#region Private Properties

privatestring sessionBingMapsKey;

private GeometryModel3D mGeometry;

privateconstdouble mapSize = 800;

privatedouble topLeftX;
privatedouble topLeftY;
privatedouble zoom;

#endregion

#region Constructor

public MainWindow()
{
InitializeComponent();

MyMap.CredentialsProvider.GetCredentials((x) =>
{
sessionBingMapsKey = x.ApplicationId;
});
}

#endregion

#region Button Event Handler

privatevoid Generate3DModel_Click(object sender, RoutedEventArgs e)
{
double cLat = MyMap.Center.Latitude;
double cLon = MyMap.Center.Longitude;

//Round off the zoom level to the nearest integer as an integer zoom level is needed for the REST Imagery service
zoom = Math.Round(MyMap.ZoomLevel);

//Only generate models when the user is zoomed in at a decent zoom level, otherwise the model will just be a flat sheet.
if (zoom < 8)
{
MessageBox.Show("This zoom level is not supported. Please zoom in closer (>8).");
return;
}

//Clear current model from the viewer panel
if (mGeometry != null)
{
viewerPanel.group.Children.Remove(mGeometry);
}

//Open up the 3D model tab
MyTabs.SelectedIndex = 1;

//Calculate bounding box of image for specified zoom level and center point for map dimensions

//Retrieve image of map dimensions for the specified zoom level and center point.
string imgUrl = string.Format("http://dev.virtualearth.net/REST/v1/Imagery/Map/Aerial/{0},{1}/{2}?mapSize={3},{4}&key={5}",
cLat,
cLon,
zoom,
mapSize,
mapSize,
sessionBingMapsKey);

ImageBrush imgBrush = new ImageBrush();
imgBrush.ImageSource = new BitmapImage(new Uri(imgUrl));

DiffuseMaterial material = new DiffuseMaterial(imgBrush);

//calcuate pixel coordinates of center point of map
double sinLatitudeCenter = Math.Sin(cLat * Math.PI / 180);
double pixelXCenter = ((cLon + 180) / 360) * 256 * Math.Pow(2, zoom);
double pixelYCenter = (0.5 - Math.Log((1 + sinLatitudeCenter) / (1 - sinLatitudeCenter)) / (4 * Math.PI)) * 256 * Math.Pow(2, zoom);

//calculate top left corner pixel coordiates of map image
topLeftX = pixelXCenter - (mapSize / 2);
topLeftY = pixelYCenter - (mapSize / 2);

//Calculate bounding coordinates of map view
double brLongitude, brLatitude, tlLongitude, tlLatitude;

PixelToLatLong(new System.Windows.Point(900, 800), out brLatitude, out brLongitude);
PixelToLatLong(new System.Windows.Point(0, 0), out tlLatitude, out tlLongitude);

//Retrieve elevation data for the specified bounding box
//Rows * Cols <= 1000 -> Let R = C = 30

string elevationUrl = string.Format("http://dev.virtualearth.net/REST/v1/Elevation/Bounds?bounds={0},{1},{2},{3}&rows=30&cols=30&key={4}",
brLatitude,
tlLongitude,
tlLatitude,
brLongitude,
sessionBingMapsKey);

WebClient wc = new WebClient();
wc.OpenReadCompleted += (s, a) =>
{
using (Stream stream = a.Result)
{
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(Response));
Response response = ser.ReadObject(stream) as Response;

if (response != null&&
response.ResourceSets != null&&
response.ResourceSets.Length > 0 &&
response.ResourceSets[0] != null&&
response.ResourceSets[0].Resources != null&&
response.ResourceSets[0].Resources.Length > 0)
{
ElevationData elevationData = response.ResourceSets[0].Resources[0] as ElevationData;

//Map elevation data to 3D Mesh
MeshGeometry3D mesh = new MeshGeometry3D();

double dLat = Math.Abs(tlLatitude - brLatitude) / 30;
double dLon = Math.Abs(tlLongitude - brLongitude) / 30;

double x, y, m2p;

for (int r = 0; r < 30; r++)
{
y = tlLatitude + (dLat * r);

for (int c = 0; c < 30; c++)
{
int idx = r * 30 + c;

x = tlLongitude + (dLon * c);

double z = -elevationData.Elevations[idx];

m2p = 156543.04 * Math.Cos(y * Math.PI / 180) / Math.Pow(2, zoom);

System.Windows.Point p = LatLongToPixel(y, x);
p.X = (p.X - 400) / mapSize;
p.Y = (p.Y + 400) / mapSize;

mesh.Positions.Add(new Point3D(p.X, p.Y, z / mapSize / m2p));

mesh.TextureCoordinates.Add(p);

//Create triangles for model
if (r < 29 && c < 29)
{
mesh.TriangleIndices.Add(idx);
mesh.TriangleIndices.Add(idx + 1);
mesh.TriangleIndices.Add(idx + 30);

mesh.TriangleIndices.Add(idx + 1);
mesh.TriangleIndices.Add(idx + 31);
mesh.TriangleIndices.Add(idx + 30);
}
}
}

//Add 3D mesh to view panel
mGeometry = new GeometryModel3D(mesh, material);
mGeometry.Transform = new Transform3DGroup();
viewerPanel.group.Children.Add(mGeometry);
}
}
};

wc.OpenReadAsync(new Uri(elevationUrl));
}

#endregion

#region Helper Methods

private System.Windows.Point LatLongToPixel(double latitude, double longitude)
{
//Formulas based on following article:
//http://msdn.microsoft.com/en-us/library/bb259689.aspx

//calculate pixel coordinate of location
double sinLatitude = Math.Sin(latitude * Math.PI / 180);
double pixelX = ((longitude + 180) / 360) * 256 * Math.Pow(2, zoom);
double pixelY = (0.5 - Math.Log((1 + sinLatitude) / (1 - sinLatitude)) / (4 * Math.PI)) * 256 * Math.Pow(2, zoom);

//calculate relative pixel cooridnates of location
double x = pixelX - topLeftX;
double y = pixelY - topLeftY;

returnnew System.Windows.Point((int)Math.Floor(x), (int)Math.Floor(y));
}

privatevoid PixelToLatLong(System.Windows.Point pixel, outdouble lat, outdouble lon)
{
double x = topLeftX + pixel.X;
double y = topLeftY + pixel.Y;

lon = (x * 360) / (256 * Math.Pow(2, zoom)) - 180;

double efactor = Math.Exp((0.5 - y / 256 / Math.Pow(2, zoom)) * 4 * Math.PI);

lat = Math.Asin((efactor - 1) / (efactor + 1)) * 180 / Math.PI;
}

#endregion
}
}

You should now be able to run the application. Give it a go and zoom into an area where there is a good chance of being varying elevations. I find using a zoom level between 15 and 19 works best. If you don’t see a model generated, try zooming out or translating along the Z axis.

Here is a screenshot of my finished application with the generated 3D model of a section of the Grand Canyon. Nice, right?

GrandCanyon

- Ricky Brundritt, EMEA Bing Maps Technology Solution Professional

Bing Maps REST Service Tips & Tricks

$
0
0

I’ve been a big fan of the Bing Maps REST services since they were first released several years ago and highly recommend trying them out if you haven’t already. The REST services are much newer than the SOAP services, have more features, are faster and can be consumed by non .NET languages with greater ease. If you have the Bing Maps REST services return JSON the response size of a request is significantly smaller than the response size from the same request using the Bing Maps SOAP services. This blog post will go through and highlight some tips for getting the most out of the Bing Maps REST services.

Using the REST services in managed code

I want to take the time to provide a useful resource for those who have had issues consuming the Bing Maps REST Services APIs using .NET. The documentation on how to consume the JSON responses from the Bing Maps REST services can be found on MSDN here.

If you are using Java then take a look at how the REST services are handled in the Bing Maps Android SDK.

Geocoding

·         Unless you are geocoding English addresses in the US you should specify a culture parameter to help ensure you get the most relevant results. By default, the culture for all requests is set to en-US. If geocoding addresses in the UK, you will find that in some cases using the culture parameter en-GB will return better results. This becomes even more important when geocoding addresses that are not in English. To specify a culture in a REST request simply use “&c=cultureCode” – you can take a look at the complete list of Supported Cultures.

·        Encode address and query parameters. This is especially important when working with non-English languages as special characters often will not work correctly. This is also important if you want to use an ampersand (&) in your query. By encoding an ampersand it will appear in the URL as “%26”. Here are the methods that can be used in different programming languages to encode URL parameters.

Language

Method

Example

JavaScript

encodeURI

encodeURI(query)

C#/VB

Uri

Uri.EscapeDataString (query)

 

·         When geocoding free form queries use the unstructured URL format rather than the structured format. The unstructured URL format tends to be much more successful for these types of queries. Note: the structured format actually overlaps with the reverse geocoding URL request format and can return odd results if your query is just made up of numbers.

Unstructured URL

http://dev.virtualearth.net/REST/v1/Locations?query=locationQuery&key=BingMapsKey

 

X Structured URL

http://dev.virtualearth.net/REST/v1/Locations/locationQuery?key=BingMapsKey

·         The Bing Maps geocoder will attempt to find the closest match as possible to your query. In some cases, it will not be able to find an exact match. This is where the match code parameter of the returned results becomes useful. The match code parameter is an array of values and can have any combination of the following three values; Good, Ambiguous, and UpHierarchy. If you are only interested in exact matches then keep a look out for UpHierachy as this indicates that your exact query was not found but an upper level address value was found. For example, you attempt to geocode a postal code but instead the associated country is returned as the postal code was not found.

·        If using the REST services from server side code you may find that the results on in your application may differ from the results found when using the services locally. The reason for this is that the REST services take your IP address into consideration when making a request. To help reduce this issue you can set theuserIpparameter of the request to 127.0.0.1. This will trick the service into thinking you are making the call from a local application and cause it to ignore your IP address.

·         If you have a lot of data you want to geocode at once consider using the Bing Spatial Data Servicesand the batch geocoding functionality. This service allows you to geocode up to 200,000 addresses in a single request.

Reverse Geocoding

·         Limit your coordinates to 6 decimal places. At 6 decimal places you have an accuracy of approximately 10cm on the ground. Any more than 6 decimal places just makes for a longer URL and can confuse the reverse geocoder into thinking this is a free form query search.

·         Ensure that your numbers are not being turned into scientific notation format when converting them to string. This occurs quite often when working with small numbers. For example, it is not uncommon for some languages to convert 0.00005 to 5E-5.  Scientific notation is not supported by the service and will be interpreted as a free form query.

·         Ensure that when converting your coordinates to string that you use an invariant culture. Coordinates that use commas for decimal places will not work correctly.

·         Like the batch geocoder you can also do batch reverse geocoding using the Bing Spatial Data Services if you need to reverse geocode a large number of coordinates in one go.

Routing

·         Many of the tips in the geocoding service apply for the routing service, like being sure to encode your address locations. However, don’t encode coordinate based locations.

·         The default distance units are in Kilometers. Use thedistanceUnitparameter to change this to miles if that is your preferred unit of measurement.

·        You can now have up to 3 possible routes returned by the routing engine for transit and driving directions in certain countries. This may be desirable in some applications but it is best to make this optional to your users. Although the calculation on Bing Maps end is fast, the response from Bing Maps is much bigger when returning 3 routes, rather than one. This could become an issue for users with slow internet connections (i.e. mobile users).

·         If using Bing Maps in areas where geocoding coverage is limited consider allowing the user to select their start and end point on the map via a click event or by dragging a pushpin. This will allow you to pass in coordinates for your end points rather than an address. The routing engine is capable of calculating routes anywhere there is road data, even if there is no geocoding coverage.

·         If you want to retrieve the coordinates that make up the route line along the roads use the routePathOutputparameter.

Imagery Service

·         When requesting a static map image from Bing Maps the imagery service the service will automatically choose the best image format to return the image in for best resolution. Note: this may not be the preferred image type in some cases. For example, the service may return Ordnance Survey maps in PNG format; you may find you prefer these maps returned as JPG of GIF format. You can specify the image type using the formatparameter.

·         The Imagery service can return two different types of metadata. The first typeof metadata gives you information about the imagery in Bing Maps for a specific location, zoom level and map type. This is useful if you want to find out the age of the imagery or want to know is a specific type of imagery is available for a certain location. The second typeof metadata is for a static image generated from the imagery service. This second metadata may include information such as pixel coordinates of pushpins on your image. This is useful if you want to be able to tie events to the generated image or create an HTML image mapout of it.

Reducing Usage Transactions

Are you are using the Bing Maps Rest services in conjunction with one of the Bing Maps map controls? If so, you can significantly reduce the number of transactions your application incurs against your Bing Maps account if you request the Bing Maps key from the map control rather than using your normal Bing Maps key. This is quite often an overlooked feature. When getting the credentials from the map you do not get back your original Bing Maps key. Instead, you get a special session key which you can use as a Bing Maps key to make requests to the Bing Maps services. By doing this all transactions incurred by this session key will be non-billable. Here are some examples of how to properly use a session key.

 

Bing Maps V7 AJAX API

function ClickGeocode()

 {

                map.getCredentials(MakeBingMapsRESTRequest);

 }

 

 function MakeBingMapsRESTRequest(sessionKey)

 {

                //Generate a request URL for the Bing Maps REST services.

                //Use the session key in the request as the Bing Maps key

 }


Full working sample using JavaScript can be found here.

Bing Maps Silverlight, WPF, WP7, and WinForm API’s

Map.CredentialsProvider.GetCredentials((c) =>

{

                string sessionKey = c.ApplicationId;

               

                //Generate a request URL for the Bing Maps REST services.

                //Use the session key in the request as the Bing Maps key

});


Full working samples using .NET can be found

here.

- Ricky Brundritt, EMEA Bing Maps Technology Solution Professional

Attend the Bing Maps and SharePoint Solutions Webcast

$
0
0
 

You are invited to attend this webcast to learn how solutions that integrate Bing Maps and SharePoint help organizations gain greater insight from their business data to make smarter business decisions. We will also explore new native geospatial support for SharePoint 2013 and Office 365 (SharePoint in the cloud) which makes it much easier to geocode address data and display with Bing Maps. We’ll explore solution scenarios and tools that illustrate the value of geospatial support in SharePoint when combined with Bing Maps, and look at examples of customers extending their investment in the SharePoint platform by leveraging location and Bing Maps.


You’ll learn what’s included in the Bing Maps platform, and how to take advantage of the data services & imagery and incorporate them into line of business solutions within your organization. We will explore the development options with the platform, and how to get started with Bing Maps and SharePoint 2013/Office 365 development.

Webcast Details

Bing Maps and SharePoint Solutions for the Enterprise (EPG382CAL)

Date/Time:

Tue Feb 26, 2013, 9:00 AM, USA Pacific

Duration:

1 Hour

Presenters:

Mark Merchant, Bing Maps Technical Solution Professional

Ricky Brundritt, Bing Maps Technical Solution Professional

Scott Caulk, VP of Product Management for IDV Solutions

Steve Milroy, CEO OnTerra Systems

3D Elevation Models using HTML5 and Bing Maps

$
0
0

Recently we published a blog post on 3D Elevation Models with Bing Maps WPF. In this blog post we will show you how to create a tool for generating a 3D model of elevation data from the Bing Maps REST Elevation Service using HTML5 and Bing Maps.

There are a couple of different ways to create 3D models in HTML5. The method we are going to use to is to project 3D data onto a 2D canvas. There are several JavaScript libraries available online that can do this. You can also find an in-depth article on how to do the math required for this projection here if you want to write your own library. For this blog post we will use the second method and make use of a JavaScript library called K3D.

In this blog post we will create a web page that uses Bing Maps to select an area on the map. We will then use the elevation service to get the elevation data for the selected area on the map. We will then generate a 3D model using the HTML5 canvas and the K3D library. We will also make use of sliders from the jQuery UI library and add functionality for rotating and translating the model.

Note that by using a helper library called ExplorerCanvas you can add HTML5 canvas support to Internet Explorer 7 and 8 which represent 13.8% of the current web browser market share.

Setting up the project

To help make things clean we will put all of our JavaScript into a file called 3DElevationModel.js and put all JavaScript files into a folder called scripts. We will also need to include JavaScript libraries for jQuery, jQuery UI, K3D, and ExplorerCanvas. We will also put all our HTML into a file called index.html. Your project structure should look like this:

 

clip_image001

The HTML Layout

For this application we will want to have a map that the user will be able to use to select what area the 3D model should be created for. Once the user has selected the area they are interested in they will be able to press a button to generate the 3D model. Once the model is created the user will be able to view and interact with the 3D model.

We will need to ad references to 7 JavaScript files. The first reference will be to the Bing Maps V7 AJAX control. We will need references to the jQuery and jQuery UI controls to create the sliders for manipulating the 3D model. A reference to the CSS stylesheet for the jQuery UI library will also be needed. The K3D and its associated MathLib JavaScript libraries will need to be referenced to help generate the 3D model. We will also load in the Explorer Canvas JavaScript library if the browser is Internet Explorer with a version less than 9. Finally, we will need a reference to the JavaScript file where we will be putting all our code: 3DElevationModel.js.

For the body of the HTML document, we will need a div to host the map control, a button to generate the 3D model, a canvas element for rendering the model and 6 div’s for generating sliders. With this, we can create the markup for the index.html file. The HTML should look like this:

<!DOCTYPEhtmlPUBLIC"-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title></title>
<metahttp-equiv='Content-Type'content='text/html; charset=utf-8'/>
<scripttype='text/javascript'src='http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0'></script>

    <!-- jQuery UI libraries for creating Slider controls -->

    <linktype="text/css"href="styles/ui-lightness/jquery-ui-1.8.21.custom.css"rel="stylesheet"/>

       <scripttype="text/javascript"src="scripts/jquery-1.7.2.min.js"></script>

       <scripttype="text/javascript"src="scripts/jquery-ui-1.8.21.custom.min.js"></script>

    <!--[if lt IE 9]><script type="text/javascript" src="scripts/excanvas.min.js"></script><![endif]-->

    <scripttype="text/javascript"src="scripts/mathlib-min.js"></script>

    <scripttype="text/javascript"src="scripts/k3d-min.js"></script>

    <!-- Main JavaScript file where we put all our logic for this application -->

    <scripttype="text/javascript"src="scripts/3DElevationModel.js"></script>


<styletype="text/css">
.slider
{
float:left;
width:30px;
}
</style>
</head>
<bodyonload='GetMap();'>
<divstyle='float:left;width:600px'>
<divid='myMap'style='position:relative;width:600px;height:400px;'></div>
<inputtype='button'onclick='GenerateModel();'value='Generate Model'/>
</div>

<divstyle='float:left;width:600px;margin-left:10px;'>
<canvasid="canvas"width="600"height="400"style="background-color:#fff;"></canvas>

<div>
<fieldsetstyle='width:90px;float:left;'>
<legend>Rotation</legend>
<divclass="slider">
X
<divid="rotateSliderX"></div>
</div>
<divclass="slider">
Y
<divid="rotateSliderY"></div>
</div>
<divclass="slider">
Z
<divid="rotateSliderZ"></div>
</div>
</fieldset>

<fieldsetstyle='width:90px;float:left;'>
<legend>Translate</legend>
<divclass="slider">
X
<divid="translateX"></div>
</div>
<divclass="slider">
Y
<divid="translateY"></div>
</div>
<divclass="slider">
Z
<divid="translateZ"></div>
</div>
</fieldset>
</div>
</div>
</body>
</html>
 

Generating the 3D Elevation Model

All the JavaScript for this project will be added to a JavaScript file called 3DElevationModel.js. At the top of this file we will have a number of global variables which we will use throughout the application. Here are the global variables we will have at the top of the file:

 

var map,            //Reference to Map control
canvas, //Reference to the HTML5 canvas used to render the model
sessionKey, //Bing Maps session key used with REST services
mapBounds, //Area of where elevation data was requested
zoom, //Zoom level used to request elevation data
model, //Reference to the generate model
k3d, //Reference to K3D control
numRows = 30, //Number of rows to use for the elevation samples
numCols = 30, //Number of columns to use for the elevation samples
textureColor = [0, 255, 0]; //Color to render the model in
 
When the application starts, lets load the map. Since we will be using the REST elevation service, we will also generate a session key from the map which we can use with the REST services to make those transactions non-billable. And while we are at it, we will also store a reference to the canvas and put all this logic into the GetMap function (which is called when the page is loaded). Also, don’t forget to add in your Bing Maps key into this code.

 

function GetMap() {
// Initialize the map
map = new Microsoft.Maps.Map(document.getElementById('myMap'),
{
credentials: 'YOUR_BING_MAPS_KEY',
center: new Microsoft.Maps.Location(43.08, -79.075),
zoom: 16,
mapTypeId: Microsoft.Maps.MapTypeId.aerial
});

//Generate a Bing Maps Session key to make our call to the REST service non-billable.
map.getCredentials(function (sKey) {
sessionKey = sKey;
});

//Store a reference to the canvas
canvas = document.getElementById('canvas');
}

Now we can start adding the logic for requesting the elevation data. When the “Generate Model” button is pressed the GenerateModel function will be fired. In this method, we will want to clear the canvas of any previous model, store a reference of the current zoom level and map bounds, and create the request to the Bing Maps Elevation service. To make things a bit cleaner, we will round off the coordinates to six decimal places as additional decimal places make no difference. This function will look like this:

function GenerateModel() {
//Clear the Canvas
var context = canvas.getContext("2d");
context.clearRect(0, 0, canvas.width, canvas.height);

//Get the current zoom level and store it
zoom = map.getZoom();

//Only generates models when the user is zoomed in at a decent zoom level, otherwise the model will just be a flat sheet.
if (zoom < 8) {
alert("This zoom level is not supported. Please zoom in closer (>8).");
return;
}

mapBounds = map.getBounds();

//Get the elevation data for a bounding box area
var elevationURL = "http://dev.virtualearth.net/REST/v1/Elevation/Bounds?bounds=" +
SigDigit(mapBounds.getSouth(), 6) + "," + SigDigit(mapBounds.getWest(), 6) + "," + SigDigit(mapBounds.getNorth(), 6) + "," + SigDigit(mapBounds.getEast(), 6) + "&rows=" + numRows + "&cols=" + numCols + "&key=" + sessionKey;

CallRestService(elevationURL + "&jsonp=Render3DModel");
}

function CallRestService(request) {
var script = document.createElement("script");
script.setAttribute("type", "text/javascript");
script.setAttribute("src", request);
document.body.appendChild(script);
}

//Simple method that rounds numbers to a certain number of significant digits
function SigDigit(number, digits) {
var delta = Math.pow(10, digits);
return Math.round(number * delta) / delta;
}

When the elevation service responds to our request, it will fire our callback function called Render3DModel. This function will need all the logic to generate the 3D model and render it on the canvas. To create the 3D model, we will need to do the following:

(1) Loop through all the elevation data, calculate the relative coordinate, and then convert this coordinate to a pixel coordinate. We will also need to convert the elevation into a pixel length.

(2) Next, we can create a 3D mesh out of the data. To do this, we will need to specify all the data points as 3 dimensional coordinates, and then specify the texture coordinate vertices used to render the mesh. We will also need to specify the point indices used to create the triangles needed for the mesh.

(3) Finally, we can pass all this data to the K3D library to generate the 3D model.

The code for this function looks like this:

function Render3DModel(result) {
if (result != null&&
result.resourceSets != null&&
result.resourceSets.length > 0 &&
result.resourceSets[0] != null&&
result.resourceSets[0].resources != null&&
result.resourceSets[0].resources.length > 0) {

var elevations = result.resourceSets[0].resources[0].elevations;

//Get bounding coordinates of map
var tlLatitude = mapBounds.getNorth();
var brLatitude = mapBounds.getSouth();
var brLongitude = mapBounds.getEast();
var tlLongitude = mapBounds.getWest();

//Calculate the degree spacing between elevation samples
var dLat = Math.abs(tlLatitude - brLatitude) / numRows;
var dLon = Math.abs(tlLongitude - brLongitude) / numCols;

var x, y, z, p, m2p, idx, idx2, idx3, idx4;
var meshPoints = [], edgeData = [], vertices = [];

for (var r = 0; r < numRows; r++) {
y = tlLatitude - (dLat * r);

//Calculate meters per pixel for given latitude so we can scale the elevation to pixels
//This is based on: http://msdn.microsoft.com/en-us/library/aa940990.aspx
m2p = 156543.04 * Math.cos(y * Math.PI / 180) / Math.pow(2, zoom);

for (var c = 0; c < numCols; c++) {
idx = r * numCols + c;

x = tlLongitude + (dLon * c);

z = -elevations[idx] / m2p;
p = map.tryLocationToPixel(new Microsoft.Maps.Location(y, x));

meshPoints.push({ x: p.x, y: p.y, z: z });

//Create triangles for vreating the mesh model
if (r < numRows - 1 && c < numCols - 1) {
idx2 = idx + 1;
idx3 = idx + numCols;
idx4 = idx3 + 1;

edgeData.push({ a: idx, b: idx2 });
edgeData.push({ a: idx2, b: idx3 });
edgeData.push({ a: idx3, b: idx });
vertices.push({ vertices: [idx, idx3, idx2], color: textureColor });

edgeData.push({ a: idx2, b: idx4 });
edgeData.push({ a: idx4, b: idx3 });
edgeData.push({ a: idx3, b: idx2 });
vertices.push({ vertices: [idx2, idx3, idx4], color: textureColor });
}
}
}

//Bind a K3D Controller object to the canvas
k3d = new K3D.Controller(canvas, true);

// create a K3D object for rendering
model = new K3D.K3DObject();
with (model) {
drawmode = "solid";
shademode = "lightsource";
otheta = -45; // Intial rotation around X axis
ophi = 0; // Intial rotation around Y Axis
ogamma = 0; // Intial roation around Z axis
fillstroke = true; // Fill the model to help prevent seems along edges due to anti-aliasing
init(meshPoints, edgeData, vertices); //Initialize the model
}

//Add the object to the controller
k3d.addK3DObject(model);

//Render the model
k3d.tick();

//Reset Sliders
$("#rotateSliderX").slider('value', -45);
$("#rotateSliderY").slider('value', 0);
$("#rotateSliderZ").slider('value', 0);

$("#translateX").slider('value', 0);
$("#translateY").slider('value', 0);
$("#translateZ").slider('value', 0);
}
}

The K3D library, when iterating through data in an array, uses a method called forEach. This method is only available in newer browsers. Trying to use this library in Internet Explorer 7 or 8 will result in an error. To get around this error we can easily detect if this method exists for arrays, and if it doesn’t, then create the needed method. The following code can be used to do this:

if (!Array.prototype.forEach) {
Array.prototype.forEach = function (fun) {
var len = this.length;
if (typeof fun != "function")
thrownew TypeError();

var param = arguments[1];
for (var i = 0; i < len; i++) {
if (i inthis) {
fun.call(param, this[i], i, this);
}
}
};
}

Finally, we will need to add the code to create and initialize the sliders for manipulating the 3D model. The code looks like this:

$(function () {
var createSlider = function (id, min, max, propertyName) {
$(id).slider({
orientation: "vertical",
range: "min",
min: min,
max: max,
slide: function (event, ui) {
if (model) {
model[propertyName] = ui.value;
k3d.tick();
}
}
});
}

//Create sliders for rotating the model
createSlider("#rotateSliderX", -180, 180, 'otheta');
createSlider("#rotateSliderY", -180, 180, 'ophi');
createSlider("#rotateSliderZ", -180, 180, 'ogamma');

//Create Sliders for translating the model
createSlider("#translateX", -400, 400, 'offx');
createSlider("#translateY", -400, 400, 'offy');
createSlider("#translateZ", -400, 400, 'offz');
});

At this point we have everything we need to generate the 3D elevation model. Open up the web page in a browser and zoom into an area likely to have varying elevations and press the “Generate Model” button. Below is a screenshot of a rendered 3D elevation model of Niagara Falls.

3DNiagraFalls_HTML5

3DFrame_HTML5

Full Source Code

index.html

<!DOCTYPEhtmlPUBLIC"-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title></title>
<metahttp-equiv='Content-Type'content='text/html; charset=utf-8'/>
<scripttype='text/javascript'src='http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0'></script>

    <!-- jQuery UI libraries for creating Slider controls -->

    <linktype="text/css"href="styles/ui-lightness/jquery-ui-1.8.21.custom.css"rel="stylesheet"/>

       <scripttype="text/javascript"src="scripts/jquery-1.7.2.min.js"></script>

       <scripttype="text/javascript"src="scripts/jquery-ui-1.8.21.custom.min.js"></script>

    <!--[if lt IE 9]><script type="text/javascript" src="scripts/excanvas.min.js"></script><![endif]-->

    <scripttype="text/javascript"src="scripts/mathlib-min.js"></script>

    <scripttype="text/javascript"src="scripts/k3d-min.js"></script>

    <!-- Main JavaScript file where we put all our logic for this application -->

    <scripttype="text/javascript"src="scripts/3DElevationModel.js"></script>

<styletype="text/css">
.slider
{
float:left;
width:30px;
}
</style>
</head>
<bodyonload='GetMap();'>
<divstyle='float:left;width:600px'>
<divid='myMap'style='position:relative;width:600px;height:400px;'></div>
<inputtype='button'onclick='GenerateModel();'value='Generate Model'/>
</div>

<divstyle='float:left;width:600px;margin-left:10px;'>
<canvasid="canvas"width="600"height="400"style="background-color:#fff;"></canvas>

<div>
<fieldsetstyle='width:90px;float:left;'>
<legend>Rotation</legend>
<divclass="slider">
X
<divid="rotateSliderX"></div>
</div>
<divclass="slider">
Y
<divid="rotateSliderY"></div>
</div>
<divclass="slider">
Z
<divid="rotateSliderZ"></div>
</div>
</fieldset>

<fieldsetstyle='width:90px;float:left;'>
<legend>Translate</legend>
<divclass="slider">
X
<divid="translateX"></div>
</div>
<divclass="slider">
Y
<divid="translateY"></div>
</div>
<divclass="slider">
Z
<divid="translateZ"></div>
</div>
</fieldset>
</div>
</div>
</body>
</html>

3DElevationModel.js

/*
* This code makes use of the K3D libaray: http://www.kevs3d.co.uk/dev
* Documentation for K3D http://en.wikibooks.org/wiki/K3D_JavaScript_Canvas_Library/Tutorial
*/

var map, //Reference to Map control
canvas, //Reference to the HTML5 canvas used to render the model
sessionKey, //Bing Maps session key used with REST services
mapBounds, //Area of where elevation data was requested
zoom, //Zoom level used to request elevation data
model, //Reference to the generate model
k3d, //Reference to K3D control
numRows = 30, //Number of rows to use for the elevation samples
numCols = 30, //Number of columns to use for the elevation samples
textureColor = [0, 255, 0]; //Color to render the model in

//Add support for the "forEach" method on arrays if the browser does not have support for this.
//This is needed by the k3d library. Notable browsers that need this added are IE7 & IE8.
if (!Array.prototype.forEach) {
Array.prototype.forEach = function (fun) {
var len = this.length;
if (typeof fun != "function")
thrownew TypeError();

var param = arguments[1];
for (var i = 0; i < len; i++) {
if (i inthis) {
fun.call(param, this[i], i, this);
}
}
};
}

//Method used to load the map
function GetMap() {
// Initialize the map
map = new Microsoft.Maps.Map(document.getElementById('myMap'),
{
credentials: 'YOUR_BING_MAPS_KEY'
center: new Microsoft.Maps.Location(43.08, -79.075),
zoom: 16,
mapTypeId: Microsoft.Maps.MapTypeId.aerial
});

//Generate a Bing Maps Session key to make our call to the REST service non-billable.
map.getCredentials(function (sKey) {
sessionKey = sKey;
});

//Store a reference to the canvas
canvas = document.getElementById('canvas');
}

//Method used to start the process of generating the 3D elevation model
function GenerateModel() {
//Clear the Canvas
var context = canvas.getContext("2d");
context.clearRect(0, 0, canvas.width, canvas.height);

//Get the current zoom level and store it
zoom = map.getZoom();

//Only generates models when the user is zoomed in at a decent zoom level, otherwise the model will just be a flat sheet.
if (zoom < 8) {
alert("This zoom level is not supported. Please zoom in closer (>8).");
return;
}

mapBounds = map.getBounds();

//Get the elevation data for a bounding box area
var elevationURL = "http://dev.virtualearth.net/REST/v1/Elevation/Bounds?bounds=" +
SigDigit(mapBounds.getSouth(), 6) + "," + SigDigit(mapBounds.getWest(), 6) + "," + SigDigit(mapBounds.getNorth(), 6) + "," + SigDigit(mapBounds.getEast(), 6) + "&rows=" + numRows + "&cols=" + numCols + "&key=" + sessionKey;

CallRestService(elevationURL + "&jsonp=Render3DModel");
}

//This method generates and renders the 3D model based on the returned elevation data.
function Render3DModel(result) {
if (result != null&&
result.resourceSets != null&&
result.resourceSets.length > 0 &&
result.resourceSets[0] != null&&
result.resourceSets[0].resources != null&&
result.resourceSets[0].resources.length > 0) {

var elevations = result.resourceSets[0].resources[0].elevations;

//Get bounding coordinates of map
var tlLatitude = mapBounds.getNorth();
var brLatitude = mapBounds.getSouth();
var brLongitude = mapBounds.getEast();
var tlLongitude = mapBounds.getWest();

//Calculate the degree spacing between elevation samples
var dLat = Math.abs(tlLatitude - brLatitude) / numRows;
var dLon = Math.abs(tlLongitude - brLongitude) / numCols;

var x, y, z, p, m2p, idx, idx2, idx3, idx4;
var meshPoints = [], edgeData = [], vertices = [];

for (var r = 0; r < numRows; r++) {
y = tlLatitude - (dLat * r);

//Calculate meters per pixel for given latitude so we can scale the elevation to pixels
//This is based on: http://msdn.microsoft.com/en-us/library/aa940990.aspx
m2p = 156543.04 * Math.cos(y * Math.PI / 180) / Math.pow(2, zoom);

for (var c = 0; c < numCols; c++) {
idx = r * numCols + c;

x = tlLongitude + (dLon * c);

z = -elevations[idx] / m2p;
p = map.tryLocationToPixel(new Microsoft.Maps.Location(y, x));

meshPoints.push({ x: p.x, y: p.y, z: z });

//Create triangles for vreating the mesh model
if (r < numRows - 1 && c < numCols - 1) {
idx2 = idx + 1;
idx3 = idx + numCols;
idx4 = idx3 + 1;

edgeData.push({ a: idx, b: idx2 });
edgeData.push({ a: idx2, b: idx3 });
edgeData.push({ a: idx3, b: idx });
vertices.push({ vertices: [idx, idx3, idx2], color: textureColor });

edgeData.push({ a: idx2, b: idx4 });
edgeData.push({ a: idx4, b: idx3 });
edgeData.push({ a: idx3, b: idx2 });
vertices.push({ vertices: [idx2, idx3, idx4], color: textureColor });
}
}
}

//Bind a K3D Controller object to the canvas
k3d = new K3D.Controller(canvas, true);

// create a K3D object for rendering
model = new K3D.K3DObject();
with (model) {
drawmode = "solid";
shademode = "lightsource";
otheta = -45; // Intial rotation around X axis
ophi = 0; // Intial rotation around Y Axis
ogamma = 0; // Intial roation around Z axis
fillstroke = true; // Fill the model to help prevent seems along edges due to anti-aliasing
init(meshPoints, edgeData, vertices); //Initialize the model
}

//Add the object to the controller
k3d.addK3DObject(model);

//Render the model
k3d.tick();

//Reset Sliders
$("#rotateSliderX").slider('value', -45);
$("#rotateSliderY").slider('value', 0);
$("#rotateSliderZ").slider('value', 0);

$("#translateX").slider('value', 0);
$("#translateY").slider('value', 0);
$("#translateZ").slider('value', 0);
}
}

//Create the sliders for rotating and translating the 3D model
$(function () {
var createSlider = function (id, min, max, propertyName) {
$(id).slider({
orientation: "vertical",
range: "min",
min: min,
max: max,
slide: function (event, ui) {
if (model) {
model[propertyName] = ui.value;
k3d.tick();
}
}
});
}

//Create sliders for rotating the model
createSlider("#rotateSliderX", -180, 180, 'otheta');
createSlider("#rotateSliderY", -180, 180, 'ophi');
createSlider("#rotateSliderZ", -180, 180, 'ogamma');

//Create Sliders for translating the model
createSlider("#translateX", -400, 400, 'offx');
createSlider("#translateY", -400, 400, 'offy');
createSlider("#translateZ", -400, 400, 'offz');
});

/***** Helper Methods ****/

//Simple method for calling a REST service
function CallRestService(request) {
var script = document.createElement("script");
script.setAttribute("type", "text/javascript");
script.setAttribute("src", request);
document.body.appendChild(script);
}

//Simple method that rounds numbers to a certain number of significant digits
function SigDigit(number, digits) {
var delta = Math.pow(10, digits);
return Math.round(number * delta) / delta;
}

- Ricky Brundritt, EMEA Bing Maps Technology Solution Professional

New Top of the World and High Resolution Satellite Imagery

$
0
0

We are excited to introduce our new top of the world imagery - which includes bathymetry data from Scripps Institution of Oceanography. We are also releasing over 13 million sq km of updated satellite imagery!

satellitetopiconglobalorthotopicon

satelliteSatellite
High-Resolution Nadir or “Straight Down” orthophotos taken by aircraft or satellite
satellite_divider_550x3

TOW_Full_550x314

We are pleased to present our new seamless base satellite imagery provided by TerraColor. This imagery has a resolution of 15 meters per pixel, providing coverage of the entire world!

This new imagery will enhance overall viewer experience with Bing Maps and the Windows 8 Maps App. Experience it yourself by visiting the Windows 8 Maps App or Bing Maps. The new Top of the World imagery (pictured above) is visible from zoom levels 1-13. Zooming in deeper will reveal our high resolution satellite imagery.

 

Cloud Coverage

Our new base satellite layer is analyzed and processed to provide views with reduced cloud cover in areas notorious for persistent cloud cover such as high latitude and equatorial regions. More detail is now visible in these once problematic areas, as seen in this island example!

 

    Oahu, Hawaii Before

Oahu, Hawaii After

W8 Map App | Bing Maps

Standard-HI

HInewTOW

Bathymetric Shading

In addition to reduced cloud coverage, we now have bathymetric imagery! The topography of the ocean floor is represented by color shading (dark blues to light blues) indicating changes in ocean depth. An ocean mask minimizes areas typically obscured by ice and clouds. The combination of the ocean mask and bathymetric imagery provides a more meaningful view of the world oceans.

 

         Fiji Before

Fiji After

W8 Map App | Bing Maps

beforewestfiji275x250afterwestfiji275x250

High Resolution Satellite Imagery

This month we are adding new High Resolution Satellite imagery with a total coverage of 13,799,276 sq km!

 

SatCoverageEd9_550x229

Visit Bing Maps World Tour Appto viewfull coverage

The images below include: a meandering river running through the Brazilian Highlands, a metropolitan area, dunes, and an active stratovolcano on the Pacific Ring of Fire.

 

Southwest of Jacobina do Piauí, Brazil

Bing Maps

Cheboksary, Russia

Bing Maps

brazilian_highlands225x200cheboksary225x200

Murzuq Desert, Southwestern Libya

Bing Maps

Kamchatka Peninsula, Russia

Bing Maps

libya225x200SAT_Image_Russia

Additional Satellite Highlights:

Sardarshahar, India
Bing Maps


South Georgia and South Sandwich Islands,
United Kingdom overseas territory
 Bing Maps


Pitesti, Romania
 Bing Maps


Arbil, Iraq
Bing Maps

globalortho

Global Ortho
High-Resolution nadir or “straight down” orthophotos taken by aircraftglobalortho_divider_550x3

GOCoverageEd9_550x236_Blog

Visit Bing Maps World Tour Appto view full coverage

 

Color 1Newly Published Global Ortho Imagery
Color 2Published Global Ortho Imagery, United States 100% Complete, Western Europe 90% Complete

Global Ortho Imagery

Our latest release of Global Ortho imagery includes 203,271 sq km of new data! In total we have published 11,001,500 sq km covering 100% of the United States and 90% of Western Europe!

 

Subdivisions, Åhrus, Denmark

Bing Maps

Port de Monaco, Monaco, France

Bing Maps

ArthusDenmark_225x200FR_Nice225x200

Beach near Laredo, Cantabria, Spain

Bing Maps

Wollaton Park, Nottingham, England

Bing Maps

N43W00Spain4225x200BirminghamEngland_225x200
Additional Highlights:

Gran Zebrù Mountain, East of Bormio, Italy 
Bing Maps

Magdalena Peninsula, Santander, Spain
Bing Maps

Turning Torso Skyscraper, Malmö, Sweden 
Bing Maps

Svendborgsund Bridge, Svendborg, Denmark
Bing Maps

Innenstadt, Lübeck, Germany
Bing Maps
 
-The Bing Maps Team
Viewing all 102 articles
Browse latest View live