Infinite horizontal panning for Maps – Silverlight DeepZoom

The HDView team released a gem of a sample app this week from ICE that amongst other things provide a solution for showing 360 degree panoramas. The solution uses three MSI controls to provide a seamless panning experience.

Now what could be a bigger 360 panorama then the Earth itself?

Map360 

Live Sample: http://deepzoom.soulclients.com/map360/

Straight away the benefits become clear:

  • We can centre the map anywhere on the World. (I can hear New Zealand cheering from here).
  • We can show flight paths and shipping routes across the Pacific Ocean
  • We can provide a smooth zooming experience from the world level without be shunted over to Africa.

The example is running with the tiles from the awesome open source project OpenStreetMap, I highly recommend you take a look at what they are doing to provide a free set of world data, updated by people like you.

Download the code for the example above from here:

http://deepzoom.soulclients.com/map360/map360example.zip

Susannah Raub to speak at GGD Brisbane Dinner 2

There’s less than a week till our 2nd Girl Geek Dinner Brisbane and this month we’re fortunate to have Susannah Raub speaking at our dinner. Google have been kind enough to send her up to Brisbane and has offered to put $20/head towards the dinner.

SusannahRaub Susannah Raub joined Google in September 2004 as a software engineer in Mountain View, California.  She worked on many features of Google Desktop until February 2006 when she moved to New York and joined the Google Maps – Search

At Google, she has been involved with the global engineering mentoring program, the development of APAC offices, and the AU/NZ Anita Borg Scholarship program.  Susannah graduated from Brown University in Providence, Rhode Island with an Sc.B. in Mathematics-Computer Science and a broad base in urban studies, European languages, and economics.

Code Walk through for DeepEarth

So you’re interested in Silverlight and Virtual Earth, you’ve downloaded the DeepEarth code but are now completely lost? Well check out two 20min videos to walk you through the code and get you started. Part1 and Part2.

The videos essentially walk you through the solutions, pointing out what each of the projects does, the structure and how to get the right project running.

Let me know if you have any comments.

Load Virtual Earth on Demand

The Virtual Earth JavaScript control is not small weighing in at 217KB when compressed in Version 6.2. When you require the rich experience of Virtual Earth this size is not an issue, the control is aimed at broadband users and within a few moments of interacting with the map you can easy exceed this with the rich imagery being loaded on demand. But what if your web page only needs to show the Virtual Earth map when a user asks for it or you just want your page to load super fast? You need to load Virtual Earth on demand.

ajax-loader

See an example here: http://www.soulsolutions.com.au/examples/VE62/loadondemand.htm (view source for full code)

In the recent release of the control, Version 6.2 a new feature was introduced to allow for a function of your choice to be called once the script was loaded. To achieve this you append the parameter onScriptLoad to the VE API url like so:

http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.2&onScriptLoad=yourfunctionname

This is the key as detecting scripts being loaded across different browser is very troublesome. Lets build an example that works in Firefox, IE and even Chrome. We will make a trivial HTML page that does not reference the VE JavaScript control. It will contain a link that will run a JavaScript function to dynamically add 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>Load Virtual Earth on Demand</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <script type="text/javascript">
        var loaded = false;
        var map = null;
        function onscriptload() {
        //get rid of our load animation and load the map
        document.getElementById("myMap").style.background = "";
        map = new VEMap('myMap');
        map.LoadMap(new VELatLong(-27.47, 153.03), 16, VEMapStyle.Hybrid);
        }
        function loadVEAPI() {
        if (!loaded) {
        loaded = true;
        //set a nice animated gif to show the map is loading
        document.getElementById("myMap").style.background = "url(ajax-loader.gif) center center no-repeat";
        if (!(window.attachEvent)) {
        appendJS("http://dev.virtualearth.net/mapcontrol/v6.2/js/atlascompat.js");
        }
        appendJS("http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.2&onScriptLoad=onscriptload");
        }
        }
        function appendJS(filename) {
        var fileref = document.createElement('script');
        fileref.setAttribute("type", "text/javascript");
        fileref.setAttribute("src", filename);
        document.getElementsByTagName("head")[0].appendChild(fileref);
        }
        </script>
        </head>
        <body>
        <p>A simple website doing its own thing, loading very fast.
        Click <a href="#" onclick="loadVEAPI()">here</a> to load a Virtual Earth map on demand.</p>
        <div id="myMap" style="position: relative; width: 800px; height: 600px;"></div>
        </body>
        </html>
        

The function loadVEAPI is called when the user clicks our link, using a simple flag to ensure we don’t load the script again. We first set a simple animated gif as the background for our map div, as the control is loading this provides feedback to our users. You can get your own animation from here: http://www.ajaxload.info

Important: For FireFox Virtual Earth includes a compatibility file, this file must be included before the Virtual Earth script. I found in testing this was not the case when loaded on demand. The fix is to manually load this file (if required) prior to the VE Script.

Lastly we load the Virtual Earth script passing our function name “onscriptload” to be called when it is loaded. I made a little helper function “appendJS” to neaten up the code, it creates a script element, sets the correct attributes and appends it to the head of the page.

When the script has loaded our “onscriptload” function is called as expected. We simply turn off our animation and load our Virtual Earth map as usual. Pretty simple hey?

See it in action here: http://soulsolutions.com.au/examples/VE62/loadondemand.htm

The Virtual Earth script is now cached for around 1 month so once it is loaded the first time in your browser it will load very quickly on subsequent pages. For testing you will need to clear your browser cache.

Bring your own Tile Provider to Virtual Earth, OpenStreetMap

