SharePoint & Bing Maps at SharePoint Connections Las Vegas
Lately, I’ve been exploring Bing Maps as a way to surface SharePoint data within a mapping application. To call Bing Maps a simple mapping application, though, would not be doing it any justice at all. In fact, it’s been a while since I’ve had this much fun ramping up on a technology.
Bing Maps is about enabling you to make decisions, create BI applications, add geo-mapping capabilities into your applications, and more generally about lighting up the web. To get started with some examples, you can check out some of the fundamental functions here: https://www.bing.com/maps/explore/?org=aj&FORM=Z9LH9. Note in the figure below the Map Apps icon; you can click it to see some great apps that have been built using the Bing Maps as the engine.
To see some other solutions, you can also check out IDV Solutions, who have put together some pretty cool examples of how you can use Bing Maps. These examples show a heat map of WW piracy and trends for hurricane activity.
https://vfdemo.idvsolutions.com/piracy/
https://vfdemo.idvsolutions.com/hurricanes/
Hopefully, just by looking at these simple examples you can begin to understand why this is an important integration with SharePoint. For example, companies use SharePoint for collaboration and think about a sales territory application where you have heat-map regions on a Bing Map that show focus areas for a fiscal quarter based on information stored in a SharePoint list or site—go one step further to factor in the external list and BCS and now you’re leveraging external data that is dynamically loaded into SharePoint to dynamically create geospatial applications in the context of a collaborative scenario.
To illustrate some of the key functionality of both Bing Maps and SharePoint integration, I presented a session this week at SharePoint Connections on how to integrate these two technologies. It was a 200-level session where I started the demo from scratch, showed how to do some simple Bing Maps eventing and then looked at different ways to bridge the two technologies. I focused on the Silverlight Bing Maps control.
From the screenshot below, you can see that to the right is the Bing Maps Silverlight control, and to the left are some additional controls. For example, the Swap View checkbox enables you to programmatically change the mode of the map; the Layers checkboxes allows you to hide and show certain layers, and the Stores listbox is where a small amount of data is pulled from SharePoint to populate a listbox. Also, the Add Stores button adds the list of stores along with tool-tips with the customer information from SharePoint as its own layer in the Bing Maps control (represented as the Other checkbox which can also be toggled on and off).
To follow are some of the code snippets that were used in the above demo, which should give you some idea as to how you do something similar. (Note that at the end of this blog, I’ve added a few posts I found useful—in some cases I applied the patterns in these other posts to this solution.)
- To create the application, open Visual Studio 2010 and create a new Silverlight application.
- Add the Bings Maps SDK libraries and the SharePoint Client Object Model libraries. You’ll need to browse to the install location of the Bing Maps SDK and the SharePoint root folder where the assemblies are located (e.g. C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\ClientBin).
- The MainPage.xaml should look something similar to the below (note the <m:Map> element where you need to add your Bing Maps developer key):
<UserControl x:Class="D2_Silverlight_Bing_Control.MainPage"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="500" d:DesignWidth="907"
xmlns:m="clr-namespace:Microsoft.Maps.MapControl;assembly=Microsoft.Maps.MapControl" xmlns:sdk="https://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="363*" />
<RowDefinition Height="137*" />
</Grid.RowDefinitions>
<m:Map x:Name="MainMap" CredentialsProvider="<insert Bing developer key here>"
AnimationLevel="Full"
Mode="Aerial"
Center="36.12154022795178, -115.16997814178468"
NavigationVisibility="Visible"
ZoomLevel="14" Margin="252,0,-214,0" Grid.RowSpan="2">
<m:Pushpin Location="36.12334022795178,-115.158322322" />
<m:MapLayer x:Name="ClothingStores">
<m:Pushpin Location="36.12334022795178,-115.155322322" />
<m:Pushpin Location="36.12224022795178,-115.144312322" />
<m:Pushpin Location="36.16154022795178,-115.169978148" />
<m:Pushpin Location="36.12154022795178,-115.156251621" />
</m:MapLayer>
<m:MapLayer x:Name="SoftwareStores">
<m:Pushpin Location="36.12154022795178,-115.133212212" />
<m:Pushpin Location="36.14154022795178,-115.155438882" />
<m:Pushpin Location="36.13154022795178,-115.161726172" />
<m:Pushpin Location="36.22154022795178,-115.122129818" />
</m:MapLayer>
<m:MapLayer x:Name="SportsStores">
<m:Pushpin Location="36.13321223112321,-115.175322322" />
<m:Pushpin Location="36.111354022795178,-115.11124312322" />
<m:Pushpin Location="36.1444022795178,-115.122978148" />
<m:Pushpin Location="36.1543154022795178,-115.1996251621" />
</m:MapLayer>
</m:Map>
<CheckBox Content="Swap View"
FontWeight="Bold" Height="19"
HorizontalAlignment="Left"
FontSize="10"
Margin="57,70,0,0" Click="chkbxView_Click"
Name="chkbxView" VerticalAlignment="Top"
Width="103" />
<sdk:Label Height="28" FontFamily="Arial Black" FontSize="14"
Content="Map Functions" Margin="15,12,665,0"
Name="lblTitle" VerticalAlignment="Top" />
<CheckBox x:Name="Clothes" FontWeight="Bold"
Content="Clothes" Click="Clothes_Click"
FontSize="10" IsChecked="True"
Margin="57,134,647,198" />
<CheckBox x:Name="Software" FontWeight="Bold"
Content="Software" Click="Software_Click"
FontSize="10" IsChecked="True" Margin="57,171,647,161" />
<CheckBox x:Name="Sports" FontWeight="Bold"
Content="Sports" Click="Sports_Click"
FontSize="10" IsChecked="True"
Margin="57,208,647,124" />
<CheckBox x:Name="Other" FontWeight="Bold"
Content="Other" Click="Other_Click"
FontSize="10" IsChecked="True"
Margin="57,245,647,87" />
<sdk:Label Height="28" Content="Views"
FontFamily="Arial Black" HorizontalAlignment="Left"
Margin="15,46,0,0" Name="lblView"
VerticalAlignment="Top" Width="120" />
<sdk:Label Content="Layers" FontFamily="Arial Black"
Height="28" HorizontalAlignment="Left"
Margin="12,100,0,0" Name="lblLayer"
VerticalAlignment="Top" Width="120" />
<Button Content="Add Stores" Height="23"
HorizontalAlignment="Left" Margin="114,88,0,0"
Name="btnGetStores" VerticalAlignment="Top" Width="75"
Click="btnGetStores_Click" Grid.Row="1" />
<Button Content="Get Stores" Height="23" HorizontalAlignment="Left"
Margin="24,88,0,0" Name="btnGetStoresFromSP" Click="btnGetStoresFromSP_Click"
VerticalAlignment="Top" Width="75" Grid.Row="1" />
<sdk:DataGrid AutoGenerateColumns="True"
Height="124" HorizontalAlignment="Left"
Margin="14,319,0,0" Name="dtgrdSPStores"
VerticalAlignment="Top" Width="219" Grid.RowSpan="2" />
<sdk:Label Content="Stores" FontFamily="Arial Black" Height="28" HorizontalAlignment="Left"
Margin="15,282,0,0" Name="lblStores" VerticalAlignment="Top" Width="120" />
</Grid>
</UserControl>
- The code-behind should look something similar to the below. Note that I’ve commented the functions where appropriate.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Maps.MapControl;
using Microsoft.SharePoint.Client;
using SP = Microsoft.SharePoint.Client;
namespace D2_Silverlight_Bing_Control
{
public partial class MainPage : UserControl
{
//1. Class-level variables
MapLayer OtherStores = new MapLayer();
string[] storeNames = new string[4];
string[] storeSales = new string[4];
private SP.ClientContext context;
private SP.ListItemCollection storeItems;
private SP.List stores;
private List<StoreDetails> listOfStores;
public MainPage()
{
InitializeComponent();
//2. Set properties of checkbox/button to avoid raising errors.
Other.IsEnabled = false;
btnGetStores.IsEnabled = false;
}
//3. This changes the view mode of the Bing Map from Aerial to Road and vice versa.
private void chkbxView_Click(object sender, RoutedEventArgs e)
{
if (chkbxView.IsChecked == true)
{
MainMap.Mode = new AerialMode();
}
else
{
MainMap.Mode = new RoadMode();
}
}
//4. Click events that call the ChangeBingMapLayer method that toggles layers (added declaratively to the XAML) on and off.
private void Clothes_Click(object sender, RoutedEventArgs e)
{
ChangeBingMapLayer(ClothingStores);
}
private void Software_Click(object sender, RoutedEventArgs e)
{
ChangeBingMapLayer(SoftwareStores);
}
private void Sports_Click(object sender, RoutedEventArgs e)
{
ChangeBingMapLayer(SportsStores);
}
private void Other_Click(object sender, RoutedEventArgs e)
{
ChangeBingMapLayer(OtherStores);
}
private void ChangeBingMapLayer(MapLayer layer)
{
layer.Visibility = (layer.Visibility == Visibility.Collapsed) ? Visibility.Visible : Visibility.Collapsed;
}
//5. Uses a set of asynchronous methods to get data from a SharePoint list.
private void btnGetStoresFromSP_Click(object sender, RoutedEventArgs e)
{
ConnectToSharePoint();
}
private void ConnectToSharePoint()
{
context = new SP.ClientContext("https://blueyonderdemo");
context.Load(context.Web);
context.ExecuteQueryAsync(OnConnectSucceeded, null);
}
private void ConnectLists()
{
context.Load(context.Web.Lists);
context.ExecuteQueryAsync(OnConnectListsSucceeded, null);
}
private void GetListData()
{
stores = context.Web.Lists.GetByTitle("Stores");
context.Load(stores);
context.Load(stores.RootFolder);
context.ExecuteQueryAsync(OnGetListDataSucceeded, null);
}
private void LoadItems()
{
var camlQuery = new SP.CamlQuery();
camlQuery.ViewXml = "<View/>";
storeItems = stores.GetItems(camlQuery);
context.Load(storeItems);
context.ExecuteQueryAsync(OnLoadItemsSucceeded, null);
}
//6. Cycles through the returned data and binds the data to a listbox and also populates array for later use.
private void ShowItems()
{
int x = 0;
listOfStores = new List<StoreDetails>();
foreach (SP.ListItem listItem in storeItems)
{
listOfStores.Add(
new StoreDetails()
{
Store = listItem["Title"].ToString(),
Sales = listItem["Sales"].ToString()
});
storeNames[x] = listItem["Title"].ToString();
storeSales[x] = listItem["Sales"].ToString();
x++;
}
dtgrdSPStores.ItemsSource = listOfStores;
Other.IsEnabled = true;
btnGetStores.IsEnabled = true;
}
private void OnConnectSucceeded(Object sender, SP.ClientRequestSucceededEventArgs args)
{
Dispatcher.BeginInvoke(ConnectLists);
}
private void OnConnectListsSucceeded(Object sender, SP.ClientRequestSucceededEventArgs args)
{
Dispatcher.BeginInvoke(GetListData);
}
private void OnGetListDataSucceeded(Object sender, SP.ClientRequestSucceededEventArgs args)
{
Dispatcher.BeginInvoke(LoadItems);
}
private void OnLoadItemsSucceeded(Object sender, SP.ClientRequestSucceededEventArgs args)
{
Dispatcher.BeginInvoke(ShowItems);
}
//7. Add code to add the stores (info from SP) to the Bing maps using some styling for the Tooltip that is stored in the App.xaml file.
private void btnGetStores_Click(object sender, RoutedEventArgs e)
{
Other.IsEnabled = true;
btnGetStores.IsEnabled = false;
MainMap.Children.Add(OtherStores);
CustomPushpin otherStore1 = new CustomPushpin();
otherStore1.Location = new Location(36.17154022795178, -115.2019299781417);
otherStore1.Title = storeNames[0].ToString();
otherStore1.Description = storeSales[0].ToString();
ToolTipService.SetToolTip(otherStore1, new ToolTip()
{
DataContext = otherStore1,
Style = Application.Current.Resources["CustomPushpin"] as Style
});
OtherStores.Children.Add(otherStore1);
CustomPushpin otherStore2 = new CustomPushpin();
otherStore2.Location = new Location(36.19121122123112, -115.1933928102321);
otherStore2.Title = storeNames[1].ToString();
otherStore2.Description = storeSales[1].ToString();
ToolTipService.SetToolTip(otherStore2, new ToolTip()
{
DataContext = otherStore2,
Style = Application.Current.Resources["CustomPushpin"] as Style
});
OtherStores.Children.Add(otherStore2);
CustomPushpin otherStore3 = new CustomPushpin();
otherStore3.Location = new Location(36.20211928123321, -115.1829182900912);
otherStore3.Title = storeNames[2].ToString();
otherStore3.Description = storeSales[2].ToString();
ToolTipService.SetToolTip(otherStore3, new ToolTip()
{
DataContext = otherStore3,
Style = Application.Current.Resources["CustomPushpin"] as Style
});
OtherStores.Children.Add(otherStore3);
CustomPushpin otherStore4 = new CustomPushpin();
otherStore4.Location = new Location(36.18882918928192, -115.1728991827188);
otherStore4.Title = storeNames[3].ToString();
otherStore4.Description = storeSales[3].ToString();
ToolTipService.SetToolTip(otherStore4, new ToolTip()
{
DataContext = otherStore4,
Style = Application.Current.Resources["CustomPushpin"] as Style
});
OtherStores.Children.Add(otherStore4);
}
}
}
Let’s take a look at each of the numbered code snippets.
1. Class-level Variables
The first object here is the MapLayer object, which is what we’re going to use to separate out the layer that will contain SharePoint information. Think of a MapLayer as a layer of objects that has events and properties associated with it. You can see later on in the code, for example, that the OtherStores MapLayer can be toggled on and off (visibility that is). The storeNames and storeSales arrays are simply used as a separate in-memory data store to contain the data coming back from SharePoint. You may or may not want to do this. The other objects are specific to the client object model and help store data coming from SharePoint.
MapLayer OtherStores = new MapLayer();
string[] storeNames = new string[4];
string[] storeSales = new string[4];
private SP.ClientContext context;
private SP.ListItemCollection storeItems;
private SP.List stores;
private List<StoreDetails> listOfStores;
2. Class-level Variables
In the MainPage() function, setting these properties is a simple way to manage (ahem, avoid) error handling.
public MainPage()
{
InitializeComponent();
//2. Set properties of checkbox/button to avoid raising errors.
Other.IsEnabled = false;
btnGetStores.IsEnabled = false;
}
3. Class-level Variables
The name of the Bing Map (if you take a look at the declarative XAML) is MainMap. This code simply sets the Mode property to either AerialMode or RoadMode (i.e. Aerial view or Road view).
private void chkbxView_Click(object sender, RoutedEventArgs e)
{
if (chkbxView.IsChecked == true)
{
MainMap.Mode = new AerialMode();
}
else
{
MainMap.Mode = new RoadMode();
}
}
4. Toggling the Bing Map MapLayers.
There are four MapLayers in the Bing Map (i.e. ClothingStores, SoftwareStores, SportsStores, & OtherStores). Each of the MapLayers represents a layer of stores that are broken out by type of store. What this code does is if you click one of the checkboxes, the click event will call the ChangeBingMapLayer method and then toggle the visibility.
private void Clothes_Click(object sender, RoutedEventArgs e)
{
ChangeBingMapLayer(ClothingStores);
}
private void Software_Click(object sender, RoutedEventArgs e)
{
ChangeBingMapLayer(SoftwareStores);
}
private void Sports_Click(object sender, RoutedEventArgs e)
{
ChangeBingMapLayer(SportsStores);
}
private void Other_Click(object sender, RoutedEventArgs e)
{
ChangeBingMapLayer(OtherStores);
}
private void ChangeBingMapLayer(MapLayer layer)
{
layer.Visibility = (layer.Visibility == Visibility.Collapsed) ? Visibility.Visible : Visibility.Collapsed;
}
5. Toggling the Bing Map MapLayers.
I recently picked up and read Microsoft Silverlight 4 and SharePoint 2010 Integration by Gaston Hillar. I liked one of the patterns he walked through in the book, so I tried it in this example. It essentially splits out the different asynchronous calls into a set of function calls that a) connect to SharePoint, b) connect to the lists in SharePoint, c) get a specific list, and d) gets the list items in that list. There are less verbose ways of doing this, but this illustrates how you can use the client object model to not only queue up and batch process commands to the server, but it also does so at distinct intervals. In past demos, I had included most of my client object model code in one call—I felt this was easier to read and follow.
private void btnGetStoresFromSP_Click(object sender, RoutedEventArgs e)
{
ConnectToSharePoint();
}
private void ConnectToSharePoint()
{
context = new SP.ClientContext("https://blueyonderdemo");
context.Load(context.Web);
context.ExecuteQueryAsync(OnConnectSucceeded, null);
}
private void ConnectLists()
{
context.Load(context.Web.Lists);
context.ExecuteQueryAsync(OnConnectListsSucceeded, null);
}
private void GetListData()
{
stores = context.Web.Lists.GetByTitle("Stores");
context.Load(stores);
context.Load(stores.RootFolder);
context.ExecuteQueryAsync(OnGetListDataSucceeded, null);
}
private void LoadItems()
{
var camlQuery = new SP.CamlQuery();
camlQuery.ViewXml = "<View/>";
storeItems = stores.GetItems(camlQuery);
context.Load(storeItems);
context.ExecuteQueryAsync(OnLoadItemsSucceeded, null);
}
6. Binding SharePoint data to two data constructs.
The ShowItems method does two main things. First, it binds the data that is returned from the SharePoint call to a listbox using a custom object (called StoreDetails), which has two public string properties: Store and Sales. The listOfStores is the list collection that is used to house the data and then bind it to the listbox. Second, it populates an array that has 4 elements with the same information coming back from the server. I use the array later in the code to ‘dynamically’ load SharePoint data into the Bing Map. You may choose to forego this and simply use the List collection.
private void ShowItems()
{
int x = 0;
listOfStores = new List<StoreDetails>();
foreach (SP.ListItem listItem in storeItems)
{
listOfStores.Add(
new StoreDetails()
{
Store = listItem["Title"].ToString(),
Sales = listItem["Sales"].ToString()
});
storeNames[x] = listItem["Title"].ToString();
storeSales[x] = listItem["Sales"].ToString();
x++;
}
dtgrdSPStores.ItemsSource = listOfStores;
Other.IsEnabled = true;
btnGetStores.IsEnabled = true;
}
7. Adding the store information (with SharePoint data) to the Bing Maps.
The btnGetStores_Click event enables the controls and then add a child MapLayer (OtherStores) to MainMap. It then adds a CustomPushpin object (which derives from Pushpin and adds three additional properties—Location, Title and Description), which uses the storeNames array to add the data to the Bing Map. It does this four times for each of the stores in the storeName array. Also, notice the ToolTipService that is used to provide some custom formatting for the tool-tip. The formatting is shown below.
private void btnGetStores_Click(object sender, RoutedEventArgs e)
{
Other.IsEnabled = true;
btnGetStores.IsEnabled = false;
MainMap.Children.Add(OtherStores);
CustomPushpin otherStore1 = new CustomPushpin();
otherStore1.Location = new Location(36.17154022795178, -115.2019299781417);
otherStore1.Title = storeNames[0].ToString();
otherStore1.Description = storeSales[0].ToString();
ToolTipService.SetToolTip(otherStore1, new ToolTip()
{
DataContext = otherStore1,
Style = Application.Current.Resources["CustomPushpin"] as Style
});
…
}
…
<Application.Resources>
<Style x:Key="CustomPushpin" TargetType="ToolTip">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border CornerRadius="5">
<Border.Background>
<SolidColorBrush Color="Blue" Opacity="0.3"/>
</Border.Background>
<ContentPresenter Margin="5">
<ContentPresenter.Content>
<StackPanel Margin="2.5" MaxWidth="100">
<TextBlock Text="{Binding Title}" FontWeight="Bold" FontSize="12" Foreground="White"/>
<TextBlock Text="{Binding Description}" Foreground="White" TextWrapping="Wrap"/>
</StackPanel>
</ContentPresenter.Content>
</ContentPresenter>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Application.Resources>
</Application>
When prepping for the session at SharePoint Connections, I consulted a number of blogs, articles and books. I can’t remember all of them, but Chris Pendleton’s blog posts was helpful as was the Silverlight Bing Maps Control Interactive SDK. Keith Kinnan’s blog was also helpful.
If you’d like to check out the PDF version of the deck I presented, you can get it here:
https://cid-40a717fc7fcd7e40.office.live.com/browse.aspx/SPC%5E_Nov%5E_2010%5E_Decks%5E_And%5E_Demos
I’ve also added the above code there as well.
To get started with Bing Maps and the Bing Maps Silverlight Control, visit:
- Bing Maps Developer Center: https://www.microsoft.com/maps/developers/web.aspx
- Bing Maps Silverlight Control on MSDN: https://msdn.microsoft.com/en-us/library/ee681884.aspx
Have fun!
Steve