Loading GeoRSS-Feeds with Dynamic Modules in the Bing Maps AJAX Control v7

Introduction

In May several Updates have been released to the Bing Maps AJAX Control v7 which Keith Kinnan has summarized here. Possibly the most significant one is the ability to dynamically load additional modules.

When the Bing Maps AJAX Control had been re-written for v7 some of the design goals had been to improve performance and to support mobile devices. In order to achieve these goals the Bing Maps AJAX control supports now HTML5 and it was also put on a diet in order to slim down to a size that can load quickly even on mobile devices. As a consequence the core-control itself supports only the minimum requirements that most mapping sites will have. However, the control was designed in such a way that additional modules can be optionally and dynamically added later on when needed. The general principal is explained in the SDK and in the interactive SDK you will find an example that implements client-side clustering. This module for client-side clustering adds about 14kB to the weight of the website and while it is a useful feature for some not everybody might need it and therefore it seems to be a good idea to stick it into a module and let the developer decide if and when he needs it.

Another feature that may be helpful for some is the ability to import GeoRSS-feeds. In this blog-post we will have a look at the steps to create such a custom module and load it on demand. To limit the amount of code for this blog-post we parse only GeoRSS-feeds following the Simple serialization. However a similar approach could be used to parse GeoRSS-feeds derived from GML as well as GPX- or KML-files.

GeoRSS-Feed

A GeoRSS-feed following the Simple serialization contains tags and to describe points (<georss:point>), lines () and polygons () as a sequence of latitudes and longitudes. The example below is a GeoRSS-feed that contains a polygon representing the Microsoft Office in London, a point representing the nearest underground station and a line representing the walk from the tube station to the Microsoft Office.

<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:georss="http://www.georss.org/georss" version="2.0">
  <channel>
    <title>Microsoft London</title>
    <link>http://www.bing.com/maps</link>
    <description />
    <language>en-gb</language>
    <item>
      <title>Microsoft</title>
      <link>http://www.bing.com/maps/?cid=42E1F70205EC8A96!14501</link>
      <description>Cardinal Place, 100 Victoria Street, London, SW1E 5JL</description>
      <guid isPermaLink="false">e7aea3b0d2e1c7b8</guid>
      <pubDate>Fri May 27 22:37:13 UTC 0100 2011</pubDate>
      <georss:polygon>51.49673999638715 -0.14145107778161137 51.496997150067386 -0.1394555142745313 51.49772184808794 -0.1397022775039014 51.497568226428456 -0.1408288052901563 51.49676003438838 -0.1414403489455518 51.49673999638715 -0.14145107778161137</georss:polygon>
    </item>
    <item>
      <title>Victoria</title>
      <link>http://www.bing.com/maps/?cid=42E1F70205EC8A96!14501</link>
      <description>Underground Station</description>
        <guid isPermaLink="false">b03dc79bd7bdb81e</guid>
      <pubDate>Fri May 27 22:37:39 UTC 0100 2011</pubDate>
      <georss:point>51.49644610469038 -0.14391334565724278</georss:point>
    </item>
    <item>
      <title>Walk from Victoria to Cardinal Place</title>
      <link>http://www.bing.com/maps/?cid=42E1F70205EC8A96!14501</link>
      <description>180m</description>
      <guid isPermaLink="false">bf8ee4e437813477</guid>
      <pubDate>Fri May 27 22:38:24 UTC 0100 2011</pubDate>
      <georss:line>51.496643145923684 -0.14391334565724278 51.496506219055234 -0.14225574048603917 51.49657969206017 -0.1415208152159586 51.4967199583771 -0.14146180661763097</georss:line>
    </item>
  </channel>
</rss>

We will use the location tags to draw points, lines and polygons and the text within the title and description tags as content for the InfoBox that pops up when we click on the object.

The Website

On the website we have a simple map and add a text-box to enter the path to the GeoRSS-feed as well as a button to load the module and import the GeoRSS-feed.

image

The code so far is shown below.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
    <title></title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0"></script>
    <script type="text/javascript">
        var map = null;
        var MM = Microsoft.Maps;

        function GetMap() {
            var options = { credentials: "YOUR BING MAPS KEY",
                            enableClickableLogo: false,
                            enableSearchLogo: false,
                            mapTypeId: Microsoft.Maps.MapTypeId.road,
                            center: new MM.Location(51.4611794944098, -0.9259434789419174),
                            zoom:17 };
                        map = new MM.Map(document.getElementById('mapDiv'), options);

                        // Hide the info box when the map is moved.
                        MM.Events.addHandler(map, 'viewchange', hideInfobox);
        }
      </script>
</head>
<body onload="GetMap();">
    <div id='mapDiv' style="position:relative; width:800px; height:600px;"></div><br />
    <a>GeoRSS-Feed</a><input id="txtGeoRSS" type="text" value="MSFT_London.xml" />
    <input id="Button2" type="button" value="Import" onclick="LoadModule()" />
</body>
</html>

We add now to the website a function to register and dynamically load an additional module. To register the module we give it a unique name and point to the location of the JavaScript that contains the module. When we load the module we can optionally specify a callback-function that is being executed when the loading is completed.

        function LoadModule(){
            // Register and load a new module
            MM.registerModule("GeoRSSModule", "./GeoRSSModule.js");
            MM.loadModule("GeoRSSModule", { callback: ModuleLoaded });
        }

The Module

The module is a basically a separate JavaScript-file that we can register on demand in our website and that can make use of the namespace Microsoft.Maps. In this module we start by defining the style of lines and polygons.

