Spatial-Enabled Windows Azure (Part 2)

Step 4: Build the basic Bing Maps Application with the Tile Layer

We start by creating a new Cloud Service Solution. A Web Cloud Service will do for this purpose.

image_thumb9

For our development and debugging we will use the development fabric but we will not use the development storage (I actually didn’t manage to get the development table storage to work with binary data types). Hence we can disable the start of development storage services in the properties of our Azure project.

image_thumb11

Next we add a new Silverlight application to our WebRole-Project:

image_thumb2

Let’s also create a test page and make sure that Silverlight debugging is enabled:

image_thumb4

To this project we add a our Bing Maps Silverlight Control as additional reference. The DLL is not in the Global Assembly Cache so you need to browse for it. If you installed the Bing Maps Silverlight Control in the default location you’ll find the DLL in the folder “C:Program FilesMicrosoft Virtual Earth Silverlight Map ControlCTPLibraries”.

image_thumb15

In the Page.xaml we add now the reference to our map control:

<UserControl x:Class="_01_SL_Charts.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:m="clr-namespace:Microsoft.VirtualEarth.MapControl;assembly=Microsoft.VirtualEarth.MapControl" >

 

…the map itself…

<m:Map HorizontalAlignment="Stretch" VerticalAlignment="Stretch" x:Name="MyMap"  
Center="0, 0" ZoomLevel="2" Mode="Road" />

….and other design components as you need it such as checkboxes that allow us to switch the tile layers on and off.

<StackPanel>
    <CheckBox x:Name="cbGDP" Click="cbGDP_Click" >
        <TextBlock Text="GDP"></TextBlock>
    </CheckBox>
    <CheckBox x:Name="cbCapita" Click="cbCapita_Click" >
        <TextBlock Text="GDP per Capita"></TextBlock>
    </CheckBox>
</StackPanel>

At the end of our user control we can also import other user controls that we may want to use as pop-ups. In this example we use user-controls as pop-ups for example to explain the colour codes. Let’s assume we have already created 2 new Silverlight user controls in our Silverlight project. Both of them have show- and close-functions in the code behind. We can import them now for use in our Page.xaml:

<mychart:LegendGDP x:Name="legendGDPPopup" Visibility="Collapsed" />
<mychart:LegendCapita x:Name="legendCapitaPopup" Visibility="Collapsed" />

In my sample I have also added a mini map and buttons to toggle this mini map as well as one to toggle fullscreen mode. You will find the complete code in the sample code at the end of this blog post.

In the extract of the XAML above you see that we define the initial centre-point, zoom-level and map-style directly in the XAML. You also see that we have prepared for 2 functions that will fire when we check or uncheck the checkboxes. So let’s have a look at the code behind in the Page.xaml.vb-file. You will see that we have attached a handler that captures when the target-view of our map changes. That actually happens whenever we pan or zoom the map. when this event occurs we check the target zoom-level and since we rendered the tile layer only for levels 1-7 we make sure that we can’t zoom any closer than that. Next we create functions that overlay our own custom tile layer when we check the checkbox and hides them again when we uncheck it. We also show and hide the legend as part of these functions.

Imports Microsoft.VirtualEarth.MapControl

Partial Public Class Page
    Inherits UserControl

    ‘Tile Layer
  
Public gdpLayer As MapTileLayer
    Public capitaLayer As MapTileLayer

    Public Sub New()
        InitializeComponent()

        ‘ Set up Main Map Events
      
AddHandler MyMap.TargetViewChanged, AddressOf MyMap_TargetViewChanged
    End Sub

    Private Sub MyMap_TargetViewChanged(ByVal sender As Object, ByVal e As Microsoft.VirtualEarth.MapControl.MapEventArgs)
        If MyMap.TargetView.ZoomLevel > 7 Then
          
MyMap.SetView(MyMap.Center, 7)
        End If
    End Sub

    Private Sub cbGDP_Click(ByVal sender AsSystem.Object, ByVal e As System.Windows.RoutedEventArgs)
        If cbGDP.IsChecked = True Then
          
‘ The bounding rectangle that the tile overlay can be placed within.
          
Dim boundingRect As New LocationRect(New Location(90, -180), New Location(-90, 180))

            ‘ Creates a new map layer to add the tile overlay to.
          
gdpLayer = New MapTileLayer()

            ‘ The source of the overlay.
          
Dim tileSource As New LocationRectTileSource()
            tileSource.UriFormat = "http://hannesvestorage.blob.core.windows.net/vetiles/GDP/{0&#125;.png"
          
‘ The zoom range that the tile overlay is visibile within
          
tileSource.ZoomRange = New Range(Of Double)(1, 7)
            ‘ The bounding rectangle area that the tile overaly is valid in.
          
