Johannes's profileHannes's Virtual Earth B...BlogListsSkyDrive Tools Help

Hannes's Virtual Earth Blog

November 28

Clustering with the Bing Maps Silverlight Control – Part 3

Publishing to Windows Azure

In the previous 2 parts we have created a Photo-Map with the Bing Maps Silverlight Control and implemented server-side clustering. Now we are going to publish this application to Windows Azure.

Let’s start with the photos. I use SpaceBlock which is available on CodePlex to upload the photos into my Windows Azure Blob Storage.

image

In this example we don’t want to deal with access control so we just set public access rights on the root-folder:

image

In the SQL Azure Server Administration Tool we create now a new database.

image

We will also need to set the firewall rules. In order to allow our Windows Azure application to access the database we need at least “Allow Microsoft Services”. This can be done by simply ticking the checkbox. Since we also want to access the database from our local machine we need to grant access to our own IP address as well. In order to do that we click on “Add Rule”. The web site automatically determines our IP-address. We copy this address and paste it in the start- and end- of the IP range. Then we submit the new rule.

image

We should now have 2 firewall rules.

image

Now we need to create the table and populate it with our data. There are a couple of migration tools out there but if we use SQL Server 2008 R2 we already have all we need. In the SQL Server Management Studio we right-click on your database and select from the context menu “Tasks” and then “Generate Scripts”.

image

In the wizard we select the objects that we want to script and then in the advanced options we select that we want to script for a SQL Azure database and that we want both the schemas and the data.

image

Now we change the database connection in our SQL Server Management Studio to our SQL Azure database. We will get the details for the connection from the SQL Azure Server Administration Page.

image

image

Finally we execute the script that was generated with the wizard before:

image

All right, the data is already in the cloud now we need to do the same with our application. In the application we need to switch the baseURL in the MainPage.xaml.vb from our local machine to the Windows Azure Blob Storage:

    'Public Shared baseURL As String = "http://localhost"
    Public Shared baseURL As String = http://hannesve.blob.core.windows.net

In the web.config we need to modify the connection string. We can generate a connection string in the SQL Azure Server Administration Page but that one didn’t quite work for me. In order to make it work I had to append @ServerName to the User ID:

<add name="TalkingDonkeyAzure" connectionString="Server=tcp:siljplpmxm.database.windows.net;Database=TalkingDonkey;User ID=jkebeck@siljplpmxm;Password=YOUR_PWD;Trusted_Connection=False;" providerName="System.Data.SqlClient" />

Once we have made the changes we can build our application again and create a new or open an existing Cloud Service project in Visual Studio. In my example I have an existing application with a couple of samples and I want to add this one to it. In the Solution Explorer I create a new folder under the Web Role project and add the xap-file as well as the htm- and js-files as existing items from the project that we have been working on. The web service goes into the root of this web role project since that’s where our code expects the service endpoint.

image

Once we have tested the project we can right-click on the Azure-Project and select “Publish” from the context menu:

image

This will package the solution, open Windows Explorer at the folder where the package was created and it will also open the Windows Azure Administration page where you can upload the package to a staging environment and test it before you swap it with the production environment.

image

That’s it. Ciao for today :-)

Clustering with the Bing Maps Silverlight Control – Part 2

Adding the Map and Extending the Navigation Control

Now that we have set up the database and created the web service we can move on to the Silverlight application. First we add the Bing Maps Silverlight control as reference to our Silverlight project:

image

Next we add a service reference pointing to the web service that we have created before. Click on discover to find the service and give it a name.

image

Particularly when we work with Windows Azure we must be able to modify the endpoint of the service dynamically and hence we add the following code to the public sub new method of the MainPage.xaml.vb

'Reset service reference endpoint (important when port or domain changes)
Dim wsURL As String = "http://" + HtmlPage.Document.DocumentUri.Host + ":" + _
HtmlPage.Document.DocumentUri.Port.ToString + "/svcPhotos.svc" svc.Endpoint.Address = New ServiceModel.EndpointAddress(wsURL)

In the MainPage.xaml we add the namespace for the Bing Maps Silverlight Control and the map itself.

image

We also add a mini-map that provides an overview. In this overview-map we remove all components that might obstruct the view on the map itself.

image

To add additional elements to the navigation control we add a handler right after we initialized the component:

Public Sub New()
    InitializeComponent()

    ' Add additional controls to the navigation bar
    AddHandler MyMap.MapForeground.TemplateApplied, AddressOf MapForeground_TemplateApplied

We are going to add 3 additional control elements

  • toggle mini map
  • toggle full-screen and
  • show/hide photos
#Region "Navigation"
    Private Sub MapForeground_TemplateApplied(ByVal sender As Object, ByVal e As EventArgs)
        AddHandler MyMap.MapForeground.NavigationBar.TemplateApplied, _
