Geocoding with Virtual Earth

I've received a few inquiries about how to geocode with Virtual Earth. I thought this was pretty straight forward, but after hearing some feedback I figured it made sense to document this a bit further than the SDK does.

First off, you should always pre-geocode and store locations that show up on the map in some kind of database. You can store your locations in MapPoint Web Service for this which has free access for developer accounts. You'd have to wrap your MWS SOAP XML calls in JavaScript if you wanted to go this route, but it's not terribly difficult and I've written blog entries on how to do this.

So, the confusion arises with map.Find() in Virtual Earth. Yes, you will use map.Find() to do geocoding in Virtual Earth. map.Find() has a number of parameters:

VEMap.Find(what, where, findType, shapeLayer, startIndex, numberOfResults, showResults, createResults, useDefaultDisambiguation, setBestMapView, callback);

If all you want is a geocode, you can leave all the parameters blank or null and you'll get a result. Something like this:

VEMap.Find(null, '1 Microsoft Way, Redmond, WA');

This is all fine and dandy because it recenters the map to this location; but, what if you want a pin on the map of the location or you just want the geocode? This is where the confusion comes in. The natural inclination would be to use the VEFindResults class to get the results from the search. So, you'll go down the path of building the callback and only to be frustrated by the null result set. The problem is that the VEFindResults class is only for the "what" parameter in map.Find(). Instead, you'll need to use the VEPlace class to get the geocodes from a map.Find query.

image

Now, as the documentation states, "The VEMap.Find Method method can create VEPlace objects, but they have no constructor, and cannot be created independently of a VEMap.Find method call. The VEMap.Find method where parameter can use a VEPlace object reference."

Two things: (1) This means you have to create the constructor. So, how do you do that? You'll create your own function in JavaScript and pass in all of the parameters yourself. Something like this:

function GetCoordinates(layer, resultsArray, places, hasMore, veErrorMessage) { }

(2) You have to load the map control since a VEPlace object cannot be created independant of map.Find which relies on map.LoadMap. Just because you have to fire map.LoadMap doesn't mean you have to render more than a single tile. If this is a performance hit to your application, you can simply set your div to 1x1 pixels and the map control will only call 1 tile minimizing your performance hit. The reason it's like this is that most people want to render a map right away.

Now, this new function will be your callback. So, the application flows a little something like this:

  1. Load Map
  2. Find Location
  3. Callback for Geocode

You can batch this if you need to take the geocodes offline and per the terms of use they can only be used on Virtual Earth or MapPoint Web Service maps, but that's pretty much it. The sample below gives you everything you need to show a map, with a pin and get the geocode of the location. And, yes, this will include our rooftop geocoding accuracy for *currently* 44 million rooftops in the US (fallback is interpolation).

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Sample App</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

      <!-- saved from url=(0014)about:internet -->
<script type="text/javascript" src="https://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6"></script>

      <script type="text/javascript">

      var map = null;
var findPlaceResults = null;
function GetMap()
{
map = new VEMap('myMap:newmap');
map.LoadMap();
map.Find(null,'1 Microsoft Way, Redmond, WA', null, null, null, null, true, true, null, true, GetCoordinates);
}

     function GetCoordinates(layer, resultsArray, places, hasMore, veErrorMessage)
{
findPlaceResults = places[0].LatLong;
var myShape = new VEShape(VEShapeType.Pushpin, findPlaceResults);
myShape.SetDescription(findPlaceResults.toString());
map.AddShape(myShape);
}

      </script>
</head>
<body onload="GetMap();">
<div id='myMap:newmap' style="position:relative; width:800px; height:600px;"></div>
</body>
</html>

Copy, paste, run and you're good to go! FWIW, I wrote this specifically for Firefox 2.0.0.13 and it works just fine in IE.

CP