tileSource.BoundingRectangle = boundingRect

            gdpLayer.TileSources.Add(tileSource)

            gdpLayer.Opacity = 0.7
            MyMap.Children.Add(gdpLayer)
            legendGDPPopup.Show()
        Else
          
MyMap.Children.Remove(gdpLayer)
            legendGDPPopup.Close()
        End If
    End Sub

End Class

Well that’s it for the tile layer. At this point we can run the project and see our statistical information as a thematic Bing Map using the Silverlight control.

Step 5: Implement spatial queries from our Bing Maps application to the Windows Azure Table Storage

First we add a few references to this WebRole project: We need the same StorageClient.dll we used in Step 3 as well as the System.Data.Services.Client from the GAC to access the Windows Azure Table Storage but we also need to Microsoft.SqlServer.Types for the spatial queries. You will have the latter already in your GAC if you have SQL Server 2008 installed otherwise download and install either SQL Server 2008 or the SQL Server CLR Types from the Feature Pack. Make sure both the StorageClient and the SqlServer.Types are copied locally so that they will be packaged and uploaded to Windows Azure.

image_thumb24

Now here comes the first tricky bit. The spatial libraries in SQL Server consists actually of an managed and an unmanaged part. We need both of them but with the approach mentioned above we only get the managed piece. Since the other part is unmanaged we could simply copy the SqlServerSpatial.dll from C:WindowsSystem32 to the bin-directory of our WebRole, e.g. C:UsersjkebeckDocumentsVisual Studio 2008ProjectsBM-Azure-01BM-Azure-01_WebRolebin.

However keep in mind that Windows Azure runs on 64-bit processors. If your system is like my laptop on 32-bit you need to download the 64-bit version of the SQL Server CLR Types from the feature pack extract the *.msi using a command such as

msiexec /a SQLSysClrTypes_64bit.msi /qb TARGETDIR="C:MyFolder"

and copy the SqlServerSpatial.dll from there.

Well, by default Windows Azure does not allow you to run native code but since the latest update you can now enable this feature. In order to do so open the ServiceDefinition.csdef in the Azure project and set the enableNativeCodeExecution to true.

image_thumb29

Next we need to consider that at least during the development we do cross-domain calls from our Silverlight application in the local development fabric to the Windows Azure Table Storage. Hence we’ll need a crossdomain.xml file. So let’s just create a new xml-file with that name and enter the following:

<access-policy>
  <cross-domain-access>
    <policy>
      <allow-from http-request-headers="*">
        >
        <domain uri="*"/>
      </allow-from>
      <grant-to>
        <resource path="/" include-subpaths="true"/>
      </grant-to>
    </policy>
  </cross-domain-access>
</access-policy>

Now we add the same class GDP.vb that we created in Step 3 as an existing item to our web role. Remember, we need this class to describe the table and entities in our Windows Azure Table Storage.

In our web.config we add next the section for our Windows Azure credentials

<appSettings>
  <add key="AccountName" value="YOUR ACCOUNT NAME" />
  <add key="AccountSharedKey" value="YOUR SHARED KEY" />
  <add key="TableStorageEndpoint" value="http://table.core.windows.net" />
</appSettings>

Finally we get to look at some code again. To connect to the Windows Azure Table Storage from our Silverlight application we create a new Silverlight-enabled WCF Service in our WebRole-project.

image_thumb17

In this WCF service we will receive the latitude and longitude of the location we clicked on as input parameters and then construct a geometry of type POINT from it. Now we loop through all the records in our Windows Azure table, retrieve the byte array describing our geometry and convert it into a GEOMETRY data type. Once we have this we can determine if the point that we clicked on is in this particular geometry. If so, we return the entity to the function that called the service.

Imports System.ServiceModel
Imports System.ServiceModel.Activation
Imports System.Data.SqlTypes
Imports Microsoft.SqlServer.Types

<ServiceContract(Namespace:="DataService")> _
<AspNetCompatibilityRequirements(RequirementsMode:=AspNetCompatibilityRequirementsMode.Allowed)> _
Public Class DataService

    <OperationContract()> _
    Public Function GetCountry(ByVal myLat As String, ByVal myLon As String) As GDPRecord
        'set culture to en-UK to avoid potential problems with decimal-separators
        System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.CreateSpecificCulture("en-UK")

        'Build geometry
        Dim myWKT As New SqlChars(New SqlString("POINT(" + myLon + " " + myLat + ")"))
        Dim myPoint As SqlGeometry
        myPoint = SqlGeometry.STGeomFromText(myWKT, 4326)

        'Query Azure table and compare geometries
        Dim myTable As New GDP
        For Each GDPRecord In myTable.GDPTable
            Dim b As Byte() = GDPRecord.Geom
            Dim g As SqlGeometry
            g = SqlGeometry.STGeomFromWKB(New SqlBytes(b), 4326)
            If g.STContains(myPoint) = 1 Then
                Return GDPRecord
                Exit For
            End If
        Next
    End Function