AddressOf NavigationBar_TemplateApplied End Sub Private Sub NavigationBar_TemplateApplied(ByVal sender As Object, ByVal e As EventArgs) Dim myNavCtrl As NavigationBar = MyMap.MapForeground.NavigationBar ' Add custom command buttons myNavCtrl.HorizontalPanel.Children.Add(New CommandSeparator()) Dim btnToggleMiniMap As New CommandToggleButton( _
New ToggleMiniMap(), "Mini-Map", "Toggle Mini-Map") myNavCtrl.HorizontalPanel.Children.Add(btnToggleMiniMap) myNavCtrl.HorizontalPanel.Children.Add(New CommandSeparator()) Dim btnPhotos As New CommandToggleButton( _
New TogglePhotos(), "Photos", "Show/Hide Photos") myNavCtrl.HorizontalPanel.Children.Add(btnPhotos) Dim imgFullScreen As New Image imgFullScreen.Source = New BitmapImage( _
New Uri("FullScreen.png", UriKind.RelativeOrAbsolute)) imgFullScreen.Width = 15 Dim btnFullScreen = New CommandButton( _
New ToggleFullscreen(), imgFullScreen, "Toggle Fullscreen") myNavCtrl.VerticalPanel.Children.Add(btnFullScreen) End Sub #End Region

The functions that are being executed when we click these control-elements are in separate classes and I have simply added them at the end of MainPage.xaml.vb

#Region "Additional Commands in Dashboard"

Class ToggleMiniMap
    Inherits NavigationBarCommandBase

    Public Sub New()
    End Sub

    Public Overloads Overrides Sub Execute(ByVal map As MapBase)
        Dim status As NavigationBarCommandStatus = Me.GetStatus(map)

        If status = NavigationBarCommandStatus.Checked Then
            MainPage.myMiniMap.Visibility = Visibility.Collapsed
        ElseIf status = NavigationBarCommandStatus.Normal Then
            MainPage.myMiniMap.Visibility = Visibility.Visible
        End If
    End Sub

    Public Overloads Overrides Function GetStatus(ByVal map As MapBase) _
As NavigationBarCommandStatus Dim status As NavigationBarCommandStatus = NavigationBarCommandStatus.Normal If MainPage.myMiniMap.Visibility = Visibility.Visible Then status = NavigationBarCommandStatus.Checked End If Return status End Function End Class Class TogglePhotos Inherits NavigationBarCommandBase Public Sub New() End Sub Public Overloads Overrides Sub Execute(ByVal map As MapBase) Dim status As NavigationBarCommandStatus = Me.GetStatus(map) If status = NavigationBarCommandStatus.Checked Then MainPage.slPhotos.Visibility = Visibility.Collapsed MainPage.slPhotos.Children.Clear() RemoveHandler map.ViewChangeEnd, AddressOf MainPage.MyMap_ViewChangeEnd ElseIf status = NavigationBarCommandStatus.Normal Then MainPage.slPhotos.Visibility = Visibility.Visible AddHandler map.ViewChangeEnd, AddressOf MainPage.MyMap_ViewChangeEnd AddHandler MainPage.svc.GetClusterInViewCompleted, _
AddressOf MainPage.svc_GetClusterInViewCompleted Dim bounds As LocationRect = map.BoundingRectangle MainPage.svc.GetClusterInViewAsync(bounds.Northwest.Latitude, _
bounds.Northwest.Longitude, bounds.Southeast.Latitude, _
bounds.Southeast.Longitude, CInt(map.TargetZoomLevel), _
MainPage.mapWidth, MainPage.mapHeight) End If End Sub Public Overloads Overrides Function GetStatus(ByVal map As MapBase) _
As NavigationBarCommandStatus Dim status As NavigationBarCommandStatus = NavigationBarCommandStatus.Normal If MainPage.slPhotos.Visibility = Visibility.Visible Then status = NavigationBarCommandStatus.Checked End If Return status End Function End Class Class ToggleFullscreen Inherits NavigationBarCommandBase Public Sub New() End Sub Public Overloads Overrides Sub Execute(ByVal map As MapBase) Application.Current.Host.Content.IsFullScreen = _
Not Application.Current.Host.Content.IsFullScreen End Sub End Class #End Region

 

Preparing the Photo-Viewer

The Photo-Viewer is basically a ChildWindow that comes as part of the Silverlight Toolkit for Visual Studio 2008 SP1 and is out-of-the-box part of Visual Studio 2010 Beta 2. So if you are using Visual Studio 2010 Beta 2 just add a new item of type ChildWindow

image

The basic xaml is quite simple since we add most of child-elements dynamically through code…