function GeoRSSModule(map) {
    var myFillColor = new Microsoft.Maps.Color(100,255,165,0);
    var myStrokeColor = new Microsoft.Maps.Color(200,255,165,0);
    var myStrokeThickness = 5;

    var myPolygonOptions={fillColor: myFillColor,
                         strokeColor: myStrokeColor,
                         strokeThickness: myStrokeThickness};
    var myPolylineOptions={strokeColor: myStrokeColor,
                           strokeThickness: myStrokeThickness};

Next we extend the Pushpin, Polyline and Polygon classes in the namespace Microsoft.Maps with properties that can hold the title and description for these objects. For Polylines and Polygons we also add properties that can hold the position where we want the InfoBox to appear.

    Microsoft.Maps.Pushpin.prototype.title = null;
    Microsoft.Maps.Pushpin.prototype.description = null;
    Microsoft.Maps.Polyline.prototype.title = null;
    Microsoft.Maps.Polyline.prototype.description = null;
    Microsoft.Maps.Polyline.prototype.anchorLat = null;
    Microsoft.Maps.Polyline.prototype.anchorLon = null;
    Microsoft.Maps.Polygon.prototype.title = null;
    Microsoft.Maps.Polygon.prototype.description = null;
    Microsoft.Maps.Polygon.prototype.anchorLat = null;
    Microsoft.Maps.Polygon.prototype.anchorLon = null;

A module can have one or more functions and for our module the main logic is implemented in the function ImportGeoRSS. Before we load the GeoRSS-feed we first remove all other entities from the map.

    this.ImportGeoRSS = function (MyFeed) {
        map.entities.clear();

Next we load the GeoRSS-feed.

        var xmlhttp = new XMLHttpRequest();
        xmlhttp.open("GET", MyFeed, false);
        xmlhttp.send();
        var xmlDoc = xmlhttp.responseXML;

In the following part we parse the XML-feed into objects of type Microsoft.Maps.Pushpin, Polyline and Polygon – including the additional properties that we prototyped before.

        var itemCount = xmlDoc.getElementsByTagName("item").length;
        var allLocs = new Array()

        for (i = 0; i <= itemCount - 1; i++) {
            var childNodeCount = xmlDoc.getElementsByTagName("item")[i].childNodes.length;
            var tagName = null;
            var geomType = null;
            var geom = null;
            var myTitle = null;
            var myDesc = null;
            var anchorLat = null;
            var anchorLon = null;
            for (j = 0; j <= childNodeCount - 1; j++) {
                tagName = xmlDoc.getElementsByTagName("item")[i].childNodes[j].nodeName;
                if (tagName in { 'georss:point': '', 'georss:line': '', 'georss:polygon': '' }) {
                    geomType = tagName;
                    geom = xmlDoc.getElementsByTagName("item")[i].childNodes[j].childNodes[0].nodeValue;
                }
                else if (tagName == "title") {
                    try {
                        myTitle = xmlDoc.getElementsByTagName("item")[i].childNodes[j].childNodes[0].nodeValue;
                    }
                    catch (err) {
                    }
                }
                else if (tagName == "description") {
                    try {
                        myDesc = xmlDoc.getElementsByTagName("item")[i].childNodes[j].childNodes[0].nodeValue;
                    }
                    catch (err) {
                    }
                }
            }
            var coords = new Array();
            coords = geom.split(" ");
            var thisLocs = new Array()

            var anchorCoord = null;
            if ((coords.length/2) % 2) {
                anchorCoord = coords.length / 2-1;
            }
            else {
                anchorCoord = coords.length / 2;
            }

            for (k = 0; k <= coords.length - 1; k = k + 2) {
                var thisLoc = new Microsoft.Maps.Location(coords[k], coords[k + 1]);
                thisLocs.push(thisLoc);
                allLocs.push(thisLoc);

                if (k == anchorCoord) {
                    anchorLat = coords[k];
                    anchorLon = coords[k + 1];
                }
            }

            var shape = null;
            switch (geomType) {
                case "georss:point":
                    shape = new Microsoft.Maps.Pushpin(thisLocs[0]);
                    break;
                case "georss:line":
                    shape = new Microsoft.Maps.Polyline(thisLocs, myPolylineOptions);
                    shape.anchorLat = anchorLat;
                    shape.anchorLon = anchorLon;
                    break;
                case "georss:polygon":
                    shape = new Microsoft.Maps.Polygon(thisLocs, myPolygonOptions);
                    shape.anchorLat = anchorLat;
                    shape.anchorLon = anchorLon;
                    break;
            }
            shape.title = myTitle;
            shape.description = myDesc;

We also attach an event to the object that will show an InfoBox with further information before we add the object to the map.

            pushpinClick = Microsoft.Maps.Events.addHandler(shape, 'click', showInfoBox);
            map.entities.push(shape);
        }

When all objects are added to the map we set the map-view to a zoom-level and centre-point that shows all objects.

        map.setView({ bounds: Microsoft.Maps.LocationRect.fromLocations(allLocs) });
    }
}

Finally we signal back to the map that the module is now loaded and trigger the execution of the callback-function.

Microsoft.Maps.moduleLoaded('GeoRSSModule');

Back to the Website

We had already prepared the website with a function to load the module but we still have to add the callback-function. In this callback-function we execute the ImportGeoRSS-function on a feed as specified in the text-box.

        function ModuleLoaded() {
            // Use the function provided by the newly loaded module
            var myModule = new GeoRSSModule(map);
            myModule.ImportGeoRSS(document.getElementById("txtGeoRSS").value);
            collectionInfoBox = new MM.EntityCollection;
            map.entities.push(collectionInfoBox);
        }

Finally we add some code to handle the InfoBoxes and we’re done.

image