End Class

Once we have this service we can build the WebRole-project and add a Service Reference to our Silverlight project.

image_thumb31

This will not only create the reference but also a ServiceReferences.ClientConfig file. In this file you have to remove the tags for the transport-mode within the security tags so that the file reads similar to:

<configuration>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="BasicHttpBinding_DataService" maxBufferSize="2147483647"
                    maxReceivedMessageSize="2147483647">
                    <security mode="None"/>
                </binding>
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://dummy/DataService.svc" binding="basicHttpBinding"
                bindingConfiguration="BasicHttpBinding_DataService" contract="DataServiceReference.DataService"
                name="BasicHttpBinding_DataService" />
        </client>
    </system.serviceModel>
</configuration>

The address of the endpoint doesn’t actually matter. It will be different in the development fabric, different in the Windows Azure staging environment and different again in the Windows Azure production environment. Hence we are going to write the URL later in our code.

Step 6: Add a chart using the Microsoft Silverlight Toolkit

First we need to add the assembly as reference to our Silverlight project. If you installed the Silverlight Toolkit in the default directory you’ll find it in the path “C:Program FilesMicrosoft SDKsSilverlightv2.0ToolkitMarch 2009LibrariesSystem.Windows.Controls.DataVisualization.Toolkit.dll”. Again: make sure that you copy this assembly locally.

image_thumb33

Now we create a new Silverlight user control Chart.xaml in our Silverlight project and we will find the Chart in your Visual Studio Toolbox

image_thumb[1]

Let’s prepare the user control to be used as a popup from our main user control Page.xaml and add a couple of more controls to host the details:

<Grid x:Name="LayoutRoot" Width="245" >
    <Popup x:Name="popChart" VerticalAlignment="Stretch" Width="245" Margin="0,0,0,0" HorizontalAlignment="Stretch">
        <Border Width="245" BorderThickness="3,3,3,3" CornerRadius="10,10,10,10" 
BorderBrush="#FFFFFFFF" Background="#FFFFFFFF"> <StackPanel Orientation="Vertical" Margin="10,10,10,10" Canvas.ZIndex="0" Width="220"> <TextBlock x:Name="myName" Margin="5,0,0,0" ></TextBlock> <StackPanel Orientation="Horizontal" Margin="5,0,0,0"> <TextBlock Text="GDP (Mio USD): "></TextBlock> <TextBlock x:Name="myTotal" VerticalAlignment="Top"></TextBlock> </StackPanel> <StackPanel Orientation="Horizontal" Margin="5,0,0,0"> <TextBlock Text="GDP/Capita (USD): "></TextBlock> <TextBlock x:Name="myCapita" VerticalAlignment="Top"></TextBlock> </StackPanel> <StackPanel Orientation="Horizontal" Margin="5,0,0,0"> <TextBlock Text="Growth Rate (%): "></TextBlock> <TextBlock x:Name="myGrowth" VerticalAlignment="Top"></TextBlock> </StackPanel> <chart:Chart Height="310" Title="Percentage Added By" x:Name="MyPieChart" Width="220"> <chart:Chart.Background> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0"> <GradientStop Color="#FFFCAC34"/> <GradientStop Color="#FFFFFFFF" Offset="1"/> </LinearGradientBrush> </chart:Chart.Background> <chart:PieSeries IndependentValueBinding="{Binding Path=myName}" DependentValueBinding="{Binding Path=myValue}" LegendItemStyle="{StaticResource MyLegendItemStyle1}" StylePalette="{StaticResource MyStylePalette}"/> </chart:Chart> <TextBlock Margin="5,0,0,0" VerticalAlignment="Top" Text="Note: if the chart area is empty…"/> </StackPanel> </Border> </Popup> </Grid>

If you want to make some major changes in the style of the control, modify the tooltip, etc it is a bit painful but then the beauty of this type of control is that you aren’t being boxed and can do it after all. A good guide on advanced styling for the chart control of the Silverlight Toolkit using Expression Blend is here.

In the code behind we prepare functions to open and close the popup. In the opening function we want to be able to receive a couple of parameters and use them to display various information and add data points to the chart.

Imports System.Windows.Controls.DataVisualization.Charting
Imports System.Globalization