<controls:ChildWindow x:Class="BM_SL_PhotoMap.PhotoShow"
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
           Width="400" Height="300"
           Title="PhotoShow">

    <Grid x:Name="LayoutRoot" Margin="2">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <Image x:Name="imgCurrentPhoto" Stretch="Uniform" Grid.Column="0" Grid.Row="0" 
Grid.RowSpan="3"/> <StackPanel x:Name="spPhotos" Background="#CC646464" Opacity="0.8" Orientation="Horizontal" VerticalAlignment="Bottom" HorizontalAlignment="Left" Width="800" Height="130" Grid.Row="2" Grid.Column="0"
MouseEnter="spPhotos_Enter"
MouseLeave="spPhotos_Leave" > <Border CornerRadius="5" BorderThickness="3" BorderBrush="White"
Margin="5" Height="120" Width="60"> <StackPanel Orientation="Vertical" Height="120"> <Image x:Name="imgPlay" Source="Play.png" Width="40" Stretch="Uniform"
Margin="0,5,0,0" VerticalAlignment="Top" /> <Image x:Name="imgPause" Source="Pause.png" Width="40" Stretch="Uniform"
Margin="0,25,0,0" /> </StackPanel> </Border> <Image Source="PanLeft.png" Width="50" Stretch="Fill" Height="120"
MouseLeftButtonDown="PanLeft_MouseLeftButtonDown"/> <ScrollViewer x:Name="svThumbs" HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden" Width="600" BorderBrush="{x:Null}" > <StackPanel x:Name="spThumbs" Orientation="Horizontal" Margin="-5,-5,0,0" > </StackPanel> </ScrollViewer> <Image Source="PanRight.png" Width="50" Stretch="Fill" Height="120"
MouseLeftButtonDown="PanRight_MouseLeftButtonDown"/> </StackPanel> <StackPanel x:Name="spTitle" Background="#CC646464" Opacity="0.8" Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Left" Width="800" Height="30" Grid.Row="0" Grid.Column="0"
MouseEnter="spPhotos_Enter"
MouseLeave="spPhotos_Leave" > <StackPanel x:Name="spTitleText" Orientation="Horizontal" Margin="5"> </StackPanel> </StackPanel> </Grid> </controls:ChildWindow>

…but I have changed the style and added storyboards in Expression Blend in order to show the title and thumbnails when you mouse over the regions and hide them when you don’t:

image

 

Calling the Web Service

Remember when we created the additional controls in our dashboard we also created a class to show or hide the photos. When activated this element adds a handler for the ViewChangeEnd event and queries our database for the first time.

The ViewChangeEnd event calls our web service asynchronously and adds a handler that processes the result.

Public Shared Sub MyMap_ViewChangeEnd(ByVal sender As Object, ByVal e As MapEventArgs)
        Dim map As Map = DirectCast(sender, Map)
        Dim bounds As LocationRect = map.TargetBoundingRectangle

        AddHandler svc.GetClusterInViewCompleted, AddressOf svc_GetClusterInViewCompleted
        svc.GetClusterInViewAsync(bounds.Northwest.Latitude, _
bounds.Northwest.Longitude, bounds.Southeast.Latitude, _
bounds.Southeast.Longitude, CInt(map.TargetZoomLevel), mapWidth, mapHeight) End Sub

When we process the result we first delete all pins in the layer before we loop through the items in our list-object and create new pins. In the tag of each pin we add the name of the photo and also any other information that we want to show in the title. We also add a handler that fires whenever we click on the pin.

    Public Shared Sub svc_GetClusterInViewCompleted(ByVal sender As Object, _
ByVal e As GetClusterInViewCompletedEventArgs) slPhotos.Children.Clear() If e.Error Is Nothing Then For i = 0 To e.Result.Count - 1 Dim myPin As New Pushpin myPin.Location = New Location(e.Result(i)._Lat, e.Result(i)._Lon) myPin.Tag = e.Result(i)._Names slPhotos.Children.Add(myPin) AddHandler myPin.MouseLeftButtonDown, AddressOf PhotoShow Next Else MessageBox.Show(e.Error.Message) End If End Sub