The complete source of our website is listed below. To see the code in action follow this link and select “GeoRSS” under the accordion-pane “Miscellaneous”.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
    <title></title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0"></script>
    <script type="text/javascript">
        var map = null;
        var MM = Microsoft.Maps;
        var infobox = null;
        var collectionInfoBox = null;

        function GetMap() {
            var options = { credentials: "YOUR BING MAPS KEY",
                            enableClickableLogo: false,
                            enableSearchLogo: false,
                            mapTypeId: Microsoft.Maps.MapTypeId.road,
                            center: new MM.Location(51.4611794944098, -0.9259434789419174),
                            zoom:17 };
                        map = new MM.Map(document.getElementById('mapDiv'), options);

                        // Hide the info box when the map is moved.
                        MM.Events.addHandler(map, 'viewchange', hideInfobox);
        }

        function LoadModule(){
            // Register and load a new module
            MM.registerModule("GeoRSSModule", "./GeoRSSModule.js");
            MM.loadModule("GeoRSSModule", { callback: ModuleLoaded });
        }

        function ModuleLoaded() {
            // Use the function provided by the newly loaded module
            var myModule = new GeoRSSModule(map);
            myModule.ImportGeoRSS(document.getElementById("txtGeoRSS").value);
            collectionInfoBox = new MM.EntityCollection;
            map.entities.push(collectionInfoBox);
        }

        //Display InfoBox
        function showInfoBox(e) {
            if (e.targetType == "pushpin") {
                collectionInfoBox.clear();
                infobox = new MM.Infobox(e.target.getLocation(), { title: e.target.title, description: e.target.description, offset: new MM.Point(0, 30), visible: true });
                collectionInfoBox.push(infobox);
            }
            else if (e.targetType == "polygon" || e.targetType=="polyline") {
                collectionInfoBox.clear();
                infobox = new MM.Infobox(new MM.Location(e.target.anchorLat, e.target.anchorLon), { title: e.target.title, description: e.target.description, offset: new MM.Point(0, 0), visible: true });
                collectionInfoBox.push(infobox);
            }
        }

        function hideInfobox(e) {
            try {
                infobox.setOptions({ visible: false });
            }
            catch (err) {
            }
        }
      </script>
</head>
<body onload="GetMap();">
    <div id='mapDiv' style="position:relative; width:800px; height:600px;"></div><br />
    <a>GeoRSS-Feed</a><input id="txtGeoRSS" type="text" value="MSFT_London.xml" />
    <input id="Button2" type="button" value="Import" onclick="LoadModule()" />
</body>
</html>

Tip: Importing Bing Maps Collections in your own Website

On the Bing Maps consumer site you can create your own collections. If you wanted to use such a collection in your own websites you can simply use the “My Places Editor” to export to GeoRSS and then use the module above to import it.

image

Posted in AJAX, Bing Maps | Tagged , , | Leave a comment

Dynamic Tile-Layers with Windows Azure and SQL Azure

Introduction

Recently I had the honour to support our friends and colleagues in Japan in the development of a service that shows the status of roads following the tragic earthquake and the even more disastrous tsunami. Setting up this service in the cloud took only a few hours and in the following I will walk through the components of this solution and the thoughts the led to this particular implementation path.

You will find the sample website here.

image

I have also created a Bing Map App here.

image

Components and Reasoning to Choose this Implementation Path

Bing Maps is a cloud-based web mapping service that guarantees high availability and scalability. It is accessible through a consumer site and through a set of APIs which include SOAP and REST web services as well as more interactive AJAX and Silverlight Controls. The AJAX and the Silverlight controls allow you to overlay your own data in vector format or rasterized into tile-layers. Since version 7 the AJAX control is not only supported on the PC and Mac but also on the iPhone, Android and Blackberry browsers. You will find a list of supported browsers here.
In order to remain independent from browser plugins and also to support multiple platforms we settled for the Bing Maps AJAX Control version 7.

When you have a large amount of vector data it might be advisable to rasterize these data into tile layers before overlaying them on your web mapping solution. In this case the source data (provided by Honda) was available as a KMZ-file. The uncompressed size of this data is 40 MB and even compressed it is still 8.7 MB. Downloading such an amount of data would take a while. Particularly if you also intend to support mobile devices it might not be the best option. In addition you have to consider the amount of objects that you intend to render. In this example the data contained 53,566 polylines and rendering would take a while, deteriorating the user experience even further.
Therefore we decided to rasterize the data and overlay them as a tile layer.

After the decision was made to rasterize the data and visualize them as a tile layer on Bing Maps the question was, if we create a static tile layer or rasterize the data on the fly. In this example the data covers 75,628 road-kilometres and an area of 156,624 square kilometres. Creating a complete tile-set down to level 19 would result in 61,875,766 tiles. Rendering all these tiles would take many hours and daily updates would not have been practical. On the other side it is not likely that all these tiles will be needed. There might be regions that people wouldn’t look at at all zoom-levels. In fact it turned out that only a few thousand different tiles are being retrieved every day.
Therefore we decided to set up a solution that creates the tiles on demand and implement a tile-cache in order to enhance performance.

When we started the project we had no idea what traffic we had to expect. Therefore we wanted to be able to scale he solution up and down depending on the traffic. We also wanted to make sure that data and services are held close to the end-users in order to keep latency times low.
Therefore we chose to deploy the solution on Windows Azure and enable the Content Delivery Network (CDN) for the tile cache. Vector data will be stored as spatial data types in SQL Azure.

The last ingredient was the Spatial ETL tool that allows us to load the KMZ-file into SQL Azure and we chose Safe FME for this task. Safe supports hundreds of spatial data formats – including KML/KMZ and SQL Server Spatial data formats.

On a high level the components of the proposed solution look like this.

image

The solution is accessible as a Map App from the Bing Maps consumer website as well as a “regular” website from PC, Mac and mobile devices.

image

Loading the Data into SQL Azure

As mentioned above we use Safe FME to load the data into SQL Azure. We only need an OGCKML-reader and the MSSQL_SPATIAL writer.

image

At the reader we expose the kml_style_url Format Attribut because this holds the information that allows us to distinguish between road segments that were open to traffic and those where traffic had not been observed.

image