Partial Public Class Chart
    Inherits UserControl

    Public Sub New()
        InitializeComponent()
    End Sub

    Public Sub Close()
        popChart.IsOpen = False
        Me.Visibility = Windows.Visibility.Collapsed
    End Sub

    Public Sub Show(ByVal country As String, ByVal total As Double, ByVal capita As Double, ByVal growth As Double, ByVal agr As Double, ByVal ind As Double, ByVal man As Double, ByVal ser As Double)
        myName.Text = country
        myTotal.Text = CDbl(total).ToString("N1", CultureInfo.InvariantCulture)
        myCapita.Text = CDbl(capita).ToString("N1", CultureInfo.InvariantCulture)
        myGrowth.Text = CDbl(growth).ToString("N1", CultureInfo.InvariantCulture)
        Dim ps As PieSeries = MyPieChart.Series(0)
        Dim myData As New List(Of myDataClass)
        myData.Add(New myDataClass("Agriculture", agr))
        myData.Add(New myDataClass("Industry", ind))
        myData.Add(New myDataClass("Manufacturing", man))
        myData.Add(New myDataClass("Service", ser))
        ps.ItemsSource = myData
        popChart.IsOpen = True
        Me.Visibility = Windows.Visibility.Visible
    End Sub
End Class

Public Class myDataClass
    Private _myName As String
    Public Property myName() As String
        Get
            Return _myName
        End Get
        Set(ByVal value As String)
            _myName = value
        End Set
    End Property

    Private _myValue As Integer
    Public Property myValue() As Integer
        Get
            Return _myValue
        End Get
        Set(ByVal value As Integer)
            _myValue = value
        End Set
    End Property

    Public Sub New(ByVal _myName As String, ByVal _myValue As Integer)
        myName = _myName
        myValue = _myValue
    End Sub
End Class

All right, now let’s string it together. In our Page.xaml we reference this new popup for the chart


<mychart:Chart x:Name="chartPopup" Visibility="Collapsed" /> <mychart:LegendGDP x:Name="legendGDPPopup" Visibility="Collapsed" /> <mychart:LegendCapita x:Name="legendCapitaPopup" Visibility="Collapsed" /> <mychart:PleaseWait x:Name="waitPopup" Visibility="Collapsed"/> </Grid> </UserControl>

In the code behind, i.e. Page.xaml.vb we want to introduce a feature that allows us to double click on a location in the map and then:

  1. add a point to the map that marks the clicked location
  2. determine the location we clicked on and call the web service we created in step 5
  3. The response will be used as parameters when we open the chart and hand over the details

First we declare a new MapLayer in our class

'Chart Layer
Public chartLayer As MapLayer

In the Public Sub New we add a new handler that takes care of a double-click

AddHandler MyMap.MouseDoubleClick, AddressOf MyMap_MouseDoubleClick

The handler will first add the layer to the map if it doesn’t already exist and then add an icon on the clicked position. Then we dynamically build the URL of the endpoint for our web service and call it asynchronously with the latitude and longitude of the clicked location as parameters.

Private Sub MyMap_MouseDoubleClick(ByVal sender As Object, ByVal e As MapMouseEventArgs)
    'Add Layer for chart points
    If Not MyMap.Children.Contains(chartLayer) Then
        chartLayer = New MapLayer
        MyMap.Children.Add(chartLayer)
    End If

    chartLayer.Children.Clear()
    chartPopup.Close()

    Dim loc As Location = MyMap.ViewportPointToLocation(e.ViewportPoint)

    Dim image As New Image()
    image.Source = New BitmapImage(New Uri("/IMG/blue.png", UriKind.Relative))
    image.Stretch = Stretch.None
    Dim location As New Location(loc.Latitude.ToString, loc.Longitude.ToString)
    Dim position As PositionMethod = PositionMethod.Center
    chartLayer.AddChild(image, location, position)

    Dim wsURL As String = "http://" + HtmlPage.Document.DocumentUri.Host + _
":" + HtmlPage.Document.DocumentUri.Port.ToString + "/DataService.svc" Dim svc As New DataServiceClient() svc.Endpoint.Address = New ServiceModel.EndpointAddress(wsURL) AddHandler svc.GetCountryCompleted, AddressOf svc_GetCountryCompleted svc.GetCountryAsync(loc.Latitude.ToString, loc.Longitude.ToString) e.Handled = True End Sub

When we receive the response we hand the details over to the Chart user control and open it.

Private Sub svc_GetCountryCompleted(ByVal sender As Object, ByVal e As GetCountryCompletedEventArgs)
    chartPopup.Show(e.Result.Name, e.Result.Total, e.Result.Capita, _
e.Result.Growth, e.Result.Agri, e.Result.Ind, e.Result.Manu, e.Result.Serv) End Sub

And finally we’re done. We can publish our work to Windows Azure from the context menu of the Azure project.

image_thumb[5]

This is how it looks like

image_thumb[3]

You will find the sample live on Windows Azure here and the source code is here

(the source code has actually a couple of more samples from this site)

Advertisements
This entry was posted in Bing Maps. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s