When we click on the pin we split its tag into a string-array the element 0 and all items with an even number contain the file-name of the photo (without the extension (.jpg). All odd-number elements contain the information that we want to show in the title.

image

Now we cycle through this string array and create child elements for our Photo-Viewer. The thumbnail images will have a handler attached that allows us to click on it in order to display a larger image in the main window and and set the title.

    Public Shared Sub PhotoShow(ByVal sender As Object, _
ByVal e As System.Windows.Input.MouseButtonEventArgs) cwPhotoShow.spThumbs.Children.Clear() cwPhotoShow.spTitleText.Children.Clear() Dim myPin As Pushpin = DirectCast(sender, Pushpin) Dim myNameArray() As String = Split(myPin.Tag.ToString, ",") For i = 0 To (myNameArray.Length / 2) - 1 If i = 0 Then cwPhotoShow.imgCurrentPhoto.Source = New BitmapImage( _
New Uri(baseURL + "/digicam/h600/" + myNameArray(i) + ".jpg", UriKind.Absolute)) Dim myTitleTxt As New TextBlock With { .Text = Replace(myNameArray(i * 2 + 1), "|", ", "), .Foreground = New SolidColorBrush(Colors.White), .HorizontalAlignment = Windows.HorizontalAlignment.Center} cwPhotoShow.spTitleText.Children.Add(myTitleTxt) End If Dim myImg As New Image With { .Source = New BitmapImage( _
New Uri(baseURL + "/digicam/h100/" + myNameArray(i * 2) + ".jpg", _
UriKind.Absolute)), .Stretch = Stretch.None, .Tag = myNameArray(i * 2 + 1)} AddHandler myImg.MouseLeftButtonDown, _
AddressOf BM_SL_PhotoMap.PhotoShow.Thumb_MouseLeftButtonDown Dim myLocArray() As String = Split(myNameArray(i * 2 + 1), "|") Dim myTxt As New TextBlock With { .Text = myLocArray(0), .Foreground = New SolidColorBrush(Colors.White), .HorizontalAlignment = Windows.HorizontalAlignment.Center} Dim mySP As New StackPanel With { .Orientation = Orientation.Vertical, .VerticalAlignment = Windows.VerticalAlignment.Center} mySP.Children.Add(myTxt) mySP.Children.Add(myImg) Dim myBorder As New Border With { .CornerRadius = New CornerRadius(5), .BorderThickness = New Thickness(3), .BorderBrush = New SolidColorBrush(Colors.White), .Margin = New Thickness(5), .Height = 120} myBorder.Child = mySP cwPhotoShow.spThumbs.Children.Add(myBorder) Next BM_SL_PhotoMap.PhotoShow.numPhotos = cwPhotoShow.spThumbs.Children.Count cwPhotoShow.Show() End Sub

Finally we add a little bit of code to the Photo-Viewer in order to scroll through the thumbnails, display the photo in the main window and also provide a slide-show function. We won’t go into more detail here but it’s all in the sample code and you can explore it for yourself.

Well that brings us to an end of part 2. The application is ready for your local environment.

image

You can have a look at it here and download the sample code here.

If you want to learn more about publishing the application to Windows Azure have a look at part 3.

Clustering with the Bing Maps Silverlight Control – Part 1

Introduction

There may be very good reasons why you don’t want to cluster your points of interest on a map e.g. if you want to show the density of points in a certain area. An example for this is the Bing Maps World Tour which has been developed by our partner Earthware and which visualizes the highlights of the latest Bing Maps data updates.

image

The downside of this is though that the map looks cluttered and you can’t access the points at the bottom of the pile anymore. In addition the number of points that you render on a map also affects the performance. The performance aspect is certainly much more relevant for the AJAX control since the Silverlight performance in general is much better but after a few thousand points you might reach a level where the user experience suffers. The Bing Maps AJAX control has client-side clustering build into the API and in a previous post I have compared the performance of client- and server-side clustering for the AJAX control.

The Bing Maps Silverlight control does not provide build-in clustering and hence we will have a look mainly at the clustering but we will also:

In this example we will build a photomap. On low zoom-levels, i.e. when we look at the whole world, all the photos for let’s say Sardinia are represented by a single pushpin…

image

…but we still have access to all the photos that are represented by this pin:

image

On higher zoom-levels we break up the cluster and show accurate positions for the photos. In the screenshot below you also see, that we extended the build-in navigation control with some custom elements to show and hide the photo-layer, toggle the mini-map and toggle full-screen mode.

image

You can see the application here and download the sample code here

Note: You will have to enter your own Bing Maps Key in the MainPage.xaml and modify the connection string in the web.config

 

What we need

 

The Concept

Before we actually start coding let’s hold on for a sec and think about the concept. The Bing Maps Silverlight control supports a variety of events. Since we don’t want to load all the photos but only those that are in the current map view we use these events to determine the bounding box and a couple of other parameters for our database query whenever we finish zooming or panning the map. At a first glance it might appear that the event TargetViewChanged might be appropriate but that would be a mistake since the target view changes with the zoom-level – so far so good – but also with every frame when you pan the map. That would create quite a lot of database queries and we certainly want to avoid that. More appropriate is the ViewChangeEnd event. We will add a handler for this event to the map and call a web service asynchronously. The web service will query our database, create a cluster of points and return a List-object that we can then process in our Silverlight application.

image

In order to cluster the photos (or points of interest) we will create in the web service a grid – let’s say with a width of 40 pixel – and determine the photos that are within each grid-cell. For each grid-cell that has one or more photo in it we will return a pin and tag it with some ID’s that identify the individual photos.

image

Once the Silverlight application receives the response we will process the data by adding pins to the map and dynamically creating child-elements for our photo-viewer. In this example we will do it by adding thumbnail images to a StackPanel in the ChildWindow. OK, let’s do it.

 

The Local Database

The database will be quite simple. We just have one table where me maintain the meta-data for our photos. The script for the table is listed below.

CREATE TABLE [Photos](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [Lat] [float] NULL,
    [Lon] [float] NULL,
    [Title] [varchar](250) NULL,
    [Name] [varchar](50) NULL,
    [Date] [date] NULL,
    [Loc1] [varchar](250) NULL,
    [Loc2] [varchar](250) NULL,
    [Loc3] [varchar](250) NULL,
 CONSTRAINT [PK_Photos] PRIMARY KEY CLUSTERED 
(
    [id] ASC
) ON [PRIMARY]
) ON [PRIMARY]

We need columns for the latitudes and longitudes of the locations where the photo was taken, the name of the file (the part before the extension .jpg) and optionally some more information that describe the location.

image

The photos themselves are accessible through a web server and for starters we’re going to use our local Internet Information Service to create a virtual directory that points to the location of the photos.

 

The Web Service

Let’s first create the web service that accesses the database. In our Visual Studio we create a new Silverlight application:

image

And choose to host the application in a new website.

image

In the web-project we create a new Silverlight-enabled WCF Service. Let’s call it svcPhotos.

image

If you consider using Windows Azure you must be aware of a bug that really puzzled me for a while. Everything worked well when I had my stand-alone application but once I wanted to use it within a Windows Azure project – no matter if it as in the development fabric or in the live environment – I received a weird error message: “cannot be processed at the receiver, due to an AddressFilter mismatch at the EndpointDispatcher. Check that the sender and receiver's EndpointAddresses agree.”. Since I was actually re-writing the endpoint in my code I was pretty sure that the address was correct and finally I found out that the error can be resolved by adding the following ServiceBehaviour:

image

In our service we create a DataContract that describes the list-items in the list-object we want to return as well as an OperationsContract that queries the database, clusters the points and returns the list-object. The OperationsContract will take as input-parameters

  • latitudes and longitudes of the bounding box of the current map view
  • The width and height of the map in the browser
  • and the zoom-level

For the clustering we will also nee some helper functions that allow us to convert latitudes and longitudes into pixel coordinates. These functions are explained in more detail here. The full code of the web service is listed below:

Imports System.ServiceModel
Imports System.ServiceModel.Activation
Imports System.Data.SqlClient
Imports System.Runtime.Serialization
Imports System.Globalization

<ServiceContract(Namespace:="svcPhotos")>
<ServiceBehavior(AddressFilterMode:=AddressFilterMode.Any)>
<AspNetCompatibilityRequirements(RequirementsMode:=AspNetCompatibilityRequirementsMode.Allowed)>
Public Class svcPhotos
    'Constants for the Clustering
    '(addressable area in Bing Maps)
    Private Const MinLatitude As Double = -85.05112878
    Private Const MaxLatitude As Double = 85.05112878
    Private Const MinLongitude As Double = -180
    Private Const MaxLongitude As Double = 180

    'Grid-Size
    Private Const GridSize As Integer = 40

    'Clips a number to the specified minimum and maximum values
    Private Shared Function Clip(ByVal n As Double, ByVal minValue As Double, _
ByVal maxValue As Double) As Double Return Math.Min(Math.Max(n, minValue), maxValue) End Function 'Determine the offset off the map Public Shared Function Offset(ByVal lvl As Integer) As UInt32 Return 256 << lvl End Function 'Convert Latitude and Longitude to VEPixel Public Shared Sub LatLongToPixel(ByVal latitude As Double, ByVal longitude As Double, _
ByVal lvl As Integer, ByRef pixelX As Integer, ByRef pixelY As Integer) latitude = Clip(latitude, MinLatitude, MaxLatitude) longitude = Clip(longitude, MinLongitude, MaxLongitude) Dim x As Double = (longitude + 180) / 360 Dim sinLatitude As Double = Math.Sin(latitude * Math.PI / 180) Dim y As Double = 0.5 - Math.Log((1 + sinLatitude) / (1 - sinLatitude)) / (4 * Math.PI) Dim mapSize As UInt32 = Offset(lvl) pixelX = CType(Clip(x * mapSize + 0.5, 0, mapSize - 1), Integer) pixelY = CType(Clip(y * mapSize + 0.5, 0, mapSize - 1), Integer) End Sub <OperationContract()> _ Public Function GetClusterInView(ByVal NWlat As Double, ByVal NWlon As Double, _
ByVal SElat As Double, ByVal SELon As Double, ByVal lvl As Integer, _
ByVal mapWidth As Integer, ByVal mapHeight As Integer) As List(Of ClusterPoint) 'set culture to en-UK to avoid potential problems with decimal-separators System.Threading.Thread.CurrentThread.CurrentCulture = _
System.Globalization.CultureInfo.CreateSpecificCulture("en-UK") 'Set up a grid for the Clustering Dim numXCells = CInt(Math.Ceiling(mapWidth / GridSize)) Dim numYCells = CInt(Math.Ceiling(mapHeight / GridSize)) Dim numCells As Integer = numXCells * numYCells - 1 Dim gridCells()() As Object = New Object(numCells)() {} 'Determine PixelX and PixelY of upper left corner Dim ulTotalX As Integer Dim ulTotalY As Integer LatLongToPixel(NWlat, NWlon, lvl, ulTotalX, ulTotalY) 'Query database(s) and create JavaScript Dim poiTotalX As Integer Dim poiTotalY As Integer Dim poiMapX As Integer Dim poiMapY As Integer Dim settings As ConnectionStringSettings 'Connection String is set in web.config

settings = ConfigurationManager.ConnectionStrings("TalkingDonkeyLocal") Dim myConn As New SqlConnection(settings.ConnectionString) myConn.Open() Dim myQuery As String = "SELECT Lat, Lon, Name, Loc1, Loc2, Loc3, Date “ _
+ ”FROM Photos WHERE (Lat BETWEEN "
+ SElat.ToString + " AND " _
+ NWlat.ToString + ") AND (Lon BETWEEN " + NWlon.ToString _
+ " AND " + SELon.ToString + ")" Dim myCMD As New SqlCommand(myQuery, myConn) Dim myReader As SqlDataReader = myCMD.ExecuteReader() While myReader.Read() 'Determine PixelX and PixelY of POI LatLongToPixel(myReader(0), myReader(1), lvl, poiTotalX, poiTotalY) poiMapX = poiTotalX - ulTotalX poiMapY = poiTotalY - ulTotalY 'Populate the array with clustered pins For x = 0 To numXCells - 1 If (x * GridSize <= poiMapX) And (poiMapX < (x + 1) * GridSize) Then For y = 0 To numYCells - 1 If (y * GridSize <= poiMapY) And (poiMapY < (y + 1) * GridSize) Then Dim myClusteredPin(4) As Object If gridCells(x * y) Is Nothing Then myClusteredPin(0) = 1 myClusteredPin(1) = myReader(0) myClusteredPin(2) = myReader(1) myClusteredPin(3) = myReader(2).ToString + "," _
+ myReader(3).ToString + "|" + myReader(4).ToString _
+ "|" + myReader(5).ToString + "|" _
+ CDate(myReader(6)).ToString("d", New CultureInfo("en-GB")) Else myClusteredPin = gridCells(x * y) myClusteredPin(0) = myClusteredPin(0) + 1 myClusteredPin(3) = myClusteredPin(3) + "," _
+ myReader(2).ToString + "," + myReader(3).ToString _
+ "|" + myReader(4).ToString + "|" + myReader(5).ToString + "|" _
+ CDate(myReader(6)).ToString("d", New CultureInfo("en-GB")) End If gridCells(x * y) = myClusteredPin End If Next End If Next End While myReader.Close() myConn.Close() 'Create the pins Dim myPins As New List(Of ClusterPoint) For i = 0 To numCells If gridCells(i) IsNot Nothing Then Dim myClusteredPin = gridCells(i) Dim myPin As New ClusterPoint(myClusteredPin(1), myClusteredPin(2), _
myClusteredPin(3)) myPins.Add(myPin) End If Next Return myPins End Function End Class <DataContract()> _ Public Class ClusterPoint <DataMember()> _ Private _Lat As Double Public Property Lat() As Double Get Return _Lat End Get Set(ByVal value As Double) _Lat = value End Set End Property <DataMember()> _ Private _Lon As Double Public Property Lon() As Double Get Return _Lon End Get Set(ByVal value As Double) _Lon = value End Set End Property <DataMember()> _ Private _Names As String Public Property Names() As String Get Return _Names End Get Set(ByVal value As String) _Names = value End Set End Property Public Sub New(ByVal _Lat As Double, ByVal _Lon As Double, ByVal _Names As String) Lat = _Lat Lon = _Lon Names = _Names End Sub End Class

 

November 21

Bing Maps – Embedded Maps

Developing with Bing Maps is great fun but not everybody wants to fire up his Visual Studio to get a map on his website. With the latest launch – Chris Pendleton blogged about it here, here, here and here – we also introduced embedded maps. Embedded maps in it’s most simple form can just be generated from our consumer facing site http://bing.com/maps. Just navigate the map to the place that you want to show and then click on the “Share” button.

image

This will bring up a dialog where you can copy a link that will get you back to http://bing.com/maps and recreate the current map-view. That is not really new, it has been there before but there is now also another area where you can copy a piece of HTML-code that you can paste into your website in order to embed a static or interactive map.

image

The link “Customize and preview” brings up another dialog that allows you to further customize this piece of HTML-code:

image

As you can see this gives you plain maps with the roadmap or aerial images but in order to see the nice Bird’s Eye view you have to use the link underneath the map that get’s you back to http://bing.com/maps.

In our Bing Maps AJAX SDK we have however now also a couple of changes and one of these allows you to create more comprehensive embedded maps by using additional URL-parameters.

Parameter Name Description

mapMode

The style of the map. Valid values are: Road, Aerial, AerialWithLabels, Birdseye, BirdseyeWithLabels

zoomLevel

An int indicating a valid zoom level for the map style.

center

Two doubles separated by an underscore that are the latitude and longitude values of the coordinate of the center of the map.

heading

A double specifying the orientation of the map in degrees.

pushpins

Two doubles separated by an underscore that are the latitude and longitude values of the coordinate of the pushpin. Multiple pushpin locations are separated by a ~.

culture

A valid culture string. The default value is “en-us”.

So this iFrame for example…

<iframe height="400" 
marginheight="0"
src="http://dev.virtualearth.net/embeddedMap/v1/ajax/Birdseye?
zoomLevel=19&amp;
center=51.46130379870268_-0.9260680852445592&amp;
heading=180"
frameborder="0"
width="100%"
marginwidth="0"
scrolling="no">
</
iframe>

…gives as a nice Birds Eye view of the Microsoft office in Reading facing south:

image

Since we also launched a Bing Maps Silverlight control we have the same approach for embedded maps available for Silverlight too. Here is the iFrame:

<iframe height="400" 
marginheight="0"
src="http://dev.virtualearth.net/embeddedMap/v1/silverlight/Aerial?
zoomLevel=17&amp;
center=51.461600455334924_-0.9259327501058578&amp;
pushpins=51.461550321682765_-0.9256913512945175"
frameborder="0"
width="100%"
marginwidth="0"
scrolling="no">
</
iframe>

and here is the result:
image

November 20

Bing Maps Silverlight Control & Photosynth

Last week we launched a new wave of Bing Maps. My esteemed colleague Chris Pendleton has blogged about some of the changes here, here, here and here. One of the main announcements has been the launch of the Bing Maps Silverlight Control v1. You will find the interactive SDK here, the full reference on MSDN here and the download here. This control has been available as a Community Technology Preview (CTP) for quite a while but those of you who have been waiting for a fully supported version before getting involved may not be too familiar with it yet. If you did work with the CTP please be aware that it is time-bombed and will stop working by the end of the year; so please consider updating your application soon.

If you have been working with the Bing Maps AJAX Control before you may actually be missing a few things that came in handy if you wanted to display additional information like for a example a Photosynth collection. This was quite simple in the AJAX control since you could simply generate an iFrame in Photosynth and just add it to a VEShape-object using the VEShape.SetDescription-method. You can look at a sample here and download the source here).

