| Johannes's profileHannes's Virtual Earth B...BlogListsSkyDrive | Help |
|
July 01 Route Optimization in Bing Maps powered by OnTerra’s free Stop Optimization ServiceOnTerra is a Microsoft Partner specialized on tailored Bing Maps solutions and focussing on but not limited to tracking and fleet management. Recently they launched a free beta version of a Stop Optimization Service. The service allows you to send an unlimited list of stops for your route and receive a string with the order of the stops optimized for the shortest driving distance. Let’s have a quick look at how it works: Bing Maps supports out-of-the-box Multi-Waypoint Routing for up to 25 stops through the method VEMap.GetDirections. However, the routing algorithm processes the stops always in the order in which they appear in the array of locations. If we want to start for example a trip in the Microsoft Office in Reading and want to visit Swindon, Oxford, Maidenhead and Newbury before we return to the Microsoft office we have to know in which order we want to visit these cities. If we just send the list in the order mentioned above it will guide us from one location to the next in exactly this order and come up with a route that is 185 miles long and takes about 3 hours and 20 minutes of drive time. The free OnTerra Stop Optimization Service figures out in which order we should drive for the shortest distance. In the example above it will suggest that we go to Maidenhead first, then Oxford, Swindon and Newbury. This will save us 45 miles and about 40 minutes of drive time. That’s not bad at all but if you would like to use it for example as a dispatcher in a fleet management application you also need to consider the times when you can make a pickup or a delivery, you may want to optimize for shortest time rather than shortest distance or you may need to consider height and weight restrictions that apply to your trucks. This is not part of the free service but in addition to the free stop optimization, OnTerra also offers such advanced features for a fee. If you are interested in this type of advanced service contact routeopt@onterrasys.com for more details. To use the free stop optimization service you will need to register and apply for a token. It requires 3 parameters:
You see that we need to geocode the locations before we send them to the stop optimization service. In the sample application above I use the Bing Maps AJAX control and use the callback function for the VEMap.Find-method to concatenate a string with the locations as expected by the stop optimization service, e.g. “txtStop1,51.461179,-0.925943#txtStop2,51.561765,-1.781815#txtStop3,51.522375,-0.727256#txtStop4,51.405876,-1.325891#txtStop5,51.756205,-1.259490”. Now here is one thing so consider: The optimization service splits the locations-string whenever it finds the character ‘#’. Unfortunately there appears to be a bug(?) which doesn’t process the string correctly when you work with the full number of decimal digits that comes back from the Bing Maps geocoder. In order to work around this bug(?) we can truncate the number of decimal digits to 6. This does actually not have a noticeable impact on the precision of the result but solves our problem. Once we have our last location we call a JavaScript-function StopOpt which actually creates an AJAX-call //Build String for Route Optimization function AppendLocations(layer, resultsArray, places, hasMore, veErrorMessage) { i = i + 1; if (locations.length > 0) { locations=locations+"#" } locations = locations + "txtStop" + i + "," The AJAX-call goes to a web handler which will call the stop optimization service and hands over the locations-string as well as a parameter that indicates if we’re doing a roundtrip or a one-way-trip. The optimized order of the result is received as a string and we process it a bit before we call the VEMap.GetDirections method. function StopOpt() { //Build URL to call the server var url = "./06-StopOpt.ashx?"; url += "locations=" + locations; if (document.getElementById("cbRoundtrip").checked == true) { url += "&roundtrip=true" } else { url += "&roundtrip=false" } //Get the appropriate XMLHTTP object for the browser var xmlhttp = GetXmlHttp(); //if we have a valid XMLHTTP object if (xmlhttp) { xmlhttp.open("GET", url, true); // varAsynx = true //set the callback xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == 4) //4 is a success { //web service returns the optimized order of the stops var result = xmlhttp.responseText var stopArray = result.split(" >> "); var stops = new Array(); var order = "Order (Optimized):<br>"; for (var i = 0; i < stopArray.length; ++i) { order = order + document.getElementById(stopArray[i]).value + "<br>"; stops.push(document.getElementById(stopArray[i]).value); } if (document.getElementById("cbRoundtrip").checked == true) { order = order + document.getElementById("txtStop1").value; stops.push(document.getElementById("txtStop1").value) } var options = new VERouteOptions; options.RouteCallback = DistTime; document.getElementById("pOrder").innerHTML = order; map.GetDirections(stops, options); } } xmlhttp.send(null); } } Finally, here is our web handler that we have been calling with our AJAX-call and which in turn calls the OnTerra stop optimization service: Imports System.Web Imports System.Web.Services Imports BM_Azure_01_WebRole.OnTerra Public Class _06_StopOpt Implements System.Web.IHttpHandler Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest 'Get the URL-Parameters Dim locations As String = context.Request.Params("locations") Dim roundTrip As Boolean = CBool(context.Request.Params("roundtrip")) Dim token As String = "YOUR TOKEN" Dim svcOT As New OnTerra.OnTerraStopOptClient("basicEndPoint") Dim output As String output = svcOT.GetStopOpt(locations, roundTrip, token) context.Response.ContentType = "text/plain" context.Response.Write(output) End Sub ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable Get Return False End Get End Property End Class
June 25 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. 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. Next we add a new Silverlight application to our WebRole-Project: Let’s also create a test page and make sure that Silverlight debugging is enabled: 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”. 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" ….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 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. 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. 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. 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. 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. 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 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" 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 … 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:
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 + _ 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, _ And finally we’re done. We can publish our work to Windows Azure from the context menu of the Azure project. This is how it looks like 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 Spatial-Enabled Windows Azure (Part 1)Introduction I have previously blogged about Bing Maps and Windows Azure (Part 1: Introduction, Part 2: Accessing Blob Storage, Part 3: Accessing Table Storage) and since we brought together a mapping application with our operating system for the cloud this is already sort of spatial-enabling but now I want to go a step further. Now I would also like to use spatial data types and spatial functions as we have them in SQL Server 2008. That may sound a bit ambitious but in their infinite wisdom the SQL Server Spatial team has made the spatial data types and spatial functions available for external use in a separate library that comes with SQL Server 2008 but also separately with the SQL Server 2008 Feature Pack. To be more precise you find them in the package “SQL Server System CLR Types”. Well, that’s almost all I need and with a little tweaking I can use this library in a way that I can leverage the spatial data types and spatial functions within Windows Azure. For this walk-through I’m going to keep it simple. I will store a couple of country-boundaries in Well Known Binary (WKB) format together with business data in the Windows Azure Table Storage. The application will allow me to click on a a country in Bing Maps and retrieve the detailed information for the selected country similar to my previous blog post Data Visualization with Bing Maps. You might wonder why I don’t just you use the reverse-geocoder in Bing Maps or MapPoint Web Service to determine the country that contains the location I clicked on. Indeed you have a valid point. However, it is not very simple to retrieve the country through the reverse-geocoder in Bing Maps. In MapPoint Web Service it is much more straight forward since you can filter the response from the SOAP web service and get only the entities of type “Sovereign” which contain the country-name. From there I could use a simple WHERE-clause to look up the business data. Unfortunately it is not always that simple. In this example I use several data sets around the Gross Domestic Product and the above mentioned approach works well for countries like Germany but if we look for example at France I want to be able to distinguish between mainland France and its overseas dependencies. In that case it will be much simpler to use a spatial query and determine the geography that contains the location. After all a spatial-enabled Windows Azure will allow me to use the same approach not only on a country level but basically for any geography you can think off (e.g. super output areas, etc) and more important I cannot only use it for simple queries like “in which area is this point” but also for “find points of interest along a route” or “find hotels within 2 miles of Hadrian’s Wall”. Even territory management type queries where I want to aggregate geographies for example into sales territories are possible then. In the previous blog post on Data Visualization with Bing Maps we used the Bing Maps AJAX Control and the Microsoft Chart Control. Unfortunately the chart control doesn’t work on Azure yet. This is a known issue and a fix is on the way but there is no ETA yet. So I chose to use the charts in the Microsoft Silverlight Toolkit and because I’m already at Silverlight I’m also using the Bing Maps Silverlight Control. The complete list of tools I used is:
We will use the same statistical information around the Gross Domestic Product (GDP) from the GEO Data Portal of the Unites Nations Environment Programme (UNEP) as in the previous blog post and go through the following steps
As usual you will find the sample code at the end of this blog for download. Step 1: Create the Bing Maps Tile Layer and Upload to Windows Azure Since I already explained the generation of the tile layer using Safe FME in the previous blog post we can keep this short and go straight to the upload into the Windows Azure Blog Storage. I use Spaceblock for this which is available for free download from Codeplex. Step 2: Load Vector Data into SQL Server 2008 So far we have created a tile layer – basically a set of images – that we can overlay on Bing Maps. This will allow us to create a quite visual colour-coded map but obviously we will loose the meta data and the granularity of the information will depend on the number of colours we use. For example Germany, France, Italy and the UK are all mapped to the the same colour. In our example we want to be able to click on a country and retrieve the detailed information. To do that we will use the original vector data and spatial relationship queries as provided by the spatial functions in SQL Server 2008. Since we will ultimately not deploy the data on SQL Server 2008 but on Windows Azure we will have a couple of constraints. One is that SQL Server 2008 also provides spatial indexing and unfortunately we can’t use that in Windows Azure. More important though is that the Windows Azure Table Storage doesn’t support the SQL Server 2008 spatial data types natively so we have to work around it using the binary data type and that one only supports an array of bytes with a size of up to 64 kB. Well, the bad news is that a geometry for a country like Canada is much bigger than that but fortunately we can use Safe FME to generalize the geometries. We could actually do something similar with the Reduce-method in SQL Server 2008 as well but FME supports more algorithms and - most important - preserves shared boundaries between countries. Below you find the FME workflow… …and the settings for the Generalizer I chose: We do the same loading procedure for all data sets that we have downloaded previously. When you query the data in SQL Server 2008 using a spatial function such as… select geom.STArea() from GDP_Capita; …you will probably get an error message because the generalized data set has self-intersections which lead to invalid geometries. To validate the data execute the following SQL-statement: update GDP set GEOM=GEOM.MakeValid(); Finally let’s create a view which joins all the statistical information and the spatial data: CREATE VIEW V_GDP AS SELECT t1.NAME, t1.Y_2005 AS GDP, t2.Y_2005 AS GDP_Capita, t3.Y_2005 AS GDP_Growth_Rate, t4.Y_2005 AS GDP_Agri_Add, t5.Y_2005 AS GDP_Ind_Add, t6.Y_2005 AS GDP_Manu_Add, t7.Y_2005 AS GDP_Service_Add, t8.Y_2005 AS GDP_Trade_Add, t1.GEOM FROM GDP AS t1 INNER JOIN GDP_Capita AS t2 ON t1.ID = t2.ID INNER JOIN GDP_Growth_Rate AS t3 ON t1.ID = t3.ID INNER JOIN GDP_Agri_Add AS t4 ON t1.ID = t4.ID INNER JOIN GDP_Ind_Add AS t5 ON t1.ID = t5.ID INNER JOIN GDP_Manu_Add AS t6 ON t1.ID = t6.ID INNER JOIN GDP_Service_Add AS t7 ON t1.ID = t7.ID INNER JOIN GDP_Trade_Add AS t8 ON t1.ID = t8.ID ORDER BY t1.NAME Step 3: Migrate Data from SQL Server 2008 to Windows Azure For this step we create a small WinForm-application that reads data from our SQL Server and inserts the records in a Windows Azure table. In order to access the Windows Azure Storage we use the StorageClient Library from the Windows Azure Samples. After we installed the Windows Azure SDK we will find these samples in the folder C:\Program Files\Windows Azure SDK\v1.0. So let’s compile the samples as described in the readme.txt, add the StorageClient.dll as reference to our project and double-check in the properties that “copy local” is set to true: If we want to use the StorageClient.dll we need to define table objects and entities in a class. Hence we create a new class GDP.vb to define the entities. We will use the same class later in our web application. Note that we define the property that will hold our spatial data in Well Known Binary (WKB) format as byte array. Imports Microsoft.Samples.ServiceHosting.StorageClient Imports System.Data.Services.Client Public Class GDPRecord Inherits TableStorageEntity Private _Name As String Public Property Name() As String Get Return _Name End Get Set(ByVal value As String) _Name = value End Set End Property Private _Total As Double Public Property Total() As Double Get Return _Total End Get Set(ByVal value As Double) _Total = value End Set End Property Private _Capita As Double Public Property Capita() As Double Get Return _Capita End Get Set(ByVal value As Double) _Capita = value End Set End Property Private _Growth As Double Public Property Growth() As Double Get Return _Growth End Get Set(ByVal value As Double) _Growth = value End Set End Property Private _Agri As Double Public Property Agri() As Double Get Return _Agri End Get Set(ByVal value As Double) _Agri = value End Set End Property Private _Ind As Double Public Property Ind() As Double Get Return _Ind End Get Set(ByVal value As Double) _Ind = value End Set End Property Private _Manu As Double Public Property Manu() As Double Get Return _Manu End Get Set(ByVal value As Double) _Manu = value End Set End Property Private _Serv As Double Public Property Serv() As Double Get Return _Serv End Get Set(ByVal value As Double) _Serv = value End Set End Property Private _Geom As Byte() Public Property Geom() As Byte() Get Return _Geom End Get Set(ByVal value As Byte()) _Geom = value End Set End Property Public Sub New(ByVal _Name As String, ByVal _Total As Double, ByVal _Capita As Double, _ ByVal _Growth As Double, ByVal _Agri As Double, ByVal _Ind As Double, _ ByVal _Manu As Double, ByVal _Serv As Double, ByVal _Geom As Byte()) MyBase.New("Country", String.Format("{0:d10}", DateTime.UtcNow.Ticks)) Name = _Name Total = _Total Capita = _Capita Growth = _Growth Agri = _Agri Ind = _Ind Manu = _Manu Serv = _Serv Geom = _Geom End Sub Public Sub New() End Sub End Class Public Class GDP Inherits TableStorageDataServiceContext Public Sub New() MyBase.New(StorageAccountInfo.GetDefaultTableStorageAccountFromConfiguration()) End Sub Public ReadOnly Property GDPTable() As DataServiceQuery(Of GDPRecord) Get Return CreateQuery(Of GDPRecord)("GDPTable") End Get End Property End Class Next we create a app.config that will hold our credentials for the Windows Azure Storage <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> In our WinForm we create just 1 button. When we load the form we try to create a new table GDP in our Windows Azure Table Storage. If this table already exists the command will do nothing. When we click the button we will read through our database view, retrieve the alphanumeric data in their normal format and the spatial data as Well Known Binary (WKB) and add each record to our Windows Azure Table Private Sub btnStartTransfer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ The source code for this little tool is available here Once we completed the upload we may want to use a tool such as the Azure Storage Explorer to verify everything went well. Technorati Tags: Virtual Earth,Bing Maps,Windows Azure,SQL Server 2008,Safe FME,Silverlight,Silverlight Toolkit TrekWireless Shows Electric Car Charging Points on Bing MapsFollowing the announcement of the a UK-wide trial of low carbon and electric cars TrekWireless has created a map of electric car charging points (juice points) in Westminster. The infobox shows the location of electric car charging points in 360 degree POSIPIX images and through their SMS service a mobile user can request a geo-tagged image be sent to his navigation device. June 22 Where Are Your Site-Visitors Coming From?If you haven’t checked it out yet, you need to have a look at Worldmaps. Worldmaps determines through an IP-address lookup where the visitors of your site are located and generates various reports. For starters you can integrate an image in your website… …but you can also get a Bing Maps application and more detailed reports through the homepage and to put it with their own words: “While it's fun to see where your visitors are coming from, it's more fun to participate in the social. See how your stats rank against your friends, and see who can achieve the highest world domination.” I’m only on rank 97 but then: I only do it for 12 days now and I’ll work on it :-) June 18 Data Visualization with Bing MapsIntroduction Presenting your data effectively is often a challenging task. The most comprehensive information is usually stored in tables – often in databases. However, the more detailed this information is the more difficult it becomes to get a quick overview. There are many ways how you can provide drill downs but then you loose the big picture. Data that relates to geographies can be well presented on a map and in previous blogs I have described how to create heatmaps or thematic maps. The thematic maps sample uses the UMN MapServer to create a Bing Maps tile layer on the fly and implements a callback-function that retrieves the details for the location you clicked on from a database. In this walk-through I will have a different approach and create a static Bing Maps tile layer using Safe FME. I will also enhance the detailed view for this location by using the Microsoft Chart Controls: The application will embed the Bing Maps AJAX control and load it along with the background maps from a Microsoft data centre. The tile layer with the thematic maps is hosted in an environment that you control - for example a virtual directory on your web server. We will attach an event to the map that captures a right-click, calculates the latitude and longitude of the location we clicked on and creates an asynchronous AJAX call to a web service. The web service will query the database, determines the country you clicked on, retrieves the detailed information, creates a pie chart and returns a VEShape-object in its response to the AJAX call. The AJAX call has been waiting for this response and adds the VEShape-object to the map. For this example I use the following components
We will use some statistical information around the Gross Domestic Product (GDP) as available on the GEO Data Portal of the Unites Nations Environment Programme (UNEP) and go through the following steps in detail
Step 1: Creating the Bing Maps Tile Layer For starters we need the country boundaries in a spatial data format and some statistical information that we can easily visualize through colour-coded maps and charts. A good source for this type of data is the GEO Data Portal of the Unites Nations Environment Programme (UNEP). For this example I searched for GDP and downloaded the following data sets on the national level for the year 2005 as ESRI Shape-files:
The data is compressed into tar.gz files and I used 7Zip to extract the archives. We only need the files starting with “GEO” from each archive. Now I use Safe FME to create my Bing Maps Tile Layer as follows: We start with a source data set pointing to our ESRI Shape-file for the Gross Domestic Product per Capita. This file describes the coordinates already in the WGS84 coordinate system we use in Bing Maps but has a geographic extend that exceeds the addressable area in Bing Maps (green pattern in the image below); hence we need to clip it to the area that we can use. This limitation is due to the Mercator projection we use in Bing Maps and which is reasonable accurate only from -85.05112878 to 85.05112878 (for more details see this article). For the clipping we create a new polygon using the Creator; this polygon will then be used as the Clipper-attribute in the Clipper-Transformer. The geometries in the Shape-file are fed into this transformer as Clippees. The result is a set of geometries that covers only the addressable area in Bing Maps. Next we define the colours by sorting the countries based on the value for the GDP per Capita into buckets. For each bucket we assign a fill-colour and for all of them we use the same colour (white) for the boundaries. Now that we have the colours defined we go on and re-project the data into the Worldwide Mercator projection using the Reprojector with EPSG:3785 as output-projection. Next we rasterize the vector data. The VirtualEarthTiler can use parameters for minimum and maximum zoom-level but for best results I suggest to have a rasterizer for each Bing Maps zoom-level that you want to create. This will guarantee a even thickness for the country boundaries. In our example I render Bing Maps tiles for the zoom-levels 1 to 7 and use the following values for the number of cells in the rasterizer:
Now we use the transformer VirtualEarthTiler to cut the raster into tiles and finally we write them as PNGRASTER to the file system using the quadkey-attribute that is created automatically by the VirtualEarthTiler as fanout-attribute. For more information on the Bing Maps tile system see this article. We repeat the same for the GDP data set and have now 2 tile sets that we can use as layers for Bing Maps. After FME has done it’s work create a virtual directory on your web server and point it to the location where you have the tiles. Step 2: Create the Bing Maps Application for the Thematic Map Our web page that implements our data visualization is a simple HTML-page. We reference the Bing Maps AJAX control in the header along with the script that contains our own JavaScript-functions. In the body we have a div-element that will host the map and another div-element with some checkboxes that allow us to switch the thematic layers on or off. When we activate the checkboxes we will fire a JavaScript-function and pass a couple of parameters into it:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Bing Maps Demos</title> <link rel="shortcut icon" href="IMG/favicon.ico" /> <script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.2"></script> <script src="JS/MyScript.js" type="text/javascript"></script> <link href="CSS/MyStyles.css" rel="stylesheet" type="text/css" /> </head> <body> <div style="position:absolute; top:0px; left:0px; width:100%; height:50px;" class="header"> <table> <tr> <td style="width:100px; text-align:left"><img src="IMG/BingMaps.png" alt="Bing Maps Logo" style="margin-left:5px; margin-top:5px" /></td> <td style="width:100%; text-align:center; white-space:nowrap">Data Visualization</td> <td style="width:100px; text-align:right"><img src="IMG/SQL08.png" alt="SQL Server 2008 Logo" style="margin-right:5px;" /></td> </tr> </table> </div> <div id="divCtrl" style="position:absolute; top:65px; left:10px; width:200px; height:300px;" class="ctrl"> <div style="position:absolute; top:5px; left:5px; right:7px; width:90%"> <input id="cbGDP" type="checkbox" onclick="AddTileLayer('cbGDP','GDP',90,-180,-90,180,'http://hannesvestorage.blob.core.windows.net/vetiles/GDP/%4.png',1,7,0.7,100)" />GDP<br /> <input id="cbCapita" type="checkbox" onclick="AddTileLayer('cbCapita','Capita',90,-180,-90,180,'http://hannesvestorage.blob.core.windows.net/vetiles/GDP_Capita/%4.png',1,7,0.7,100)" />GDP per Capita<br /> <div id="divLegend" style="position:absolute; left:5px; width:100%"></div> </div> </div> <div id="divMap" style="position:absolute; top:65px; left:220px; width:300px; height:300px;" class="ctrl"></div> <div style="position:absolute; bottom:0px; left:0px; width:100%; height:20px;" class="footer"> <a href="http://johanneskebeck.spaces.live.com" target="_blank">My Blog</a> | <a href="http://talkingdonkey.info" target="_blank">My Office Live</a> | <a href="http://twitter.com/JohannesKebeck" target="_blank" >Twitter</a> | <a href="http://www.linkedin.com/in/johanneskebeck" target="_blank" >Linked In</a> | <a href="https://www.xing.com/profile/Johannes_Kebeck" target="_blank" >Xing</a> | <a href="http://www.facebook.com/people/Johannes-Kebeck/719916893" target="_blank" >Facebook</a> </div> </body> </html> In our JavaScript we create window-level events that load the map when the browser loads the HTML-document and resizes it when the size of the browser-window changes. We also attach an event that fires after we zoomed the map and prevents us from zooming to a level where we don’t have any more data. Remember we rendered our tile layer only down to level 7. Finally we provide a function that allows us to add or remove the tile layer. This is the one that is triggered by the checkboxes in our HTML-document. window.onload = GetMap; window.onresize = Resize; //Global Parameters var map = null; var mapWidth = null; var mapHeight = null; function GetMap() { map = new VEMap('divMap'); //Load and resize the map map.LoadMap(new VELatLong(0, 0), 2, 'r', false); Resize(); //Map Events map.AttachEvent("onendzoom", EventEndZoom); } //Resize map and controls whenever the size of the browser window changes //Also load the minimap function Resize() { var mapDiv = document.getElementById("divMap"); var ctrlDiv = document.getElementById("divCtrl"); var windowWidth = document.body.clientWidth; var windowHeight = document.body.clientHeight; mapWidth = windowWidth - 230; mapHeight = windowHeight - 95; mapDiv.style.width = mapWidth + "px"; mapDiv.style.height = mapHeight + "px"; ctrlDiv.style.height = (windowHeight - 95) + "px"; map.Resize(mapWidth, mapHeight); map.ShowMiniMap(mapWidth-205, 13, VEMiniMapSize.Large); } //Restrict Zoom-Level function EventEndZoom(e) { if (e.zoomLevel > 7) { map.SetZoomLevel(7); } } //Tile Layer function AddTileLayer(control, layer, maxlat, maxlon, minlat, minlon, url, minlvl, maxlvl, opac, zindex) { if (document.getElementById(control).checked == false) { map.DeleteTileLayer(layer); document.getElementById("divLegend").innerHTML = ""; } else { var bounds = [new VELatLongRectangle(new VELatLong(maxlat, maxlon), new VELatLong(minlat, minlon))]; var tileSourceSpec = new VETileSourceSpecification(layer, url); tileSourceSpec.Bounds = bounds; tileSourceSpec.MinZoomLevel = minlvl; tileSourceSpec.MaxZoomLevel = maxlvl; tileSourceSpec.Opacity = opac; tileSourceSpec.ZIndex = zindex; map.AddTileLayer(tileSourceSpec); if (control == "cbGDP") { document.getElementById("divLegend").innerHTML = "<br><hr><br><b>Million USD (2005)</b><br><table border=0 cellspacing=0 cellpadding=0><tr><td style='background-color:White;width:10px;height:10px'></td><td> No Data</td></tr><tr><td style='background-color:Red;width:10px;height:10px'></td><td> 1..49,999</td></tr><tr><td style='background-color:#FF5400;width:10px;height:10px'></td><td> 50,000..99,999</td></tr><tr><td style='background-color:#FFAA00;width:10px;height:10px'></td><td> 100,000..499,999</td></tr><tr><td style='background-color:#FFFF00;width:10px;height:10px'></td><td> 500,000..999,999</td></tr><tr><td style='background-color:#AAFF7E;width:10px;height:10px'></td><td> 1,000,000..4,999,999</td></tr><tr><td style='background-color:#00FF00;width:10px;height:10px'></td><td> 5,000,000..</td></tr></table><br><br><i>Right-click on a country to retrieve details.</i>"; } else{ document.getElementById("divLegend").innerHTML = "<br><hr><br><b>USD per Person (2005)</b><br><table border=0 cellspacing=0 cellpadding=0><tr><td style='background-color:White;width:10px;height:10px'></td><td> No Data</td></tr><tr><td style='background-color:Red;width:10px;height:10px'></td><td> 1..4,999</td></tr><tr><td style='background-color:#FF5400;width:10px;height:10px'></td><td> 5,000..9,999</td></tr><tr><td style='background-color:#FFAA00;width:10px;height:10px'></td><td> 10,000..19,999</td></tr><tr><td style='background-color:#FFFF00;width:10px;height:10px'></td><td> 20,000..29,999</td></tr><tr><td style='background-color:#AAFF7E;width:10px;height:10px'></td><td> 30,000..39,999</td></tr><tr><td style='background-color:#00FF00;width:10px;height:10px'></td><td> 40,000..</td></tr></table><br><br><i>Right-click on a country to retrieve details.</i>"; } } } At this point we can already run our application and overlay the thematic map. Step 3: Load the database with our Spatial and Business Data For this task we use again Safe FME. We load all of our 7 ESRI Shape-files as source data sets in the workbench and use the “SQL Server Spatial” format as destination. We also make sure that the coordinate system for the destination is set to EPSG:4326. Once we have loaded the data we run the following queries from our SQL Server Management Studio to create indexes and validate the geometries: alter table GDP alter column ID int not null; alter table GDP add constraint PK_GDP primary key clustered (ID); update GDP set GEOM=GEOM.MakeValid(); CREATE SPATIAL INDEX SPATIAL_GDP ON GDP(GEOM) USING GEOMETRY_GRID WITH( BOUNDING_BOX = ( xmin = -180, ymin = -90, xmax = 180, ymax = 90), GRIDS = ( LEVEL_1 = MEDIUM, LEVEL_2 = MEDIUM, LEVEL_3 = MEDIUM, LEVEL_4 = MEDIUM), CELLS_PER_OBJECT = 16); We also create a view that contains all of the relevant business and spatial data: CREATE VIEW V_GDP AS SELECT t1.NAME, t1.Y_2005 AS GDP, t2.Y_2005 AS Capita, t3.Y_2005 AS Growth, t4.Y_2005 AS Agr, t5.Y_2005 AS Ind, t6.Y_2005 AS Man, t7.Y_2005 AS Ser, t1.GEOM FROM GDP AS t1 INNER JOIN Capita AS t2 ON t1.ID = t2.ID INNER JOIN Growth AS t3 ON t1.ID = t3.ID INNER JOIN Agr AS t4 ON t1.ID = t4.ID INNER JOIN Ind AS t5 ON t1.ID = t5.ID INNER JOIN Man AS t6 ON t1.ID = t6.ID INNER JOIN Ser AS t7 ON t1.ID = t7.ID; Step 4: Create the Pie Chart For the pie chart we use the Microsoft Chart Control which is available for free download (Chart Controls for Microsoft .NET Framework 3.5, Chart Controls Add-on for Microsoft Visual Studio 2008, Documentation, Samples). Note: this control requires the .NET Framework 3.5 and it must be installed on the web server. If you intend to use it in a hosted environment you need to ask your hosting provider to install it for you. If you just want to copy the DLL into the hosting environment you need to make sure that you have set the ASP.NET environment to full trust which is not really advisable. The control will also be shipped as part of the .NET Framework 4.0. Unfortunately the chart control doesn’t work on Azure yet. This is a known issue and a fix is on the way but there is no ETA yet. Once you have installed the Chart Control and the Visual Studio Add-On you can create a new web form and start designing the chart. We intend to pass the parameters for the values in the URL when we call the chart so we add some code to the Page_Load event that retrieves the URL-parameters, creates a series of data points and adds a tooltip with the value for each point. Partial Class Chart Inherits System.Web.UI.Page Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load 'set culture to en-UK to avoid potential problems with decimal-separators System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.CreateSpecificCulture("en-UK") Dim agr As Double = Math.Round(CDbl(Page.Request.Params("agr")), 1) Dim ind As Double = Math.Round(CDbl(Page.Request.Params("ind")), 1) Dim man As Double = Math.Round(CDbl(Page.Request.Params("man")), 1) Dim ser As Double = Math.Round(CDbl(Page.Request.Params("ser")), 1) Dim yValues As Double() = {agr, ind, man, ser} Dim xValues As String() = {"Agriculture", "Industry", "Manufacturing", "Service"} Chart1.Series("Default").Points.DataBindXY(xValues, yValues) Chart1.Series("Default")("PieLabelStyle") = "Disabled" Chart1.Series("Default").ToolTip = "#VALX: #VALY%" End Sub End Class Step 5: Create a callback function that retrieves the details when we right-click on a map Here we have a couple of things to do.
Let’s start with the JavaScript In the function GetMap we define our new event and we also clear the default styles for the info-box that pops up when we mouse over a VEShape-object on the map. This is reasonable since we want to have more space for our chart. function GetMap()
{
…//Capture Right-click map.AttachEvent("onclick", RightClick); //Set Style for InfoBox map.ClearInfoBoxStyles(); } Once we have cleared the default style the map will use the custom styles that we defined in our style sheet: .customInfoBox-previewArea { width:250px; height:400px; } The function that is fired when we click on the map comes next. If the mouse-click was a right-click we determine the latitude and longitude of the location we clicked on and fire our AJAX-call GetDetails with the location as input-parameter. function RightClick(e) { if (e.rightMouseButton == true) { //var pixel = new VEPixel(); var loc = map.PixelToLatLong(new VEPixel(e.mapX, e.mapY)); GetDetails(loc); } } The AJAX-call goes to our web service asynchronously passing the latitude and longitude of the clicked location as URL-parameter. Once it receives a response it executes it using the eval-function. //Get Data from SQL Server function GetDetails(loc) { //Delete existing Shapes map.DeleteAllShapes(); //Build the URL var url = "./DataService.ashx?lat=" + loc.Latitude + "&lon=" + loc.Longitude; //Get the appropriate XMLHTTP object for the browser var xmlhttp = GetXmlHttp(); //if we have a valid XMLHTTP object if (xmlhttp) { xmlhttp.open("GET", url, true); //varAsynx = true //set the callback xmlhttp.onreadystatechange = function() { if (xmlhttp.readystate == 4) //4 is a success { //server code creates JavaScript "on the fly" for us to //execute using eval() var result = xmlhttp.responseText; eval(result); } } xmlhttp.send(null); } } //Helper function function GetXmlHttp() { var x = null; try { x = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { try { x = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) { x = null; } } if (!x && typeof XMLHttpRequest != "undefined") { x = new XMLHttpRequest(); } return x; } Before we go to the web service let’s prepare the database. We actually need to execute a spatial query in the database to determine on which country we clicked. This can be done by passing the latitude and longitude of the clicked location to a stored procedure which then creates a spatial object of type POINT from these numeric information and runs the STContains-method to determine which country covers this point. Let’s do all of that in a stored procedure. In our SQL Server Management Studio we run the following statement: CREATE PROCEDURE GetCountryData @Lat VARCHAR(MAX), @Lon VARCHAR(MAX) AS DECLARE @clickString VARCHAR(MAX); SET @clickString = 'POINT(' + @Lon + ' ' + @Lat + ')'; DECLARE @click GEOMETRY; SET @click = GEOMETRY::STPointFromText(@clickString, 4326); SELECT NAME, GDP, Capita, Growth, Agr, Ind, Man, Ser FROM V_GDP WHERE (GEOM.STContains(@click) = 1); Finally we come to our web service. In this example I implement it as generic web handler and call it DataService. The data service retrieves the URL-parameters with the clicked location. It then sets up the connection to the database that we defined in the web.config and creates a SqlCommand that calls our stored procedure with parameters for the latitude and longitude. The response from the stored procedure is now parsed into a VEShape-object. In the VEShape.Description-property we have a couple of alphanumeric data and an iframe that embeds our chart control. In this iframe we use some of the detailed data from our database records to define the data points for the pie chart. Finally we append JavaScript-statements to add the VEShape-object to the map and open the info-box before we send the response back to the AJAX-call. <%@ WebHandler Language="VB" Class="DataService" %> Imports System Imports System.Web Imports System.Data.SqlClient Imports System.Globalization Public Class DataService : Implements IHttpHandler Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest 'set culture to en-UK to avoid potential problems with decimal-separators System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.CreateSpecificCulture("en-UK") 'Retrieve the URL-parameter Dim myLat As String = context.Request.Params("lat") Dim myLon As String = context.Request.Params("lon") 'Retrieve Database Setting from web.config Dim settings As ConnectionStringSettings = ConfigurationManager.ConnectionStrings("GDP") Dim myConn As New SqlConnection(settings.ConnectionString) myConn.Open() Dim cmd As New SqlCommand() 'Set SQL Parameters cmd.Connection = myConn cmd.CommandType = Data.CommandType.StoredProcedure cmd.Parameters.Add(New SqlParameter("Lat", myLat)) cmd.Parameters.Add(New SqlParameter("Lon", myLon)) 'Specify the stored procedure name as the command text cmd.CommandText = "GetCountryData" Dim reader As SqlDataReader = cmd.ExecuteReader() 'Read the DataReader to process each row Dim myPin As String = "" While reader.Read() myPin = "var shape=new VEShape(VEShapeType.Pushpin, new VELatLong(" + myLat + ", " + myLon + "));" + _ "shape.SetCustomIcon('./IMG/blue.png');" + _ "shape.SetTitle('" + reader.Item(0) + "');" + _ "shape.SetDescription('GDP (Mio USD): " + CDbl(reader.Item(1)).ToString("N1", CultureInfo.InvariantCulture) + _ "<br>GDP/Capita (USD): " + CDbl(reader.Item(2)).ToString("N1", CultureInfo.InvariantCulture) + _ "<br>Growth Rate (%): " + CDbl(reader.Item(3)).ToString("N1", CultureInfo.InvariantCulture) + _ "<br><iframe frameborder=0 width=\'250px\' height=\'300px\' scrolling=\'no\' src=\'./Chart.aspx?agr=" + _ reader.Item(4).ToString + "&ind=" + _ reader.Item(5).ToString + "&man=" + _ reader.Item(6).ToString + "&ser=" + _ reader.Item(7).ToString + "\'></iframe><br><i>Note: if the chart area is empty there are no detailed information for this country.</i>');" + _ "map.AddShape(shape);" + _ "map.ShowInfoBox(shape);" End While reader.Close() myConn.Close() context.Response.Write(myPin) End Sub Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable Get Return False End Get End Property End Class That’s it. You will find the complete sample code including the database the SQOL scripts and the FME workspaces here:
June 09 The Bing Maps Travel GameRecently I had the pleasure to work with MSN Norway on a Bing Maps Travel Game. The game is sponsored by VisitNorway.no and went officially live today. Although it is easily localized since all text is received either dynamically through a web service or loaded at the application-start from a xml-file only those of us who speak Norwegian will really be able to enjoy it and have a chance to win a great price at the end. So, if you do speak Norwegian give it a shot but be aware that speed is everything. The faster you find the next lead the more points you get. You can start the game from MSN Norway’s homepage… The game is based on the Bing Maps Silverlight Control. When you start the game a number of randomly chosen questions is selected for you and the time to find the first lead ticks down. Now jog your brain and move the map as fast as possible to the location described in the lead. Zoom in to see the sponsor logo and then mouse over it to stop the timer and receive the credit. The faster you are the higher the credit. Once in the game and only once you can cheat and use the help button to guide you to the destination but be aware: this will cost you 500 points and reduces your chances to win the price. After you acknowledged the credit the timer ticks for the next question. When you are through with all questions that have been selected for you you can enter your name and email-address to participate in the drawing. You can also send updates to Twitter or Facebook to share the game with your friends. Have fun :-)
May 24 Trek Wireless & Virtual EarthTrek Wireless specialises in the creation and application of geotagged 2D and 3D digital images for mapping, navigation and commercial applications. Recently they demonstrated the integration of their great POSIPIX images into Virtual Earth. Here you see an animated 360 degree photo of the Central Hall Westminster… …but it get’s even better when you look at The Maltings in Ely. Here you can follow the arrows to walk through the rooms and use your mouse to discover each room in the full 360 degree angle.
Technorati Tags: Virtual Earth,Trek Wireless May 23 Virtual Earth & Office LiveYou have certainly heard about Office Live and maybe you have heard about Office Live Small Business as well? Office Live Workspace and Office Live Small Business are complementary services. Office Live Workspace is for all Office users - it’s your place for online collaboration, to save and share documents and other files. Office Live Small Business, available in select countries, provides additional features that a small business needs to take their business online, including a professional website, domain name, company-branded emails, and online tools for managing customers and projects. With the Office Live Small Business Web Design Tool you can easily create and design you websites; it even comes with a module to add Maps and Directions to your office: Unfortunately this module uses the MapPoint Web Service. That is certainly good enough to find your office but for a great visual experience we can definitely do better. There is indeed an HTML module which allows us to enter our own HTML content and luckily this module also support JavaScript. Not only that, it even allows to reference JavaScript from other sites and therefore we can easily copy and paste our own Virtual Earth web site directly into this module: Now resize the module to the size that you prefer… …and there you go: You find my Office Live Small Business web site here and below is the HTML-page that I copied into the module: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
Technorati Tags: Virtual Earth,Office Live May 20 Virtual Earth Performance ImprovementsGreat news on the performance front. As of now the Content Delivery Network (CDN) for the Virtual Earth Platform is live!!! The Microsoft CDN is a key pillar of the Microsoft cloud computing strategy and is one of the primary investments the Virtual Earth team is making to increase overall platform performance. CDN is composed of multiple geo-distributed data centres (throughout the Americas, Europe and Asia) that allow Virtual Earth to host content closer to customers and end users. As a result of CDN, delivery speeds of Virtual Earth content have been improved up to 82 percent for both the Virtual Earth AJAX Map Control and Virtual Earth Silverlight Map Control. This first release is part of an on-going program that will expand the network later this year. To begin utilizing CDN, you will need to make an opt-in, non-breaking change to your AJAX map control code; no action is required for Silverlight Map Control users, as CDN logic is already built-in. You will find some documentation here but it is really very simple. You just need to add 4 little characters in the script-reference and change from: <script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.2"></script>
Technorati Tags: Virtual Earth,Performance April 29 Mapping the Outbreak of Swine Flue and Latest News?UMapper has created a great heatmap based on Virtual Earth to visualize the outbreak of Swine Flue. This map is being updated several times a day using a variety of sources. The BBC provides also a great website where you find video- and audio-files as well as well as reports and comments from those who have been affected:
April 11 Virtual Earth 3D – UpdatesOn the 9th of April an update for the version 6.2 of the Virtual Earth AJAX MapControl was launched and along with it came a new version of the VE 3D-Control which brings performance enhancements but also lots of new features which are described in more detail on the Virtual Earth 3D Team Blog. Amongst others you can now walk into your custom 3D models again. This feature had been there before but was silently disabled in a previous release. Now you can stand inside your house and have a look at the miserable weather outside again :-). Have a look for yourself. From the new features I find the custom Digital Elevation Model (DEM) most significant. I’m not sure if “Bunny-Valley” will now become one of the wonders of the world but it certainly demonstrates the new capabilities.
Technorati Tags: Virtual Earth,3D Inca X – Live Media GPS now using the Virtual Earth Silverlight MapControlThe guys from Inca X provide this wonderful application Live Media GPS since quite a while. It allows you to transform your GPS-enabled mobile phone into a “geocasting” device. It uses the camera on your mobile phone to either record or directly broadcast a video-feed and geo-reference it using an integrated or connected GPS. The Inca X website allows you to playback the video and follow the track on a Virtual Earth map. The website is now available in 2 versions – one is using the Virtual Earth AJAX MapControl and the other one is using the Virtual Earth Silverlight MapControl. This makes the experience even better than it has already been before. Great stuff guys!!!
March 22 Virtual Earth Flight SimulatorYou just have to check this out. Ricky Brundritt has created a Flight Simulator for Virtual Earth by writing a plug-in for the VE3D MapControl. In his 4 part blog series he also explains the making of and provides the source code. The control even leverages the local time. Ricky, this is just great!!! The 3D control is usually started through the Virtual Earth AJAX MapControl but there is also a documented SDK that allows you to access the assemblies directly and extend them with your own plug-ins. On their blog the VE3D team provides lots of helpful information and sample applications to get you started. The VE3D API is one of now 5 documented APIs for Virtual Earth but before you get carried away keep in mind that only 2 of these APIs are officially supported: the AJAX and the SOAP API. The VE3D API is still a beta and the Silverlight API is a Community Technology Preview (CTP). The ASP.NET Server Control is part of the Windows Live Tools for Visual Studio and a CTP as well. It is basically a wrapper around the AJAX control.
Database Connections with the Virtual Earth Silverlight MapControl CTPIntroductionIt has been long anticipated and finally it arrived as a Community Technology Preview (CTP): The Virtual Earth Silverlight MapControl. To get access to the CTP you need to register at Microsoft Connect. Once you are registered you will have access to an installer which gets you the libraries as well as the offline documentation. There is also an Interactive SDK, a link to a getting stated guide on Via Windows Live and another one that allows you to submit bugs and feature requests. One of the things that I like particularly about Silverlight is the ability to separate the work of a designer from the developers work. Most developers are quite happy with a button that executes the desired function when you click on it but designers pay much more attention on the details of the presentation. They might look for a button with rounded corners or images in the background and a nice animation when you mouse over it. With Silverlight the developer can put his button on the page and start working in Visual Studio on the code while at the same time the designer can use Expression Blend to work on the style. But aside from that there are a lot of immediate advantages for Virtual Earth:
Lets have a look at the last point and create a quick application with the Virtual Earth Silverlight MapControl that connects to a database to retrieve the points of interest in the current map view. Integrating the Virtual Earth Silverlight MapControlBefore we get started we need to verify that we have the Visual Studio 2008 SP1 and the Silverlight Tools for Visual Studio 2008 SP1 installed. We also need to register at Microsoft Connect and install the Virtual Earth Silverlight MapControl CTP. Once we have prepared our development environment we can start Visual Studio and create a new Silverlight project… …with an ASP.NET Web project. To the Silverlight project (not the Web project) we add now a reference to the Microsoft.VirtualEarth.MapControl.dll. By default this is installed in C:\Program Files\Microsoft Virtual Earth Silverlight Map Control\CTP\Libraries. In the Page.xaml we add a reference to our assembly and embed our map. For the map we can already define a number of properties such as the centre point (Center), the zoom-level (ZoomLevel) and the style (Mode). We also define a MapLayer which will host our points of interest as a child of the map. Finally we add a button that will allow us to switch the POI layer on and off. <UserControl x:Class="VE_SL_DBConn.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"> <Grid x:Name="LayoutRoot" Background="Black" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> <m:Map Name="MyMap" Center="51.461962075378054, -0.9260702133178665" ZoomLevel="17" Mode="Road"> <m:Map.Children> <m:MapLayer x:Name="MyPOI"></m:MapLayer> </m:Map.Children> </m:Map> <Button Height="25" Width="100" HorizontalAlignment="Right" VerticalAlignment="Top" Click="LoadPoiInMapView_Click"> <TextBlock x:Name="btnPOI">Get POI</TextBlock> </Button> </Grid> </UserControl>
You can already compile the project and have a look at your map but obviously we are not yet connected to our database. Querying the DatabaseNow that we have defined our map we prepare the database connection. A quick and simple way to do so is the OR-mapping in the LINQ to SQL classes. In the web project add a new LINQ to SQL mapper. Now simply drag and drop a table from your Server Explorer to the design surface of the dbml file… …and in the properties of the newly created data context set the Serialization Mode to unidirectional. Next we create a web service. We add a Silverlight-enabled WCF Service to our web project. Since we have already mapped the table in our SQL Server to objects we only need a minimum of code. As input parameters we take the bounding box of the current map view in order to reduce the number of returned results. We will determine the bounding box a little bit later. The result is returned as a List of objects. Imports System.ServiceModel Imports System.ServiceModel.Activation <ServiceContract(Namespace:="DataService")> _ <AspNetCompatibilityRequirements(RequirementsMode:=AspNetCompatibilityRequirementsMode.Allowed)> _ Public Class DataService <OperationContract()> _ Public Function GetPOIinView(ByVal NWlat As Double, _ ByVal NWlon As Double, _ ByVal SElat As Double, _ ByVal SELon As Double) _ As List(Of UK_low_bridges_all) Dim datacontext As New HannesPOIDataContext Return ((From UK_low_bridges_alls _ In datacontext.UK_low_bridges_alls _ Where UK_low_bridges_alls.Latitude <= NWlat And _ UK_low_bridges_alls.Latitude >= SElat And _ UK_low_bridges_alls.Longitude >= NWlon And _ UK_low_bridges_alls.Longitude <= SELon).ToList()) End Function End Class To use this web service in our Silverlight project (not the Web project) we add a Service Reference: Connecting the Map to the Web ServiceIn order to connect the map to the web service we add some code to our code-file for our page (Page.xaml.vb). First we import the namespaces for our Virtual Earth Silverlight MapControl and our web service. Next we create a procedure that is to be executed when we click on our button (LoadPoiInMapView_Click). To load the POI we add an event handler that fires when the map view changed, i.e. when we finished zooming or panning. This event handler will determine the bounding box of the map and use it as parameters for an asynchronous call to our web service. Once the call returns the result we will render the POI in our map layer. Imports VE_SL_DBConn.DataServiceReference Imports Microsoft.VirtualEarth.MapControl Partial Public Class Page Inherits UserControl Public Sub New() InitializeComponent() End Sub Public Sub LoadPoiInMapView_Click(ByVal sender As Object, _ ByVal e As RoutedEventArgs) If btnPOI.Text = "Get POI" Then AddHandler MyMap.ViewChangeEnd, AddressOf MyMap_ViewChanged btnPOI.Text = "Remove POI" Dim bounds As LocationRect = MyMap.GetBoundingRectangle() Dim svc As New DataServiceClient() AddHandler svc.GetPOIinViewCompleted, AddressOf svc_GetPoiInMapViewCompleted svc.GetPOIinViewAsync(bounds.Northwest.Latitude, _ bounds.Northwest.Longitude, _ bounds.Southeast.Latitude, _ bounds.Southeast.Longitude) Else RemoveHandler MyMap.ViewChangeEnd, AddressOf MyMap_ViewChanged MyPOI.Children.Clear() btnPOI.Text = "Get POI" End If End Sub Private Sub MyMap_ViewChanged(ByVal sender As Object, _ ByVal e As MapEventArgs) MyPOI.Children.Clear() Dim bounds As LocationRect = MyMap.GetBoundingRectangle() Dim svc As New DataServiceClient() AddHandler svc.GetPOIinViewCompleted, AddressOf svc_GetPoiInMapViewCompleted svc.GetPOIinViewAsync(bounds.Northwest.Latitude, _ bounds.Northwest.Longitude, _ bounds.Southeast.Latitude, _ bounds.Southeast.Longitude) End Sub Private Sub svc_GetPoiInMapViewCompleted(ByVal sender As Object, _ ByVal e As GetPOIinViewCompletedEventArgs) If e.Error Is Nothing Then For i = 0 To e.Result.Count - 1 Dim point As New Ellipse() point.Width = 10 point.Height = 10 point.Fill = New SolidColorBrush(Colors.Red) point.Opacity = 0.65 Dim location As New Location(e.Result(i).Latitude, e.Result(i).Longitude) MapLayer.SetMapPosition(point, location) ToolTipService.SetToolTip(point, e.Result(i).Name) MyPOI.Children.Add(point) Next Else MessageBox.Show("Error occurred while loading POI from database", _ "Error", MessageBoxButton.OK) End If End Sub End Class That’s it, we’re done. You will find the sample code here.
SummaryIn this quick sample we had a look at the database connection for the Virtual Earth Silverlight MapControl. Aside from the fact that we did the coding end-to-end in .NET we could also have a look at the performance and compare the user experience in various browsers. Yes, using Silverlight may not have the reach of HTML (yet) since you need to install a browser plug-in and most of all since it isn’t supported on all possible platforms and browsers (yet). It isn’t as powerful as the Windows Presentation Framework (WPF) either but it is a great compromise between outreach and richness.
Technorati Tags: Virtual Earth,Silverlight February 27 Virtual Earth Applications on Windows Azure (Part 3)Accessing Table StorageIn the first part we had a look at creating applications for Windows Azure and deploying them into the cloud. This was very simple and straight forward. Now we want to have a look at the Windows Azure Table storage and this is going to be slightly more tricky but don’t worry we get to it. A while ago I have blogged about connecting your Virtual Earth application to a SQL database and we will migrate one of these applications to the cloud. With a SQL database the approach was to attach an event to the map, that fires when we pan or zoom the map. The function that is then being called will determine the bounding box of our map view and create an asynchronous AJAX-call to a web service or web handler which will execute the SQL statement and create JSON-objects. The response from this service is now received by our AJAX-call and the VEShape-objects will be added to the map. Tables in the Windows Azure Table storage are different from SQL tables. They are not relational but hierarchical tables and instead of primary keys we have to work with PartitionKey and RowKey. This might remind some of us of the old IMS database systems and we might wonder if we made a step back in time but if you give it a second thought and consider that this table storage is intended to supported massive scalable applications you will see the logic behind. After all what do you do if you scale out a SQL Server? You will create partitions and distribute the load over multiple servers. In the case of Windows Azure the resources are accessible through instances and as mentioned in the first part of this series you just need to change the number the number of instances in the configuration file to scale up or down. The biggest challenge might be that in the current CTP Windows Azure supports only a subset of the LINQ Query Operators and Comparison Operators. It is also worth mentioning that the current CTP will truncate the results of a query after 1,000 entities so you will need to design your queries accordingly. To get some background on the Windows Azure Table storage I suggest this whitepaper on Programming Table Storage. Obviously it will be helpful to have the SDK at hand. I also really like this video in which Steve Marx creates a .NET application using the Windows Azure Table Storage. It is really a great starting point for a quick start and I will use some of the tips and tricks he describes there. As mentioned in part 2 the Windows Azure SDK comes with a couple of samples and one of those – the StorageClient - contains a .NET library that I will use here. So let’s assume that I have already compiled this library. Now I go ahead and add this library as well as the System.Data.Services.Client library as references to my project. Next I add the account details and credentials to my web.config file: <appSettings> <add key="AccountName" value="YOUR STORAGE ACCOUNT NAME"/> <add key="AccountSharedKey" value="YOUR STORAGE ACCOUNT KEY"/> <add key="TableStorageEndpoint" value="http://table.core.windows.net"/> </appSettings> Note: Do not add the account name to the URL for the TableStorageEndPoint. The access to the table in the Windows Azure Table Storage we need to create a class-file which defines the data model. In our new class we import the 2 references that we added before: the StorageClient from our sample library and the System.Data.Services.Client. The first class in my code-file contains the description of the entities in my table. It is derived from the TableStorageEntity in our sample StorageClient library and has properties for a latitude, a longitude and a name. The first function in this class is meant to add data to our table and even though this is not within the scope of our web application we will need it while we migrate data from our SQL Server into the cloud. The second function in the first class is an empty one and is used when we return data from our table. The second class defines the table and it is derived from the TableStorageDataServiceContext in our sample StorageClient library. The function in this class is called with a number of parameters that will host the latitudes and longitudes of the bounding box in our map view. When this class is called the function first retrieves the storage account info from the configuration file. Finally I add a property that executes the DataServiceQuery and actually queries the Windows Azure Storage Table with the parameters for the bounding box of our map as filter criteria. Imports Microsoft.Samples.ServiceHosting.StorageClient Imports System.Data.Services.Client Public Class BPUKRecord Inherits TableStorageEntity 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 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 Private _Name As String Public Property Name() As String Get Return _Name End Get Set(ByVal value As String) _Name = value End Set End Property Public Sub New(ByVal _Lat As String, ByVal _Lon As String, ByVal _Name As String) MyBase.New("", String.Format("{0:d10}", DateTime.UtcNow.Ticks)) Lat = _Lat Lon = _Lon Name = _Name End Sub Public Sub New() End Sub End Class Public Class BPUK Inherits TableStorageDataServiceContext Private ulLat As Double Private ulLon As Double Private brLat As Double Private brLon As Double Public Sub New(ByVal _ulLat As Double, ByVal _ulLon As Double, _ Before we can query our table in our web application we have to create it add some data. For our example I will migrate a table from SQL Server to the Windows Azure Table storage. I basically use the class above in a small application that connects to my database, reads all records in a table and for each record that my SqlDataReader reads, I will add an entity to the Windows Azure table. If we want to add data to our Windows Azure Table we have to retrieve the storage account information from the configuration file and first we need to make sure that the table exists. If it doesn’t I’ll create it with the code below. Dim tables = TableStorage.Create(StorageAccountInfo.GetDefaultTableStorageAccountFromConfiguration()) tables.TryCreateTable("BPUKTable") Then we initialize our data model and add an object to our table and finally we save the changes. The lines below are executed for each record that our SqlDataReader reads from our SQL database. Dim svc = New BPUK() svc.AddObject("BPUKTable", New BPUKRecord(myReader(0).ToString, myReader(1).ToString, myReader(2).ToString)) svc.SaveChanges() You will find this little helper application here.
After this short excurse on the migration of my SQL Server table let’s come back to our web application. We already have our data model for the table in the Windows Azure Table storage described in the class above. Our JavaScript remains exactly the same as the one that we use to access the SQL database. It is called when we tick a checkbox in our website. function AddTableData(control) { if (document.getElementById(control).checked == false) { //Delete POI slBPUK.DeleteAllShapes(); //Detach Map-Events map.DetachEvent("onendpan", LoadData); map.DetachEvent("onendzoom", LoadData); } else { //Add the Shape-Layer try { map.AddShapeLayer(slBPUK); } catch (e) { } //Attach Map-Events map.AttachEvent("onendpan", LoadData); map.AttachEvent("onendzoom", LoadData); LoadData(); } } function LoadData() { slBPUK.DeleteAllShapes(); //Retrieve the boundaries of the mapview var ulPixel = new VEPixel(0, 0); var brPixel = new VEPixel(mapWidth, mapHeight); var ulLatLon = map.PixelToLatLong(ulPixel); var ulLat = ulLatLon.Latitude; var ulLon = ulLatLon.Longitude; var brLatLon = map.PixelToLatLong(brPixel); var brLat = brLatLon.Latitude; var brLon = brLatLon.Longitude; //Build URL to call the server var url = "./LoadFromAzure.ashx?"; url += "&ulLat=" + ulLat; url += "&ulLon=" + ulLon; url += "&brLat=" + brLat; url += "&brLon=" + brLon; //Get the appropriate XMLHTTP object for the browser var xmlhttp = GetXmlHttp(); //if we have a valid XMLHTTP object if (xmlhttp) { xmlhttp.open("GET", url, true); // varAsynx = true //set the callback xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == 4) //4 is a success { //server code creates JavaScript "on the fly" for us to var result = xmlhttp.responseText //execute using eval() eval(result); } } xmlhttp.send(null); } } What is left to do is the web handler that connects to my class and executes the query. So let’s go ahead and create a new generic web handler. In the ProcessRequest-function we fetch the URL-parameters from our AJAX-call. These parameters represent the coordinates of the bounding box of our map view. Then we call our class to query the Windows Azure table with these parameters and build our JavaScript that we return to the AJAX-call. 'Get the URL-Parameters Dim ulLat As String = context.Request.Params("ulLat") Dim brLat As String = context.Request.Params("brLat") Dim ulLon As String = context.Request.Params("ulLon") Dim brLon As String = context.Request.Params("brLon") Dim i As Integer Dim myPins As String = "" Dim myTable As New BPUK(ulLat, ulLon, brLat, brLon) For Each BPUKRecord In myTable.BPUKTable myPins = myPins + _ "var shape" + i.ToString + "=new VEShape(VEShapeType.Pushpin, new VELatLong(" + _ That’s it we’re done. You find the application live on Windows Azure here and the source code here
Technorati Tags: Virtual Earth,Windows Azure Virtual Earth Applications on Windows Azure (Part 2)Accessing Blob StorageIn this part we want to extend the application and move some of our data into the cloud as well. Let’s start simple and move some blobs into the Windows Azure Blob storage. In this example I use a Microsoft Research (MSR) HD View application that I created with MSR ICE and render it as an iFrame inside the InfoBox of a VEShape-object. I also have a number of photo- and video-collections using Vertigo’s Slide.Show 2 that I render inside the InfoBox of the VEShape-object as well as a VETileLayer: We will move the images and videos to the Windows Azure Blob Storage and keep the active content like JavaScript in the web-role project to avoid cross-domain scripting issues. We will also make the files publicly available to keep it simple. An example for authentication and authorisation will be in the 3rd part in which we look at the table storage. First open up your Windows Azure Developer Portal and create a new Storage Project: Note the storage endpoints and your keys. Obviously you can write an application and uploads, downloads or deletes files from the Windows Azure Blob storage but there are also tools available that you can use. In the Windows Azure SDK for example you will find a number of sample applications. One of which is the CloudDrive that allows you to work with the cloud storage through the Windows PowerShell and provides lot’s of command-line functions including a feature similar to xcopy: Those who prefer the comfort of a graphical user interface will like SpaceBlock which you can download via CodePlex: So lets create our folders. By right-clicking on the level 1 folder (e.g. hdview) you can open up a context menu that allows you to set the access rights for the container. As mentioned before I’ll give them for now public access. Note: this right is only for reading, not for write-access. Now that I have my container in the Windows Azure Blob Storage I transfer my local files to the cloud and do the same again for videos, photos and my VETileLayer. SpaceBlock makes it really as simple as working with your local file-system Once the files are uploaded you can select them in SpaceBlock, right-click on them and have SpaceBlock generate a URL. Lastly you only need to change the URL’s in your application and you’re done. Below is an example for the data file that is used in Sllide.Show 2. <?xml version="1.0" encoding="utf-8"?> <data startalbumindex="0" transition="CrossFadeTransition"> <album title="Wilfreigehege Hellenthal" description="June 2008" image="http://hannesvestorage.blob.core.windows.net/photos/Wildfreigehege_Hellenthal/CIMG2143_t.jpg"> <slide title="Wildfreigehege Hellenthal" description="June 2008 - Taken: 10/06/2008" image="http://hannesvestorage.blob.core.windows.net/photos/Wildfreigehege_Hellenthal/CIMG2139_s.jpg" thumbnail="http://hannesvestorage.blob.core.windows.net/photos/Wildfreigehege_Hellenthal/CIMG2139_t.jpg" datetaken="10/06/2008" /> ...
Technorati Tags: Virtual Earth,Windows Azure Virtual Earth Applications on Windows Azure (Part 1)IntroductionCloud Computing is a hot topic today and of course it is much more than just hosting of web application. Cloud computing allows you to dynamically scale your applications up and down as you need by using virtualised resources. To see the value let’s have a look at an example.
Let’s assume you start a new website and you have just the right amount of resources in your infrastructure – with enough flexibility for a moderate growth. Once you are past the early adopting phase you might need to buy more web servers. As the word spreads more and more users come to your site and you will increase the capacity of your database server. Now everybody tells his friends and family about this great website of yours. The traffic grows exponentially and therefore you have to redesign your database for scalability and eventually to buy more of everything. Unfortunately quite often it happens that after the initial hype the traffic goes back again before it settles at a certain level and now you have all that unused capacity in your data centres. Now, hosting is of course a way how you can get around this but even then you will have to make the redesign in your databases and possibly your applications as well. Not to forget that you usually can’t just get an awful lot of resources without a pre-warning from your hosting provider. Now imagine an extreme example like a disaster management or information system. On normal days you will have moderate traffic but when the disaster strikes you have a huge peak in traffic. Particularly at that time when you need your application the most you might be flooded with requests and the service breaks down unless you have an awful lot of spare resources. On the other side if you have this huge amount of resources you are burning money every day that you don’t use them. Cloud computing with capacity on demand is a cost efficient way out of this dilemma. While Virtual Earth is already a highly scalable cloud application we need to have a look at the code and data that you add for your application and we will do that by migrating your application to Windows Azure. Windows Azure is part of the Azure Services Platform. Think of it as an operating system for the cloud. Windows Azure provides virtual resources for computing and storage on which you can build your application but the real magic is in the Fabric. The Fabric allows you distribute the application to as many resources as you need by just increasing the number of instances for your application either manually through a website or by uploading a new configuration-file. Well that sounds simple enough and indeed it is simple but the storage does behave differently – particularly the table storage and we will have a closer look on how to create, build and publish a Virtual Earth Application on Windows Azure. Creating and Publishing your first Virtual Earth Application for Windows AzureThere are lot’s of information for Windows Azure Developers. After you have signed up for an developer account you will probably want to download the Windows Azure SDK and – at least if you use the Visual Studio – also the Windows Azure Tools for the Microsoft Visual Studio. The former comes with a couple of samples of which we will use one later and the latter brings you templates for the Visual Studio. The Windows Azure SDK also comes with a development environment that emulates computing and storage services as well as the fabric on your local PC. A warning for those who are like me and always have to play with the latest technology: If you are using the Windows 7 Beta and use the January CTP of the Windows Azure SDK you won’t be able to run web-role projects through the local development fabric. You can however still run web-pages by right clicking them in the Solution Explorer and selecting ‘View in Brower’ from the context menu and as a matter of fact the application that I created for this blog post was developed with Visual Studio 2008 on Windows 7. Once you have installed the Windows Azure SDK and Tools for Visual Studio bring up your Visual Studio and create a new project of type ‘Web Could Project’. This will actually create a solution with 2 projects. The upper one contains the configuration and definition files. The lower one is the web-project that will hold our application. Actually if you don’t need to access the storage or use worker-roles you can just just copy your existing web-page into the web-role project and publish it right away into Windows Azure. For starters I’ll use an ASP.NET AJAX application with an Accordion-Control from the ASP.NET AJAX Control Toolkit. The application uses a few Virtual Earth functions and imports GeoRSS-feeds from other internet services. To publish the application right-click on the cloud service project and select publish from the context menu. This will build the solution and package it for deployment. It will then open the Windows Explorer at the location of the package and the Windows Azure Developer Portal in your web browser: Here you can navigate to an existing project or create a new one. Once you selected the project you can deploy it to a staging environment. Now you can run the application … …and test it. Once you feel comfortable with your application you can swap staging and production environment and you’re live:
Technorati Tags: Virtual Earth,Windows Azure February 09 Microsoft TagMost of you will be aware of Virtual Earth implementations for your mobile phone such as Live Search. Live Search comes as a web site as well as a mobile device application. Recently Microsoft launched a new service Microsoft Tag - a web-based tag creator and a tag-reader for your mobile device. It is similar to a barcode reader but works with a colour-coded pattern that can be captured through the camera of your mobile device. Now there must be some useful connections between the 2 technologies :-) Once you signed up to Microsoft Tag via your Windows Live ID you can create such tags for URLs, free text, vCards or Dialers. If you go to the homepage of Microsoft Tag you’ll find a video with various use cases. One that I really like is the use of Microsoft Tag for driving directions. Suppose you are at a hotel and want to get directions to the next train station, airport or theatre; with Microsoft Tag you can just create a photo of the tag and the driving directions will be shown immediately – without any typing. Let’s have a walk through the process. The idea is to create a tag that provides driving directions from a hotel to the Microsoft Innovation Centre in Barneveld, Netherlands. You could easily create a website for mobile devices using the Virtual Earth Web Service and add your own branding but to keep it short and simple I’m using Live Search for now. In Live Search (http://m.live.com) you create the Driving Directions… …and copy the URL. Now go to Microsoft Tag and create a new tag of type URL pointing to the address that you just copied. You’re almost there. Render the tag in a style and size as appropriate for your purpose… …and stick it to a brochure, poster, billboard or slide: Don’t forget to provide a location for the download of the tag-reader software: http://gettag.mobi Well, that’s already everything you need to do. The user can now download and install the software on his mobile device. Once the software is installed he can start the tag reader… …capture the tag with his camera… …and the browser will start automatically and show the driving directions:
Technorati Tags: Virtual Earth,Microsoft Tag December 01 Creating Heat Maps with Virtual Earth and SQL Server 2008IntroductionIn my previous blog I described how to create thematic maps using Virtual Earth, SQL Server 2008 and the UMN MapServer. While thematic maps are a powerful visualization when you analyze by fixed boundaries it is also often helpful to create heat maps based on geographic density. Heat maps are not only useful in a geographic context, they are also used for many other sorts of density analysis. According to Wikipedia a heat map in general is a graphical representation of data where the values taken by a variable in a two-dimensional map are represented as colours. Aside from geographic analysis heat maps are used for example as:
In the context of this blog I will use the heat map algorithm described by Dylan Vester to visualize the results of a geographic cluster analysis. Dylan has explained the creation of heat-points and the colorizing very well so I won’t go into the details there but rather describe the clustering that allows us to set the intensity for the heat-points. Generating the Heat-MapAs Dylan explains, creating the heat-map is a 2 step process. First we have to create an intensity mask as a 256 grayscale image and then we translate each shade of gray into a colour. For more details have a look at Dylan’s blog. The intensity is determined by the density of points and to determine this density we create a grid and count how many points are in each cell of the grid. In order to do so we will pan and zoom the map to the area that we want to analyze. Next we determine some required parameters of the current map view.
Virtual Earth provides the necessary methods. The pixel-coordinates of the upper left corner in the map view are always (0, 0) and of the lower right corner they are (MapWidth, MapHeight). The method to determine the latitude and longitude for a pixel in the MapView is VEMap.PixelToLatLong. The width and height are the dimensions of the HTML DIV-element which hosts our VEMapControl. To determine the zoom-level we use the method VEMap.GetZoomLevel. With these information we will now create an AJAX-call to a Generic WebHandler. First we set up the grid. In my sample I have set the size of a grid-cell to 40 pixels but you can change this value of course. Since we are now using a generic web handler we can’t use the VEMap.LatLongToPixel method to determine the pixel-coordinates. However, the math is very well explained in this MSDN article on the Virtual Earth Tile System. In my sample code it is implemented in the function LatLongToPixel. We express all points as absolute pixels in the Virtual Earth pixel-coordinate system who’s origin - pixel (0,0) - corresponds to Latitude 85.05112878 and Longitude –180 in the WGS84 coordinate system. For more details see the article mentioned above. We start by determining the absolute pixel-coordinates of the upper left corner in the current map view and then set up a database connection to our source data which we want to analyze. While we loop through our data reader we convert each pair of latitude and longitude to a pixel-coordinate and assign it to a grid cell. Once we read all data from our database connection we have a grid and each cell has information about the pixel coordinates and the number of points in it. Now we will loop through the grid cells and create a heat point for each one and add them to an empty bitmap. This is the part where I re-use the code from Dylan Vester. As a result we will have a heat-map-image which matches the current map view in its size: Creating a Virtual Earth Tile Layer from our Heat Map using MapCruncherThe most simple way to create a Virtual Earth Tile Layer is to use the MapCruncher. We already generated our image and we have discussed 2 that we calculated the latitudes and longitudes of the upper left and lower right corner. To align the image in MapCruncher and project it for Virtual Earth we need at least 3 points. Therefore I extended the sample application and retrieve all 4 points of the bounding box of our map view (see 01_Generate_HeatMap.htm in the attached sample code). Now we load the image into MapCruncher and move the corners 1 by one underneath the crosshair in the left window. In the right window we paste the latitudes and longitudes for each map-correspondence point as determined above in the input-boxes for the Virtual Earth Position. Once we have at least 3 points we can lock the view and render the tiles (see 02_Overlay_HeatMap.htm in the attached sample code). Dynamically Creating and Tiling the Heat MapAs mentioned above the generation of the heat map uses the current zoom-level as 1 parameter which means that we are not working on a optimum level of detail when we zoom in if we follow the approach mentioned above. In order to be more dynamic it would be ideal to generate the heat map on the fly whenever we pan or zoom the map. A while ago I posted a blog which describes how you could generate dynamic tile layers from images through an ad-hoc tile-cutting service. I have recycled parts of that code in the 03_Dynamic_HeatMap.htm of the attached sample code. In order to reduce the number of AJAX-calls to our web handler we will introduce a threshold and only generate a new heat map if we pan for more pixels than the result is or if we zoom the map. Well, that’s it. you will find the sample code here.
|
|
|