On the writer we create a new User Attribute Color and link the kml_style_url from the reader to it.

image

Also on the writer we define under the tab parameters a SQL script for the Spatial Index Creation.

image

alter table JapanRoads0319 add MyID int identity;
ALTER TABLE JapanRoads0319 ADD CONSTRAINT PK_JapanRoads0319 PRIMARY KEY CLUSTERED (MyID);
CREATE SPATIAL INDEX SI_JapanRoads0319 ON JapanRoads0319(GEOM) USING GEOMETRY_GRID WITH( BOUNDING_BOX  = ( xmin  = 122.935256958008, ymin  = 24.2508316040039, xmax  = 153.965789794922, ymax  = 45.4863815307617), GRIDS  = ( LEVEL_1  = MEDIUM, LEVEL_2  = MEDIUM, LEVEL_3  = MEDIUM, LEVEL_4  = MEDIUM), CELLS_PER_OBJECT  = 16);

In the navigator on the left hand side of the FME workbench we also define a SQL statement that runs after we completed the loading process and validates potentially invalid geometries.

image

update JapanRoads0319 set geom=geom.MakeValid();

Now that we have the data in SQL Azure we can already start to analyse and find out how many road-kilometres are open to traffic and on how many road-kilometres traffic has not been verified, e.g.

SELECT color, sum(geography::STGeomFromWKB(GEOM.STAsBinary(), 4326).STLength())/1000 as [1903] from JapanRoads0319 group by color

If we keep doing this with each of the daily data updates the results allow us to create graphs showing the improvements over time.

image

Tile Rendering and Caching

The tile rendering and cache handling is implemented in a Generic Web Handler (SqlTileServer.ashx). When you overlay a tile layer on top of Bing Maps the control will send for each tile in the current map view a HTTP-GET-Request to a virtual directory or in our case a web service. The request passes the quadkey of the tile as a parameter. In our service we will need to

  • determine if we have already cached the tile and if so retrieve it from the cache and return it to the map
  • If we haven’t cached the tile
    • determine the bounding box of the tile. This is where we use a couple of functions as listed in this article.
    • query the database and find all spatial objects that intersect this bounding box.
    • create a PNG image
    • write this image to cache and also
    • return the image to the map

In order to keep the communication between SQL Azure and the web service efficient we transport the data as binary spatial data types. This requires then of course that we decode the binary data in the web service. Fortunately the SQL Server Spatial team provides us with the means to do that. As part of the SQL Server 2008 R2 Feature Pack you will find the “Microsoft System CLR Types for SQL Server 2008 R2” which include the spatial data types and spatial functions and can be integrated in .NET applications and services even if you don’t have SQL Server installed.

image

Note: The package is available in 32bit, 64bit and Itanium versions. Windows Azure is based on Windows Server 2008 R2 64bit. If you want to deploy your solution on Windows Azure make sure you use the 64bit version of the “Microsoft System CLR Types for SQL Server 2008 R2”.

In the database we have a stored procedure that executes our spatial query.

CREATE PROCEDURE [dbo].[GetJapanRoads]
@nwLon nvarchar(10),
@nwLat nvarchar(10),
@seLon nvarchar(10),
@seLat nvarchar(10)
AS
BEGIN
DECLARE @bbox geometry;
SET @bbox = geometry::STPolyFromText('POLYGON(('+@nwLon+' '+@nwLat+', '+@nwLon+' '+@seLat+', '+@seLon+' '+@seLat+', '+@seLon+' '+@nwLat+', '+@nwLon+' '+@nwLat+'))', 4326);
SELECT GEOM, Color
FROM JapanRoads0319
WHERE (GEOM.STIntersects(@bbox) = 1)
END;

In order to convert geographic coordinates into pixel coordinates for the Bing Maps Tile System we use a couple of functions as listed in this article.

Since we want to cache the tiles in the Windows Azure Blob Storage we will also need to add references to the assemblies

  • Microsoft.WindowsAzure.ServiceRuntime and
  • Microsoft.WindowsAzure.StorageClient

Both assemblies are part of the Windows Azure SDK and the Windows Azure Tools for Microsoft Visual Studio, which includes the Windows Azure SDK.

Once we have the stored procedure in SQL Azure, the 3 assemblies (2 for Azure, 1 for SQL Server Spatial) and the helper functions for Bing Maps in place the code for the tile rendering looks like this.

Imports System.Web
Imports System.Web.Services
Imports Microsoft.SqlServer.Types
Imports System.Data.SqlClient
Imports System.Drawing
Imports System.IO
Imports System.Drawing.Drawing2D
Imports Microsoft.WindowsAzure.StorageClient
Imports Microsoft.WindowsAzure
Imports Microsoft.WindowsAzure.ServiceRuntime
Imports System.Net

Public Class SqlTileServer
Implements System.Web.IHttpHandler

Public quadkey As String
Public lvl As Integer
Public tileX As Integer
Public tileY As Integer
Public nwX As Integer
Public nwY As Integer
Public nwLon As Double
Public nwLat As Double
Public seLon As Double
Public seLat As Double
Public myPixelX As Integer
Public myPixelY As Integer
Public myBaseUri As String = http://YOUR_WINDOWS_AZURE_ACCOUNT.blob.core.windows.net/YOUR_WINDOWS_AZURE_CONTAINER
Public myBaseUriCDN As String = http://YOUR_WINDOWS_AZURE_CDN_TOKEN.vo.msecnd.net/YOUR_WINDOWS_AZURE_CONTAINER
Public myBlobUri As String
Public isCached As Boolean = False

Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
'Fetch URL-Parameters
quadkey = context.Request.Params("quadkey")

'Determine Zoom-Level
lvl = quadkey.Length

'Is file cached?
myBlobUri = myBaseUriCDN + "/" + quadkey + ".png"

