Virtual Earth 6.1 10min Map

johnWeeGo.jpgThis example comes from Steve Lombardi’s blog post last year and has been updated to the latest version of Virtual Earth at the time of writing. The code and collection was presented at the Museums and the Web conference in Montreal, Canada.

In the space of 10 minutes you can present a custom collection presented on the Virtual Earth platform within your own web site by leveraging http://maps.live.com as your content editor and storage and using the Virtual Earth API. Here we present the simple code to do this and some extra features like a map search, custom numbered icons and an outline text listing with clickable titles.

10minMap3D

To begin, navigate to http://maps.live.com. This is Microsoft’s consumer mapping site built on top of the Virtual Earth API but with many more features. Here you can sign in using live ID and add pushpins, polylines, polygons and even 3D models to a collection. For example add some pushpins of famous landmarks in your area, add textual description, an image and a link to further information. Now save your collection and make it public. Here is an example of some landmarks around Montreal:

http://maps.live.com/?v=2&encType=1&cid=5CB0B41C15FA0196!437

Most of the hard work is now done, you have an interface to create and edit your content and share it with the world via GeoRSS, KML or as a VE collection. We will use the last option for this example. In order to access the VE collection you have made we need the collection ID, I find the easiest way to retrieve this is from the Actions -> Send to -> email in the collections panel. The link provided in the email contains the collection ID we will need it will look something like this (Note the exclamation point and following digits are part of the ID):

http://maps.live.com/?v=2&encType=1&cid=5CB0B41C15FA0196!437

The Virtual Earth API is a JavaScript file, hosted by Microsoft. In order to be compatible with many of its features you must set your page doctype to XHTML and use UTF8 encoding. The following shows a complete HTML page, the import of our collection, creation of a text outline listing, custom icons and a custom location search. The important concept here is the asynchronous nature of this technology, you must hook into events that fire when actions complete. The sequence we will follow here is:

  1. Page Body loads -> load the map
  2. Map Loads -> load the collection
  3. Collection Loads -> change the icons and make the text outline with links

 

<!--Set the doctype to be XHTML-->
<!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>The 10 Minute Map</title>
    <!--Set the Character set to UTF8 -->
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <!--Add your style sheet to make it look pretty-->
    <link type="text/css" rel="stylesheet" href="styles.css" />
    <!--Include the latest Virtual Earth Control-->
    <script type="text/javascript" src="http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.1"></script>
    <script type="text/javascript">
        //Global varible for our map object
        var map = null;
        
        //Once the Page (DOM) has completly loaded we can create our map
        function Page_Load()
        {    
            //First we create our object passing the ID of the Div for our map
            map = new VEMap('mymapdiv');
            //Next we set the function to execute once the map has loaded
            map.onLoadMap = fnLoadCollection;
            //Now we load the map
            map.LoadMap();
        }
        
        //When the map has loaded we can load our collection
        function fnLoadCollection()
        {    
            //set a loading label
            document.getElementById('outline').innerHTML = "<b>Loading Collection...</b>";

            //import our layer
            //First we create a new layer
            var layer = new VEShapeLayer();
            //Then add this new blank layer to the map
            map.AddShapeLayer(layer);
            //Then we create a ShapeSourceSpecification, here we use a VECollection, pass its ID and our new layer.
            var veLayerSpec = new VEShapeSourceSpecification (VEDataType.VECollection, "5CB0B41C15FA0196!437", layer);
            //finally we call the import method, when it is finished we can access the data, we also set the map to show the best fit
            map.ImportShapeLayerData(veLayerSpec, fnLayerLoaded, true);                
        }
        
        //When our collection layer has loaded we can access its data
        function fnLayerLoaded(layer)
        {
            //build up a HTML string to give a text outline of all the items
            var outline = "";
            //how many items are in our collection?
            var totalShapes = layer.GetShapeCount();
            //loop through them all building our HTML and changing to a custom Icon based on their index
            for (var cnt=0;cnt < totalShapes; cnt++) {
                //get the actual shape object by its index
                var shape = layer.GetShapeByIndex(cnt);
                //create some HTML
                var lnk = "<a href='#' onclick='javascript:map.SetCenterAndZoom(new VELatLong(" + 
                    shape.GetPoints()[0].Latitude + "," + 
                    shape.GetPoints()[0].Longitude + "),16);'>";
                outline = outline + "<b>" + lnk + (cnt+1) + ". " + 
                    shape.GetTitle() + "</a></b><br><img src='" +
                    shape.GetPhotoURL() + "' style='width:200px' /><br />" +
                    shape.GetDescription() +  "<br><br>";
                //Update each Pushpins Icon to be a numbered circle.
                shape.SetCustomIcon("http://dev.virtualearth.net/legacyService/i/bin/1.3.1204222815.33/pins/RedCircle" + (cnt+1) + ".gif");
            }
            //set our outline text        
            document.getElementById('outline').innerHTML = outline;
        }

        //A simple event to trigger a map search
        function SearchClick() {
            //here we search for a location (where) so the first parameter (what) is null.
            map.Find(null, document.getElementById('txtFind').value);
        }
        
        //When the page unloads we dispose the map object to free up memory
        function Page_Unload()
        {    
            if (map) {map.Dispose();}
        }        
        
        //set page event handlers
        if (window.attachEvent) {
            window.attachEvent("onload", Page_Load);
            window.attachEvent("onunload", Page_Unload);
        } else {
            window.addEventListener("DOMContentLoaded", Page_Load, false);
            window.addEventListener("unload", Page_Unload, false);
        }        
    </script>
</head>

<body>
    <div class="title">The 10 Minute Map</div>
    <div>
        <input id="txtFind" type="text" name="Find" size="30"/> 
        <input id="btnSearch" type="button" value="Find Place" name="Search" onclick="return SearchClick()"/>
    </div>
    <div id="mymapdiv" style="height:500px;width:600px;overflow:hidden;position:relative"></div>
    <div id="outline"></div>
</body>
</html>

In your application move all the JavaScript code into another file, this will keep it tidy, maintainable and allow browsers to cache it on future requests.

See the final page in action here:

http://www.soulsolutions.com.au/legacyimages/10minmap/10minutemap61.htm