Introduction
Bing Maps SDK is available for Visual Studio 2012 RC and Windows 8 release preview, Bing Maps SDK is easy fast and cool control that you can use in your Metro App.
Bing Maps SDK for Metro style apps (Release Preview) combines the power of Windows 8 and Bing Maps™ to provide an enhanced mapping experience for Metro style apps. This SDK includes controls for apps built using JavaScript, as well as apps built using C#, C++, and Visual Basic.
In this article we'll see how to use Bing Maps SDK for C# & JavaScript metro applications.
Step 1: Maps Developer Account
Before you can use Bing SDK from Windows 8 Metro App you need Maps Developer Account, open http://www.bingmapsportal.com/, create or enter with existing Windows Live account. Choose “Create or view keys” and create new Key for your application.
C#
Copy and save that key, create a resource in App.xaml called BingCredentials
with the key value.
<x:String
x:Key="BingCredentials">AnJwk78cjoJfBMQXAxC85FwLiLPwmy6
paQ1TsTJVg_-62hNraRRUzXRz1RELKfHa</x:String>
Step 2: Create Bing Maps Metro Application
- Download the latest bits - Bing Maps SDK for Metro style apps
- Open Visual Studio 2012 RC, and create a Blank Project. (C# or JavaScript)
JavaScript
“Add Reference” –> select “Bing Maps for JavaScript (Beta)….” and click OK.
C#
“Add Reference” –> select “Bing Maps for C#….” and click OK.
After adding the Bing Maps reference to our project you will see a “Warning” icon on the Bing binary, the reason is Bing Maps doesn't support “Any CPU” configuration.
You need to change the project configuration as describe here - http://msdn.microsoft.com/en-us/library/hh855146.aspx
- C#, Visual Basic: ARM, x86 or x64
- C++: ARM, Win32 or x64
Read the reference API docs: Bing Maps SDK
Step 3: Add Map Control
C#
Now open “MainPage.xaml” and just drag the “Map” control from ToolBox or add the following XAML code:
xmlns:Maps="using:Bing.Maps"
<Maps:Map Credentials="{StaticResource BingCredentials}" />
Run the application, using Touch or Mouse you can move the map and zoom in and out.
JavaScript
Inside default.html we add a div element under the body, this will be the map holder.
<body>
<div id="mapdiv"></div>
</body>
Now we need to load the map into that control, be just before we need to load the map module, in order to do that we’ll call Maps.loadMoudle
and set a callback to another method called initMap
.
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
if (args.detail.previousExecutionState !== activation.ApplicationExecutionState.terminated) {
} else {
}
args.setPromise(WinJS.UI.processAll().then(function () {
Microsoft.Maps.loadModule('Microsoft.Maps.Map', { callback: initMap });
}));
}
};
After the Maps module is loaded the initMap
function will place the Map into the mapdiv
element, the function below defines several default values such as:
- Bing Map credentials
- Center location
- Map type
- Zoom
Finally we obtain the mapdiv
element and define a new object called map defined from new Map that receives the div and the options.
function initMap() {
try {
var mapOptions =
{
credentials: "AvOW5Fz4QTsubTTdmaVnseeZnAQ0JYwbx_6zdMdgHk6iF-pnoTE7vojUFJ1kXFTP",
center: new Microsoft.Maps.Location(50, 50),
mapTypeId: Microsoft.Maps.MapTypeId.road,
zoom: 5
};
var mapDiv = document.querySelector("#mapdiv");
map = new Microsoft.Maps.Map(mapDiv, mapOptions);
}
catch (e) {
var md = new Windows.UI.Popups.MessageDialog(e.message);
md.showAsync();
}
}
Step 4: Basics
C# + JavaScript
In this section we'll use Geolocator
that require "Location
" permissions, so make sure you enable “Location
” as application capability.
I’ve added couple of buttons on the right side to help us controlling the map view.
C#
Zoom
– the map control have a “ZoomLevel” double property, the zoom range is 0-20.
private void btnZoomOut_Click(object sender, RoutedEventArgs e)
{
var zoom = map.ZoomLevel - 2;
map.SetZoomLevel(zoom < minZoom ? minZoom : zoom);
}
private void btnZoomIn_Click(object sender, RoutedEventArgs e)
{
var zoom = map.ZoomLevel + 2;
map.SetZoomLevel(zoom > maxZoom ? maxZoom : zoom);
}
MapType
– there are three types of maps: Aerial, Road and Birdseye.
private void btnChangeMapType_Click(object sender, RoutedEventArgs e)
{
switch (map.MapType)
{
case Bing.Maps.MapType.Aerial:
map.MapType = Bing.Maps.MapType.Birdseye;
break;
case Bing.Maps.MapType.Birdseye:
map.MapType = Bing.Maps.MapType.Road;
break;
default:
map.MapType = Bing.Maps.MapType.Aerial;
break;
}
}
Set Current Location – Using Geolocator
to find current location, then using map SetView
to center map based on the new location.
private async void btnSetLocation_Click(object sender, RoutedEventArgs e)
{
Geolocator geolocator = new Geolocator();
var pos = await geolocator.GetGeopositionAsync();
Location location = new Location(pos.Coordinate.Latitude, pos.Coordinate.Longitude);
map.SetView(location, 15.0f);
}
JavaScript
- Zoom – The JS map control has built in Zoom in and Zoom out but you can still change the zoom from code:
var zoomValue = map.getZoom();
map.setView({ zoom: zoomValue + 2 });
Map Type - there are three types of maps : Aerial, Road and Birdseye.
function changeMapType() {
var type = map.getMapTypeId();
switch (type) {
case Microsoft.Maps.MapTypeId.aerial:
type = Microsoft.Maps.MapTypeId.road;
break;
case Microsoft.Maps.MapTypeId.road:
type = Microsoft.Maps.MapTypeId.birdseye;
break;
default:
type = Microsoft.Maps.MapTypeId.aerial;
break;
}
map.setView({ center: map.getCenter(), mapTypeId: type });
}
Set Current Location – Using Geolocator
to find current location, then using map SetView
to center map based on the new location (Make sure you enable “Location” as application capability.)
function centerPosition() {
var geolocator = new Windows.Devices.Geolocation.Geolocator();
geolocator.getGeopositionAsync().then(function (loc) {
var mapCenter = map.getCenter();
mapCenter.latitude = loc.coordinate.latitude;
mapCenter.longitude = loc.coordinate.longitude;
map.setView({ center: mapCenter, zoom: 18 });
});
}
Step 5: Push Pin
In step 4 we used “Geolocator” to find our current position, then we center the map based on the new position.
Instead of just centering the map we’ll like to add a PushPin
directly on our location.
C#
First we add the “PushPin
” control as a child of the Map, you can also create any user control for that (if you want new image etc…)
<Maps:Map x:Name="map" Margin="60,0,0,0"
Credentials="{StaticResource BingCredentials}"
ShowScaleBar="True" ZoomLevel="5"
Grid.Row="1" ShowTraffic="False" >
<Maps:Map.Children>
<Maps:Pushpin x:Name="pushPin" />
</Maps:Map.Children>
</Maps:Map>
Now, in order to place the PushPin
on the desire location you need to use the MapLayer.SetPosition
, passing the pushpin control and the location value.
private async void btnSetLocation_Click(object sender, RoutedEventArgs e)
{
Geolocator geolocator = new Geolocator();
var pos = await geolocator.GetGeopositionAsync();
Location location = new Location(pos.Coordinate.Latitude, pos.Coordinate.Longitude);
MapLayer.SetPosition(pushPin, location);
map.SetView(location, 15.0f);
}
JavaScript
We need to create new PushPin
object and assign the location where the pushpin should be placed on the map.
function addPushPin(location) {
map.entities.clear();
var pushpin = new Microsoft.Maps.Pushpin(location, null);
map.entities.push(pushpin);
}
function centerPosition() {
var geolocator = new Windows.Devices.Geolocation.Geolocator();
geolocator.getGeopositionAsync().then(function (loc) {
var mapCenter = map.getCenter();
mapCenter.latitude = loc.coordinate.latitude;
mapCenter.longitude = loc.coordinate.longitude;
map.setView({ center: mapCenter, zoom: 15 });
addPushPin(mapCenter);
});
}
Step 6: Tap and Pin
One more functionality I’ll would like to add is “Tap”, this mean the user click tap on the map (or mouse click) and this will place a Push Pin where the user clicked.
C#
First I’ve create a new User Control called CustomPin
that only contains XAML for star shape.
<Canvas>
<Path x:Name="border"
Stretch="Fill"
Data="F1 M 0,217.042L 227.5,217.042L 297.875, 0L 367.542,217L
595.542,217L 410.208,353.667L 480.708, 569.667L 297.208,
436.667L 116.208,568.167L 185.708,352.667L 0,217.042 Z"
Width="25" Height="25" Margin="-12.5,-12.5,0,0"
Fill="Yellow" Stroke="Red">
</Path>
</Canvas>
In the MainPage
constructor we add the new User control inside the Map children collection, we can also add this directly from XAML.
private CustomPin pin;
public MainPage()
{
this.InitializeComponent();
pin = new CustomPin();
map.Children.Add(pin);
}
We need to add a “Tapped
” event for Map control
<Maps:Map x:Name="map" Margin="60,0,0,0"
Credentials="{StaticResource BingCredentials}"
ShowScaleBar="True" ZoomLevel="5"
Grid.Row="1" ShowTraffic="False"
Tapped="map_Tapped">
<Maps:Map.Children>
<Maps:Pushpin x:Name="pushPin" />
</Maps:Map.Children>
</Maps:Map>
When tapped event is fired we can use the tapped event arguments to get the relative position of the tap position by called the GetPosition
method, this will give us the pixel position on the Map control not the actual location. So we need to translate the pixel position to location.
We can do that by calling the TryPixelToLocation
method from the map control, then after we recived
the location where the user clicked we can add a push pin and center the map based on the the new location.
private void map_Tapped(object sender, TappedRoutedEventArgs e)
{
var pos = e.GetPosition(map);
Location location;
map.TryPixelToLocation(pos, out location);
MapLayer.SetPosition(pin, location);
map.SetView(location);
}
JavaScript
One more functionality I’ll would like to add is “Tap”, this mean the user click tap on the map (or mouse click) and this will place a Push Pin where the user clicked.
Instead of using the standard PushPin image I want to load my own image as pin, so I add a “star.png” file to images folder. Also I add new event listener for "click" on the mapDiv
element calling new function called tapped
.
var mapDiv = document.querySelector("#mapdiv");
mapDiv.addEventListener('click', tapped, false);
map = new Microsoft.Maps.Map(mapDiv, mapOptions);
When the tapped
event is fired we can use the tapped event arguments to get the relative position of the tap position by calling the tryPixelToLocation
method from the map control, then after we received the location where the user clicked we can add a push pin and center the map based on the the new location.
function tapped(location) {
var mapCenter = map.getCenter();
var pos = map.tryPixelToLocation(new Microsoft.Maps.Point(location.clientX, location.clientY),
Microsoft.Maps.PixelReference.control);
mapCenter.latitude = pos.latitude;
mapCenter.longitude = pos.longitude;
map.setView({ center: mapCenter });
var pin = new Microsoft.Maps.Pushpin({
latitude: pos.latitude, longitude: pos.longitude }, {
icon: '/images/star.png',
anchor: new Microsoft.Maps.Point(8, 8)
});
map.entities.clear();
map.entities.push(pin);
}
Step 7 - Apply Custom Tile Overlay - Google Maps
I got a question from a friend who wanted to replace Bing Maps Tiles with Google Maps Tiles.
You might ask yourself why? If you want Google Tiles just replace Bing Control and work with Google Maps….. In this case I want to work with Bing Map vontrol because the benefits I get in Metro Applications in Windows 8 for C#, C++, VB.NET, and JavaScript.
And I also want Google Maps language support.
Currently Bing Maps doesn't support any language except English, and I want to display the map with the user natural language.
Before we jump to the solution let’s talk about the common ground between Bing and Google Maps.
- Each tile consists of 256 x 256 pixels
- Each succeeding zoom level divides the map into 4 N tiles, where N refers to the zoom level. For example:
- at zoom level 1,each map divides the world up into a 2x2 grid for a total of 4 tiles;
- at zoom level 2, each map divides up the world into a 4x4 grid for a total of 16 tiles, etc
- (From “Tile Coordinates” Google API) – Zoom Level 2 (4x4)
- (From “Tile Coordinates and Quadkeys” MSDN) – Zoom Level 3 (8x8)
I order to replace the tile we need to have Google map url that will return the specific tile based on the X, Y, and Zoom level – and we have it here:
Google Maps –> calling "https://mts0.google.com/vt/hl=he&src=api&x=2&s=&y=1&z=3” will return: (X = 2 , Y = 1 , Zoom = 3)
So what is the problem? The problem is that in order to replace Tiles in Bing Maps you need to pass the “QuadKey
” value. - MapTileLayer Class
As you can see from the code example below the TileSource
gets the URL of the custom tile with a parameter called {quadkey}
.
C#
MapTileLayer tileLayer = new MapTileLayer();
tileLayer.TileSource =
"http://www.microsoft.com/maps/isdk/ajax/layers/lidar/{quadkey}.png";
map.TileLayers.Add(tileLayer);
JavaScript
function addTileOverlay() {
var options = { uriConstructor:
"http://www.microsoft.com/maps/isdk/ajax/layers/lidar/{quadkey}.png"};
var tileSource = new Microsoft.Maps.TileSource(options);
var tilelayer = new Microsoft.Maps.TileLayer({ mercator: tileSource });
map.entities.push(tilelayer);
}
What is QuadKey? - A quadkey uniquely identifies single tiles at particular levels of detail
To optimize the indexing and storage of tiles, the two-dimensional tile XY coordinates are combined into one-dimensional strings called quadtree keys, or “quadkeys” for short. Each quadkey uniquely identifies a single tile at a particular level of detail, and it can be used as an key in common database B-tree indexes. To convert tile coordinates into a quadkey, the bits of the Y and X coordinates are interleaved, and the result is interpreted as a base-4 number (with leading zeros maintained) and converted into a string. For instance, given tile XY coordinates of (3, 5) at level 3, the quadkey is determined as follows:
- tileX = 3 = 011 2
- tileY = 5 = 101 2
- quadkey = 100111 2 = 213 4 = “213”
Quadkeys have several interesting properties. First, the length of a quadkey (the number of digits) equals the level of detail of the corresponding tile. Second, the quadkey of any tile starts with the quadkey of its parent tile (the containing tile at the previous level). As shown in the example below, tile 2 is the parent of tiles 20 through 23, and tile 13 is the parent of tiles 130 through 133:
So now that we understand what is QuadKey we understand that we need to translate this value into Google Map X,Y and Zoom, we can use the following method to convert the QuadKey into x, y, and level values.
C#
public static void QuadKeyToTileXY(string quadKey,
out int tileX, out int tileY, out int levelOfDetail)
{
tileX = tileY = 0;
levelOfDetail = quadKey.Length;
for (int i = levelOfDetail; i > 0; i--)
{
int mask = 1 << (i - 1);
switch (quadKey[levelOfDetail - i])
{
case '0':
break;
case '1':
tileX |= mask;
break;
case '2':
tileY |= mask;
break;
case '3':
tileX |= mask;
tileY |= mask;
break;
default:
throw new ArgumentException("Invalid QuadKey digit sequence.");
}
}
}
But you don’t need to do that because the quadkey is automatically translated into those values, so instead of setting a static URL for TileOverlay
we will set a function called convert that received the quadkey object.
JavaScript
function convert(quadkey) {
return "https://mts0.google.com/vt/hl=he&src=api&x=" + quadkey.x +
"&s=&y=" + quadkey.y +
"&z=" + quadkey.levelOfDetail;
}
function addTileOverlay() {
var options = { uriConstructor: convert };
var tileSource = new Microsoft.Maps.TileSource(options);
var tilelayer = new Microsoft.Maps.TileLayer({ mercator: tileSource });
map.entities.push(tilelayer);
}
Bing Maps without Google Tiles
Bing Maps with Google Tiles