Try
Dim req As WebRequest = WebRequest.Create(myBlobUri)
Dim response As HttpWebResponse = DirectCast(req.GetResponse(), HttpWebRe-sponse)
Dim myImage As New Bit-map(System.Drawing.Image.FromStream(response.GetResponseStream))
isCached = True
WritePngToStream(myImage, context.Response.OutputStream)
Catch ex As Exception
isCached = False
'Get TileXY-Coordinates
QuadKeyToTileXY(quadkey, tileX, tileY, lvl)

'Get PixelXY of the North-West Corner of the tile
TileXYToPixelXY(tileX, tileY, nwX, nwY)

'Get Latitude and Longitude of the North-West Corner
PixelXYToLatLong(nwX, nwY, lvl, nwLat, nwLon)
PixelXYToLatLong(nwX + 256, nwY + 256, lvl, seLat, seLon)

'Retrieve Database Setting from web.config
Dim settings As ConnectionStringSettings = ConfigurationManag-er.ConnectionStrings("azure")

'Open a connection to the database
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("nwLon", nwLon))
cmd.Parameters.Add(New SqlParameter("nwLat", nwLat))
cmd.Parameters.Add(New SqlParameter("seLon", seLon))
cmd.Parameters.Add(New SqlParameter("seLat", seLat))

'Specify the stored procedure name as the command text
cmd.CommandText = "GetJapanRoads"

'Create a new image
Dim myBitmap As New Bitmap(256, 256, Imaging.PixelFormat.Format32bppArgb)

'Read data and draw image
Dim myReader As SqlDataReader = cmd.ExecuteReader()
While myReader.Read()
'Get the Geometry
Dim myGeom As SqlGeometry = myReader(0)

'Deteremine the number of Geometries in the object
Dim numGeom As Integer = myGeom.STNumGeometries
For j = 1 To numGeom
Dim curGeom As SqlGeometry = myGeom.STGeometryN(j)
Dim numPoints As Integer = curGeom.STNumPoints
Dim myPointArray(numPoints - 1) As Point
For i = 1 To numPoints
Dim myPoint As SqlGeometry = curGeom.STPointN(i)
Dim myLon As Double = myPoint.STX
Dim myLat As Double = myPoint.STY
LatLongToPixelXY(myLat, myLon, lvl, myPixelX, myPixelY)
myPointArray(i - 1) = New Point(myPixelX - nwX, myPixelY - nwY)
Next

'Draw the Graphics
Dim g As Graphics = Graphics.FromImage(myBitmap)
Dim myBrush As New SolidBrush(Color.Transparent)
Dim myPen As Pen
Select Case myReader(1).ToString
Case "#blue"
myPen = New Pen(Brushes.Green)
Case Else
myPen = New Pen(Brushes.Gray)
End Select
myPen.Width = 3
g.DrawLines(myPen, myPointArray)
Next
End While
myReader.Close()
myConn.Close()
WritePngToStream(myBitmap, context.Response.OutputStream)
End Try
End Sub

Private Sub WritePngToStream(ByVal image As Bitmap, ByVal outStream As Stream)
Dim writeStream As New MemoryStream()
image.Save(writeStream, Imaging.ImageFormat.Png)
If isCached = False Then
writeStream.Seek(0, SeekOrigin.Begin)
Dim MyBlob = Me.GetContainer().GetBlobReference(quadkey + ".png")
MyBlob.Properties.ContentType = "image/x-png"
MyBlob.UploadFromStream(writeStream)
End If
writeStream.WriteTo(outStream)
image.Dispose()
End Sub

We also need a helper function to access the Windows Azure Blob Storage.

Private Sub EnsureContainerExists()
Dim container = GetContainer()
container.CreateIfNotExist()
Dim permissions = container.GetPermissions()
permissions.PublicAccess = BlobContainerPublicAccessType.Container
container.SetPermissions(permissions)
End Sub

Private Function GetContainer() As CloudBlobContainer
' Get a handle on account, create a blob storage client and get container proxy
Dim account = CloudStorageAc-count.Parse("DefaultEndpointsProtocol=http;AccountName=YOUR_WINDOWS_AZURE_ACCOUNT_NAME;AccountKey=YOUR_WINDOWS_AZURE_ACCOUNT_KEY")
Dim client = account.CreateCloudBlobClient()
Return client.GetContainerReference("YOUR_CONTAINER")
End Function

Calling the Service from Bing Maps

Adding a tile layer in Bing Maps is very simple. Below you find the source code for a complete web page that calls our service.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Bing Maps - v7.0</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0&mkt=ja-JP"></script>
<script type="text/javascript">
var map = null;
var MM = Microsoft.Maps;
function GetMap() {
map = new MM.Map(document.getElementById("myMap"), {
credentials: "YOUR_BING_MAPS_KEY",
center: new MM.Location(38.80131086471941, 139.5055539160967),
mapTypeId: "r",
zoom: 8,
enableClickableLogo: false,
enableSearchLogo: false
});

map.entities.push(
new MM.TileLayer({
mercator: new MM.TileSource({
uriConstructor: http://YOUR_WINDOWS_AZURE_ACCOUNT.cloudapp.net/BM-AJ-Japan/SqlTileServer.ashx?quadkey={quadkey}
}), opacity: .7
}));
}
</script>
</head>
<body onload="GetMap();">
<div id='myMap' style="position:absolute; top:0px; left:0px; width:100%; height:100%;"></div><br />
</body>
</html>

And that’s already it. In a very short time we were able to develop a service and deploy it as a scalable and highly available service in the cloud.

One final tip: If you also plan to use this service from other Silverlight applications in domains outside your Windows Azure environment you will need to publish a ClientAccessPolicy.xml file to the root of your Windows Azure hosted service. In our case that was necessary since we will access the service not only from the website itself but also from a Bing Map App.

Posted in AJAX, Bing Maps, Spatial, SQL Azure, Windows Azure | Tagged , , , , , , | 4 Comments