The Virtual Earth JavaScript control has supported tile overlays for some time. A tile layer is set of 256px square images matching the Mercator projection of Virtual Earth. Tile layers are usually layered on top of Virtual Earth, the control supports transparency and an opacity of the entire layer. With the latest release of Version 6.2 of the control you can now disable the base tile layers allowing for greater performance when your tiles are all you need. Lets look at what is required to load OpenStreetMap tiles into Virtual Earth.

VEOSM

OpenStreetMap is a free editable map of the whole world. It is made by people like you. OpenStreetMap allows you to view, edit and use geographical data in a collaborative way from anywhere on Earth. OpenStreetMap's hosting is kindly supported by the UCL VR Centre and bytemark.

I am very impressed with how far OSM has come in the last year and highly recommend it as an open project (unlike another major online mapping company that wants you to contribute but then own the data themselves). So lets look at just how simple it is to get OSM into Virtual Earth.

See the working page here: http://www.soulsolutions.com.au/examples/osm/ (View source for the full code sample)

The first thing we need to do for this example is to disable some of the core Virtual Earth functionality we don’t need here.

var map = null;
        function loadmap() {
        map = new VEMap('myMap');
        map.onLoadMap = onloadmap;
        map.SetDashboardSize(VEDashboardSize.Tiny);
        var mapOptions = new VEMapOptions();
        mapOptions.LoadBaseTiles = false;
        mapOptions.EnableBirdseye = false;
        mapOptions.EnableDashboardLabels = false;
        map.LoadMap(new VELatLong(-27.47, 153.03), 16, VEMapStyle.Hybrid, false, VEMapMode.Mode2D, true, 0, mapOptions);
        map.HideScalebar();
        }

We change to the tiny dashboard, and pass into the LoadMap() method the optional final parameter of VEMapOptions. In Version 6.2 this includes the ability to disable the base VE tiles using LoadBaseTiles = false and you can also disable Birdseye as I do here. I set the initial view of the map over Brisbane, Australia and hide the scalebar. These operations are not mandatory, in your application you may want to keep some of these features.

Next we subscribed to the onLoadMap event before we called VELoadMap. This event fires once the map has loaded, a perfect time to add our new tile layer.

function onloadmap() {
        var bounds = [new VELatLongRectangle(new VELatLong(90, -180), new VELatLong(-90, 180))];
        var tileSourceSpec = new VETileSourceSpecification("OSM", "", 1, bounds, 1, 18, getTilePath, 1.0, 100);
        map.AddTileLayer(tileSourceSpec, true);
        }

To add a tile layer to Virtual Earth you have two options, either create your tiles in the same naming convention as Virtual Earth or use your own convention. We add the tile layer to VE using the VEMap.AddTileLayer method passing in a Tile spec and whether we want the layer immediately visible. The Tile spec, VETileSourceSpecification, has a bunch of properties for you to set:

Name Description

Bounds

An array of VELatLongRectangle Class objects that specifies the approximate coverage area of the layer

ID

The unique identifier for the layer. Each tile layer on a map must have a unique ID.

TileSource

The location of the tiles.

NumServers

The number of servers on which the tiles are hosted.

MinZoomLevel

The minimum zoom level at which to display the custom tile source.

MaxZoomLevel

The maximum zoom level at which to display the custom tile source.

getTilePath

When viewing a map in 2D mode, the function that determines the correct file names for the tiles.

Opacity

Specifies the opacity level of the tiles when displayed on the map.

ZIndex

Specifies the z-index for the tiles.

Most of these are straight forward but the two we will look at are TileSource and getTilePath. The TileSource is when you have a tile set that follows the conventions from Virtual Earth. You supply a URL with some special tokens to be replaced:

  • %1—this parameter specifies the map style to associate with your customer tiles. For example, if your tile file names begin with “r”, the custom tiles will only be displayed if the map style is set to “road”. Valid values are r, h, and a.
  • %2—if you are using more than one server for load-balancing, this parameter is used in conjunction with the VETileSourceSpecification.NumServers Property to cycle through the tile servers.
  • %4—this variable cycles through the file names.

    An example would be: http://TileServer%2/MyTiles/%1%4.png

    This works in 2D and 3D modes with the exception of using multiple servers for load balancing that was removed from 3D in the last version. I hope this makes a come back soon.

    GetTilePath on the other hand allows for you to write your own JavaScript function to return the correct tile. This is where it gets interesting as many other tile providers out there also use the Mercator projection and 256px tiles, for example OpenStreetMap. OSM uses the following naming convention:

    http: //tile.openstreetmap.org/{z}/{x}/{y}.png

    Where z is the zoom level, and x and y are the index of the tile from the top left. This makes our implementation trival as the getTilePath function we defined is passed these exact values in a tileContext parameter:

    function getTilePath(tileContext) {
            return "http://tile.openstreetmap.org/" + tileContext.ZoomLevel + "/" + tileContext.XPos + "/" + tileContext.YPos + ".png";
            }

    All we need to do is return the correct url by replacing the values as required. It couldn’t be more simple.

    View it online here: http://www.soulsolutions.com.au/examples/osm/ 

    So we get the 2D Virtual Earth control with only our custom tile layer. 3D mode will throw an exception as it will look for the TileSource property. We could of left the base Virtual Earth imagery on and overlaid OSM, even made it slightly opaque. An interesting modification is to leave the dashboard, hide the base tiles and put a switch statement in the getTilePath javascript function. The final property of the tileContext is MapStyle, we could use OSM for road data but fall back to Virtual Earth for Aerial Images.

    Let me know what you come up with.