Since pushpins in the Silverlight control don’t have an info-box like the ones in the AJAX-control the approach here is quite different but it still works very well. The major obstacle appear to be that we cannot directly integrate HTML-code into our Silverlight application. The solution is actually to use the HTML Bridge in order to lay a HTML DIV-element on top of the Silverlight control and arrange it appropriately. Of course you can write everything from the scratch but the guys from divelements have created a Silverlight-Control HtmlHost for us that you can download for free and use to add and control HTML content directly from your Silverlight application. Let’s have a crack at it. For this walkthrough we will need

First we create a new Silverlight application and add references to our Bing Maps Silverlight Control

image

In the MainPage.xaml we add a reference to our Bing Maps Silverlight control…

xmlns:m="clr-namespace:Microsoft.Maps.MapControl;assembly=Microsoft.Maps.MapControl"

 

…and the the map itself with a pushpin at the location where we our Photosynth collection is. We also attach an event to the pushpin that fires when we click on it.

<m:Map CredentialsProvider="YOUR BING MAPS KEY"
       Mode="AerialWithLabels"
       Center="32.362980181127874,-64.71483707427978"
       ZoomLevel="17">
       <m:Pushpin Name="pinMartelloTower"
                  Location="32.362980181127874,-64.71483707427978"
                  MouseLeftButtonDown="pinMartelloTower_MouseLeftButtonDown"/>