Bing Maps AJAX Control version 7 – Adding Vector Data

After a quick overview on the new features in the Bing Maps AJAX Control v7 as well as a look at loading the map and displaying localized labels we are now moving on to the various options to add your own data. As before we will compare version 6.3 and version 7 and you will find live examples with source code on my Windows Azure site.

In version 6.3 vector data can be dropped as VEShape-objects of type Pushpin, Polyline or Polygon into the map directly or grouped into VEShapeLayer.

image

The concept is very much the same in version 7 but the namespace and object-model is different. In version 7 objects can as well be dropped as entities into the map directly or grouped into EntityCollections. In v6.3 VEShape-objects and VEShapeLayer had to be vector data while raster data were added as VETileLayer. In v7 vector and raster data are now all entities and we only distinguish the entity-type Pushpin, Polyline, Polygon or TileLayer.

image

Adding a Default Pushpin in v6.3

To add a Pushpin in v6.3 we need at a minimum the latitude and longitude where we want to place it on the map.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Bing Maps - v6.3</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.3"></script>

<script type="text/javascript">
var map = null;

function GetMap() {
map = new VEMap('myMap');

var mapOptions = new VEMapOptions();
mapOptions.DashboardColor = 'black';
mapOptions.UseEnhancedRoadStyle = true;
map.LoadMap(new VELatLong(51.46117933094501, -0.9259434789419174), 18, 'h', false, VEMapMode.Mode2D, true, 0, mapOptions);

var pushpin = new VEShape(VEShapeType.Pushpin, new VELatLong(51.46117933094501, -0.9259434789419174));
map.AddShape(pushpin);
}
</script>
</head>
<body onload="GetMap();">
<div id='myMap' style="position:absolute; top:0px; left:0px; width:100%; height:100%;"></div>
</body>
</html>

Adding a Default Pushpin in v7

