Using the Virtual Earth Control in SharePoint

I have been using the Virtual Earth control a lot lately for various demos, mostly by putting customized maps into SharePoint Content Editor web part.  It's a cool effect, and can be an incredibly useful tool for your company's intranet.

For instance, I am planning an event at Qwest in Denver, Colorado.  There are over 12 people from Microsoft involved with the event, only 2 of which are actually from Denver.  Having a workspace where everyone can upload their decks, see a list of people that are also participating in the event, review the schedule, manage tasks, and make modifications to things like signage and handouts is really useful.  What makes this really useful for the out-of-towners is taking the guesswork out of where the event will actually be held (not to mention it saves a few emails from my inbox).

Add a Content Editor Web Part

This part is simple, just add a Content Editor Web Part to your page by clicking "Site Actions" and then "Edit Page".  Once you are in edit mode, click a zone to add a web part and choose a Content Editor Web Part.

Edit the HTML Source

Click the "Source Editor..." button in the web part property pane.  This will bring up a window where you edit the HTML source. 

The examples that you will see in the Virtual Earth SDK all use the body.onLoad() method to initialize the VEMap object.  Using the Content Editor web part, you can't do that, because you don't specify the html, head, or body tags in the content... you specify tags that are child elements of the HTML body tag.  So how do you wire it up?  There's a good post in the TechNet Forums that shows how to add an onload handler to the window.

 <script type="text/javascript" src="https://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.1"></script>
      <script type="text/javascript">
      var map = null;
             
        function addLoadEvent(func)
        {
           var oldonload = window.onload;
           if (typeof window.onload != 'function')
           {
               window.onload = func;
           }
           else
           {
               window.onload = function()
               {
                   oldonload();
                   func();
               }
           }
        }
        addLoadEvent(GetMap);

Initalize the Map

Notice that we call GetMap as an argument to our custom addLoadEvent method.  GetMap is a custom function that we write that initializes the VEMap object (which is obtained by referencing the mapcontrol.ashx script above).  The constructor parameter "myMap" refers to a DIV tag that we will specify later in this post.

       function GetMap()
      {
         map = new VEMap('myMap');
         map.LoadMap();
         AddPushpin();
      }

Add Custom Shapes

Now that we have a VEMap class, we can add custom pushpins to the map.

       function AddPushpin()
      {
         var latlong = new VELatLong(39.745801,-104.995401);
         map.SetCenterAndZoom(latlong,17);
         
         var shape = new VEShape(VEShapeType.Pushpin, latlong);

         //Set the icon
         var icon = new VECustomIconSpecification();
         icon.Image="https://www.qwest.com/global/dots/images/qwest_logo_q.gif";
         icon.ImageOffset = new VEPixel(-100, -35);
         icon.TextContent=" ";
         shape.SetCustomIcon(icon);

         var description = '<img src="https://www.qwest.com/favicon.ico" mce_src="https://www.qwest.com/favicon.ico">' +
             '<b>Qwest Facilities</b><br/>' +
             '930 15th Street<br/>' +
             '9th floor<br/>' +
             'Denver, CO 80202<br/><br/>' +
             '<a href="https://maps.live.com/?v=2&where1=930%2015th' +
             '%20St%2C%20Denver%2C%20CO%2080202-2932&encType=1" ' +
             'target="_new">Drive To...</a>'
         shape.SetDescription(description);
         map.AddShape(shape);

         map.SetMapStyle(VEMapStyle.BirdseyeHybrid);
      }

We start out by specifying a VELatLong class, specifying the latitude and longitude for the location.  I could have used the Virtual Earth Find method with a callback to obtain the latitude and longitude for an address (geocode), but I was able to find the latlong coordinates just fine by using a site that provides geocoding.

Next we create the pushpin using the latlong coordinates, and specify a custom icon.  You might be looking at the ImageOffSet thing and think, "Wha?!?"  When you specify a custom icon for a pushpin, it centers the image on the pushpin location.  If you want the image to be nudged a bit so that the pushpin hover points to the right place, you can offset the image.

While talking about controlling the offset, Scott noticed in the comments to this post that the popup can be incorrectly positioned. The fix is to include this bit of JavaScript, making sure that you include it after the JavaScript function that loads the VE map.

     Gimme.Screen.getScrollPosition = function()
    {
        var position = { x: 0, y: 0 };

        // window.pageYOffset is used by Firefox and Mozilla browsers, Safari, Opera, and Konqueror
        if (typeof window.pageYOffset !== 'undefined')
        {
                        position.x = window.pageXOffset;
                        position.y = window.pageYOffset;
        }
        // document.documentElement.scrollTop is used by IE6 in standards-compliant mode
        else if (!Gimme.Browser.isInQuirksMode && typeof document.documentElement.scrollTop !== 'undefined')
        {
                        position.x = document.documentElement.scrollLeft;
                        position.y = document.documentElement.scrollTop;
        }
        // document.body.scrollTop is used by IE6 in "Quirks" mode
        else if (typeof document.body.scrollTop !== 'undefined')
        {
                        position.x = document.body.scrollLeft;
                        position.y = document.body.scrollTop;
        }

        return position;
    };

The last bit simply sets the markup that is displayed when you hover over the pushpin.

Specify the Map Control Container

The last thing we need to do is to close the script tag and specify the container for the Map Control.  Remember that this string is what we passed into the constructor of the VEMap class in the beginning of the post.

       </script>
      <div id='myMap'></div>

That's it!  We now have a Content Editor Web Part in SharePoint that shows a Bird's Eye view of the location, complete with the company's logo as a custom pushpin. You can see the result in action, just click the image below.

Virtual Earth in SharePoint Content Editor Web Part

I use this often enough that I should turn it into a JavaSCript control.  I can imagine a script that accepts an address, pushpin icon image, pushpin title, and pushpin hover image.

For More Information

How to Integrate Virtual Earth with a SharePoint List

integrate Virtual Earth with SharePoint lists via a custom content type

Using the Virtual Earth Control for Geocoding

Virtual Earth Interactive SDK

Update 6/10/2008 - Included JavaScript fix for correctly positioning the popup's offset and making sure that it is positioned correctly when you scroll the page.