</m:Map>

 

If you had done something like this with the CTP before you will notice a few differences:

  • The namespace for the Bing Maps Silverlight Control has changed
  • We now need to provide a CredentialsProvider in order to authenticate. This is basically the Bing Maps Key that we generated before.
  • We have now a Pushpin-object that we can use as a child of a map

In my sample here I chose to bring up the Photosynth collection as part of a ChildWindow. A ChildWindow is a Silverlight control that comes with the Silverlight Toolkit. So let’s add a ChildWindow as new object to our Silverlight-project. I call it Photosynth. The trouble with a ChildWindow and HTML-DIV-elements on top is that the ChildWindow has an animation and explodes when you open it. If you would just add the HTML-DIV-element when the ChildWindow opens it would appear in the bottom-right quadrant of the screen. In order to avoid this we can simply remove the animation by changing the template in Expression Blend. In Visual Studio just right-click on the ChildWindow and select “Open in Expression Blend” from the context menu.

image

In Expression Blend right-click on the ChildWindow-object and select “Edit Template” => “Edit a Copy”.

image

Now we switch to code-view and remove the whole VisualStateManager that handles the StoryBoards for opening and closing the window. By default this would start around line 119:

image

While we’re at it we can optionally make a few more changes to adjust the design:

image

Now we can leave Expression Blend and go back to Visual Studio. In order to add the HtmlHost from divelements right-click on the Toolbox, select “Choose Items” from the context menu and add the Divelements.SilverlightTools.dll from the location where you extracted the download.