The basic idea is the same instead of “VEShape(VEShapeType.Pushpin, new VELatLong(lat, lon)” we use now Microsoft.Maps.Pushpin(new Microsoft.Maps.Location(lat, lon)

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Bing Maps - v7.0</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0"></script>

<script type="text/javascript">
var map = null;
var MM = Microsoft.Maps;

function GetMap() {
map = new MM.Map(document.getElementById("myMap"), {
credentials: "Your Bing Maps Key",
center: new MM.Location(51.46117933094501, -0.9259434789419174),
mapTypeId: "a",
zoom: 18
});

var pushpin = new MM.Pushpin(new MM.Location(51.46117933094501, -0.9259434789419174));
map.entities.push(pushpin);
}
</script>
</head>
<body onload="GetMap();">
<div id='myMap' style="position:absolute; top:0px; left:0px; width:100%; height:100%;"></div>
</body>
</html>

Btw: I think that the new default Pushpin is definitely an improvement Smile

image

Customizing Pushpins in v6.3

Customizing Pushpins in v6.3 is quite simple with regards to changing icons but it would be a bit more fiddling to add labels to the pushpin or next to it (see for example Keith Kinnan’s blog). To use a custom icon we only need to add 1 line of code:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Bing Maps - v6.3</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.3"></script>

<script type="text/javascript">
var map = null;

function GetMap() {
map = new VEMap('myMap');

var mapOptions = new VEMapOptions();
mapOptions.DashboardColor = 'black';
mapOptions.UseEnhancedRoadStyle = true;
map.LoadMap(new VELatLong(51.46117933094501, -0.9259434789419174), 18, 'h', false, VEMapMode.Mode2D, true, 0, mapOptions);

var pushpin = new VEShape(VEShapeType.Pushpin, new VELatLong(51.46117933094501, -0.9259434789419174));
pushpin.SetCustomIcon('./IMG/pushpin.png');
map.AddShape(pushpin);
}
</script>
</head>
<body onload="GetMap();">
<div id='myMap' style="position:absolute; top:0px; left:0px; width:100%; height:100%;"></div>
</body>
</html>

Customizing Pushpins in v7

In v7 you can define a much larger variety of PushpinOptions. Text and textOffset for example can simply be defined as a PushpinOption. So is the icon that you want to use on the map.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Bing Maps - v7.0</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0"></script>

<script type="text/javascript">
var map = null;
var MM = Microsoft.Maps;

function GetMap() {
map = new MM.Map(document.getElementById("myMap"), {
credentials: "Your Bing Maps Key",
center: new MM.Location(51.46117933094501, -0.9259434789419174),
mapTypeId: "a",
zoom: 18
});

var pushpinOptions = { icon: './IMG/pushpin.png' };
var pushpin = new MM.Pushpin(new MM.Location(51.46117933094501, -0.9259434789419174), pushpinOptions);
map.entities.push(pushpin);
}
</script>
</head>
<body onload="GetMap();">
<div id='myMap' style="position:absolute; top:0px; left:0px; width:100%; height:100%;"></div>
</body>
</html>

Adding an InfoBox or Popup in v6.3

Now here comes the first major difference: In version 6.3 you can simply set additional Pushpin-properties such as a title or a description. Once you set at least a title, an InfoBox pops up when you mouse-over the Pushpin. Further customization of this InfoBox is possible by defining styles in the Cascading Style Sheet and clearing the default InfoBox styles.

In the example below we will increase the size of the InfoBox and load a Photosynth-collection in the description area.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Bing Maps - v6.3</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.3"></script>
<style type="text/css">
.customInfoBox-previewArea {
width:500px;
height:420px;
}
</style>

<script type="text/javascript">
var map = null;

function GetMap() {
map = new VEMap('myMap');

var mapOptions = new VEMapOptions();
mapOptions.DashboardColor = 'black';
mapOptions.UseEnhancedRoadStyle = true;
map.LoadMap(new VELatLong(32.362980181127874, -64.71483707427978), 17, 'h', false, VEMapMode.Mode2D, true, 0, mapOptions);

//Set Style for InfoBox
map.ClearInfoBoxStyles();

var pushpin = new VEShape(VEShapeType.Pushpin, new VELatLong(32.362980181127874, -64.71483707427978));
pushpin.SetCustomIcon('./IMG/pushpin.png');
pushpin.SetTitle('Martello Tower');
pushpin.SetDescription('<iframe frameborder=0 src="http://photosynth.net/embed.aspx?cid=ba12ab48-6899-4d7f-b28c-624f5f7ff4f0&delayLoad=false&slideShowPlaying=false" width="500" height="400"></iframe>');
map.AddShape(pushpin);
}
</script>
</head>
<body onload="GetMap();">
<div id='myMap' style="position:absolute; top:0px; left:0px; width:100%; height:100%;"></div>
</body>
</html>

Adding an InfoBox or Popup in v7

In version 7 there is at present no build-in mechanism to create InfoBoxes or pop-ups but here is how it could be done.

  1. we extend the properties of the Pushpin-object using the JavaScript prototype function in order to provide “title” and “description” for a Pushpin object
  2. We create a div-element that can we positioned, hidden or shown to show the InfoBox
  3. We add an event-handler to the map that opens the InfoBox whenever we click on the pin and another one that hides the InfoBox again when we change the map-view.

Admittedly this is a bit more effort in v7 than it is in v6.3 but once the framework is in place it can be re-used.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Bing Maps - v7.0</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0"></script>

<script type="text/javascript">
var map = null;
var MM = Microsoft.Maps;

function GetMap() {
map = new MM.Map(document.getElementById("myMap"), {
credentials: "Your Bing Maps Key",
center: new MM.Location(32.362980181127874, -64.71483707427978),
mapTypeId: "a",
zoom: 17
});

var pushpinOptions = { icon: './IMG/pushpin.png' };
var pushpin = new MM.Pushpin(new MM.Location(32.362980181127874, -64.71483707427978), pushpinOptions);

//extend the pushpin class to store information for popup
MM.Pushpin.prototype.title = null;
pushpin.title = "Martello Tower";
MM.Pushpin.prototype.description = null;
pushpin.description = '<iframe frameborder=0 src="http://photosynth.net/embed.aspx?cid=ba12ab48-6899-4d7f-b28c-624f5f7ff4f0&delayLoad=false&slideShowPlaying=false" width="500" height="400"></iframe>';

//add a click event
pushpinClick = MM.Events.addHandler(pushpin, 'click', displayEventInfo);

//close the infobox when the map is panned or zoomed
MM.Events.addHandler(map, 'viewchangestart', closeInfoBox);

map.entities.push(pushpin);
}

function displayEventInfo(e) {
if (e.targetType = "pushpin") {
var pix = map.tryLocationToPixel(e.target.getLocation(), MM.PixelReference.control);
var ibTitle = document.getElementById('ibTitle');
ibTitle.innerHTML = e.target.title;
var ibDescription = document.getElementById('ibDescription');
ibDescription.innerHTML = e.target.description;
var infobox = document.getElementById('infoBox');
infobox.style.top = (pix.y - 60) + "px";
infoBox.style.left = (pix.x + 20) + "px";
infoBox.style.visibility = "visible";
document.getElementById('myMap').appendChild(infoBox);
}
}

function closeInfoBox() {
var infobox = document.getElementById('infoBox');
infoBox.style.visibility = "hidden";
}
</script>
</head>
<body onload="GetMap();">
<div id='myMap' style="position:absolute; top:0px; left:0px; width:100%; height:100%;"></div><br />
<div id='infoBox' style="visibility:hidden; position:absolute; top:0px; left:0px; max-width:550px; z-index:10000; font-family:Verdana; font-size:12px">
<img src="IMG/leftbeak.png" alt="Left Beak" style="position:absolute;top:10px; left:0px;" />
<table style="width:510px; border:medium solid Orange; position:absolute;top:0px; left:22px; background-color:White">
<tr style="width:510px">
<td style="width:510px"><b id='ibTitle'></b></td>
<td align="right" valign="top"><img src="IMG/close.png" alt="close" onclick="closeInfoBox()" /></td>
</tr>
<tr>
<td colspan="2"><a id='ibDescription'></a></td>
</tr>
</table>
</div> 
</body>
</html>

image

As mentioned before you will find these and other examples – including Polylines and Polygons here.

Previous Postings on the Bing Maps AJAX Control v7

Posted in AJAX, Bing Maps | Tagged , , , , , | 4 Comments

Bing Maps AJAX Control version 7 – Loading the Map & Localized Labels

In a previous blog post I had introduced some of the new features in the Bing Maps AJAX Control version 7. I had mentioned that we are very excited about this new version but also that it is a complete redesign with breaking changes. Don’t worry we have not planned for the end-of-life of version 6.3 yet and when we make an announcement it will be with ample time to migrate your applications. However, since v7 is indeed new and different I would like to start with a series of blog posts that walk you through some of the more frequently used functions in comparison to v6.3. All blog posts will be accompanied by interactive examples which demonstrate functionality and provide access to the source code. You will find this example on my Windows Azure account here.

Version 6.3
When you load the Bing Maps AJAX Control in version 6.3 you can add market-parameters for localized labels as well as various options for example to set the dashboard-colour and to use the new map style.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Bing Maps - v6.3</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.3&mkt=de-DE"></script>

<script type="text/javascript">
var map = null;

function GetMap() {
map = new VEMap('myMap');

var mapOptions = new VEMapOptions();
mapOptions.DashboardColor = 'black';
mapOptions.UseEnhancedRoadStyle = true;
map.LoadMap(new VELatLong(54.87350326912944, 15.333815098500053), 4, 'r', false, VEMapMode.Mode2D, true, 0, mapOptions);
}
</script>
</head>
<body onload="GetMap();">
<div id='myMap' style="position:absolute; top:0px; left:0px; width:100%; height:100%;"></div><br />
</body>
</html>

Version 7.0
In version 7 you have the same options to provide market-parameters for localzed labels but namespace and object structure have changed. The Bing Maps Key which was previously technically not enforced is now a mandatory requirement. If you don’t have such a Bing Maps Key yet you can get one at the Bing Maps Account Centre.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Bing Maps - v7.0</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0&mkt=de-DE"></script>

<script type="text/javascript">
var map = null;
var MM = Microsoft.Maps;

function GetMap() {
map = new MM.Map(document.getElementById("myMap"), {
credentials: "Your Bing Maps Key",
center: new MM.Location(54.87350326912944, 15.333815098500053),
mapTypeId: "r",
zoom: 4
});
}
</script>
</head>
<body onload="GetMap();">
<div id='myMap' style="position:absolute; top:0px; left:0px; width:100%; height:100%;"></div><br />
</body>
</html>


 

Posted in AJAX, Bing Maps | Tagged , , , , , | 5 Comments

Did you know…

… if you lost your Windows Phone 7 you can use Bing Maps & http://windowsphone.live.com to

  • map its location
    image
  • Ring it
  • Lock it
  • Erase it

Posted in AJAX, Bing Maps, Windows Phone 7 | Tagged , , , | Leave a comment

Upcoming Bing Maps Events

The Bing Maps team is on the road again. All events are half business / overview and half technical.

Next week, on the 22nd and 23rd of November, we have 2 days of customer & partner events in the Microsoft offices in Paris.

37 quai Prés Roosevelt, 92130 Issy les Moulineaux

The week thereafter we travel to Finland and Sweden. On Tuesday, the 30th of November we have a joint event with our partner Affecto in the Affecto offices in Helsinki.

Atomitie 2 C, 00370 Helsinki

You can still register here.

On Thursday, the 2nd of December we have a joint event with our partner Sweco in the Sweco offices in Stockholm.

Gjörwellsgatan 28, Stockholm

You can still register here.

Our partners, my colleagues and I are looking forward to see you there.

Posted in Bing Maps, Events | Tagged , , , , , , | Leave a comment

Bing Maps AJAX Control version 7 – What’s New?

Last week we launched the Bing Maps AJAX Control version 7.0. I believe that this is a really exciting release with better performance and great new features. However, this is a complete re-design with breaking changes. Therefore it is not possible to upgrade from previous releases by just changing the version number in the JavaScript reference. Over the next days I will post a series of articles that walk you through the most frequently used features but first let’s have a look at what’s new.

Performance 

The control supports both HTML5/CSS3 and HTML4/CSS2. It will automatically detect if the browser is capable of supporting HTML5/CSS3 and leverage HTML5/CSS3 features to enhance performance if possible. However, even in HTML4/CSS2 the performance is significantly improved. The below graph shows rendering times for an increasing number of points in various Bing Maps controls using the Internet Explorer 8.

image

Size

Version 7 is much slimmer than it’s predecessors. In version 6.3 we had already introduced a core-mode which stripped out a lot of features from the AJAX control to speed up the initial loading. In version 7 we have improved on this concept by having a richer core set of features with better performance and yet smaller size.

image

(All sizes are compressed sizes and include secondary JavaScript-, CSS and image-files)

Browser Support

On the Desktop v7 supports now officially the latest versions of Firefox, Safari and Chrome as well as Internet Explorer 7, 8 and 9. Internet Explorer 6 is not officially supported anymore but it should still work. Additionally the iPhone 3GS/4G browser is now supported as well.

Animated Zoom

Zooming in the map is now animated in a similar way as the DeepZoom-effect in Silverlight.

Panning over the International Date Line

Panning over the international date line was previously available in the Silverlight control but not in the AJAX control and that led to quite nasty side-effects if you wanted to visualize for example a flight path from Sydney to Los Angeles. v7 now also supports panning over the international date line in the AJAX control.

image

Enhanced Bird’s Eye View

In previous releases of the AJAX control Bird’s Eye imagery were rendered as scenes and when you panned over the edges from one scene to the next you saw black areas before the next scene as loaded. The enhanced Bird’s Eye View allows a seamless panning over these edges just like it is in Silverlight.

image

Additionally the enhanced Bid’s Eye View synthesizes oblique aerial images at lower zoom-levels which allows you to zoom further out. In the screenshot below you see for example that with the chosen map size in version 6.3 you could not zoom out far enough to see the entire area of the Tower of London. In version 7 the synthesized view is available everywhere and also on lower zoom-levels – just as it is in Silverlight.

image

Map Styles

The new style for roadmaps was already available on an opt-in basis for the AJAX control version 6.3 (see previous blog post here). This style is now the only style for roadmaps in version 7 with the exception of the UK and Japan.

image

In addition you can display Ordnance Survey and Collins Bartholomew maps in the UK if you set the market parameter to en-GB, e.g.

<script type=”text/javascript”
                src=”http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx
                   ?v=7.0&mkt=en-GB”>
</script>

image

Map Types

Besides the new styles mentioned above there is now also a Map Type automatic which automatically chooses a style for you based on the zoom-level.

image

More New Features

  • New Navigation Control
  • Version 7 now requires a Bing Maps Key. If you haven’t done so yet, sign-up for a free developer key at the Bing Map Account Centre.
  • Geocoding and Routing are not part of the AJAX control anymore and we suggest using the Bing Maps REST Web Service to implement these capabilities.

As mentioned before I will post several articles over the coming days to walk you through some of the most frequently used features but if you can’t wait you will find the SDK online here. A downloadable version is also available as CHM and PDF.

Posted in AJAX, Bing Maps | Tagged , , , , | 6 Comments