geo2web.com

Review of GIS GPS GEO and MAPs technology

geo2web.com header image 2

Spatial-Enabled Windows Azure (Part 2)

June 25th, 2009 · No Comments · GEO, GIS and GEO technology, GPS, Maps

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 Files\Microsoft Virtual Earth Silverlight Map Control\CTP\Libraries”.

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}.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:\Windows\System32 to the bin-directory of our WebRole, e.g. C:\Users\jkebeck\Documents\Visual Studio 2008\Projects\BM-Azure-01\BM-Azure-01_WebRole\bin.

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 Files\Microsoft SDKs\Silverlight\v2.0\Toolkit\March 2009\Libraries\System.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)

Technorati Tags: Virtual Earth,Bing Maps,Windows Azure,SQL Server 2008,Safe FME,Silverlight,Silverlight Toolkit

Tags: ···