image

Now drag&drop the control on your ChildWindow. This will automatically add the required namespace you only need to add a Name-property to the HtmlHost. Your XAML-should now look like this

<controls:ChildWindow x:Class="SilverlightApplication2.Photosynth"
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
           xmlns:my="clr-namespace:Divelements.SilverlightTools;assembly=Divelements.SilverlightTools"  
           Width="400" Height="300"
           Title="Photosynth">
    <controls:ChildWindow.Resources>
…
</controls:ChildWindow.Resources
> <controls:ChildWindow.Style> <StaticResource ResourceKey="ChildWindowStyle"/> </controls:ChildWindow.Style>
<Grid x:Name="LayoutRoot" Margin="2"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <my:HtmlHost Name="htmlHost"></my:HtmlHost> </Grid> </controls:ChildWindow>

Finally we can start coding. The only code we need is actually in the MainPage.xaml.vb. When we initialize the page we also add a handler which resizes the ChildWindow whenever the size of the browser window changes.

When we click on the pushpin we set the title for the ChildWindow as well as the source for the HtmlHost. In this case we just add the iFrame which we can copy directly from Photosynth…

image

…and make to minor changes:

  • we change the width and height of the iFrame to 100%
  • we set the URL-parameter delayLoad from true to false. By default the Photosynth viewer would show a static image and only load the collection after you clicked on this image. By changing this parameter we load the collection immediately.
Imports System.Windows.Browser

Partial Public Class MainPage
    Inherits UserControl

    Public myHeight As Integer
    Public myWidth As Integer

    Private WithEvents cwPhotosynth As New Photosynth

    Public Sub New()
        InitializeComponent()

        AddHandler LayoutRoot.SizeChanged, AddressOf Page_SizeChanged
    End Sub

    Private Sub Page_SizeChanged(ByVal sender As Object, ByVal e As SizeChangedEventArgs)
        myWidth = CInt(e.NewSize.Width)
        myHeight = CInt(e.NewSize.Height)

        cwPhotosynth.Width = e.NewSize.Width * 0.8
        cwPhotosynth.Height = e.NewSize.Height * 0.8
    End Sub

    Private Sub pinMartelloTower_MouseLeftButtonDown _
(ByVal sender As System.Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) cwPhotosynth.Title =
"Martello Tower" cwPhotosynth.htmlHost.SourceHtml = _
"<iframe frameborder='0' src='http://photosynth.net/embed.aspx?cid=ba12ab48-6899-4d7f-b28c-624f5f7ff4f0” + _
&delayLoad=false&slideShowPlaying=false' width='100%' height='100%'></iframe>"
cwPhotosynth.Show()
End Sub End Class

Well that’s already it:

image

You can have a look at the sample here and download the source code here.

Note: if you download the sample code you need to create a Bing Maps Key and enter it into the XAML in order to load the Bing Maps. Also note that the solution was build with Visual Studio 2010 Beta 2, however all methods work as well in Visual Studio 2008 SP1

 

Visitors

The Latest News From

Chris Pendleton

Loading...Loading...

Richard Brundritt

Loading...Loading...

VE3D-Team

Loading...Loading...

Ed Katibah

Loading...Loading...
No folders have been shared yet.