It's very common for a company to want to create a section of their web site where users can type in their address and figure out which location of a business is closest to them. Luckily, mapping technology has had lots of big backers, such as Microsoft, Google, and Yahoo!, so there are plenty of choices out there, (not to mention one of the most familiar players in the game, MapQuest.) For companies that foresee having thousands of hits a day to their application, you can purchase packages from these companies that support a large load. However, for small or medium sized businesses, it doesn't seem practical to spend tens of thousands of dollars on such a service. I was happy to discover that many of these mapping applications offer APIs that are free for use, as long as the hits remain below a certain level (which is actually quite generous.)
The locator application has to work with Yahoo! Maps in five capacities:
- Displaying a map of the location the user typed in (i.e. starting point).
- Having a URL to Yahoo! Maps, so when the user clicks on the image of their location, they're taken to the Yahoo! Maps web site at that location.
- Displaying a map for each location in the search results.
- Having a URL to Yahoo! Maps for location.
- Most importantly, calculating the distance between the starting point and each location.
Although the free API has a rather high threshold for hits, it still makes sense to not have to query Yahoo every time a location is pulled up in the results. This both reduces the number of hits to the service, but also increased performance. It makes more sense to query the location when it is saved by the back-end user, and then stored in a database. Based on the criteria above, we need to get three pieces of information for each location: the map image itself, a URL to the Yahoo! Maps site, and location coordinates (longitude and latitude) for each location.
The first step is to find the location coordinates for the location. You can do this by passing in Address, City, State, and Zip Code information to Yahoo. Yahoo will automatically calculate the closest distance, based on the information you pass it. It will not only return to you the longitude and latitude coordinates of the location, it will also return the address that it searched, as well as any error messages. For instance, if a user enters a city and a state, and Yahoo cannot find that city in the state, the address it returns shows that it just returned the center point for the state alone, along with an error message that it couldn't find the city.
The Yahoo! Maps simple API uses a simple POST to get the information needed. I created a namespace called Yahoo and created a class called Maps inside that namespace. (All my code is written in VB.NET.) My code is based off a sample from the Yahoo! Maps developer site listed above.
Public Function GetResponse(ByVal address As Uri, ByVal data As String) As String
Dim request As HttpWebRequest = DirectCast(WebRequest.Create(address), HttpWebRequest)
' Set type to POST
request.Method = "POST"
request.ContentType = "application/x-www-form-urlencoded"
Dim appId As String = ConfigurationManager.AppSettings("YahooAppId")
data = "appid=" & ConfigurationManager.AppSettings("YahooAppId") & data
' Create a byte array of the data we want to send
Dim byteData() As Byte = UTF8Encoding.UTF8.GetBytes(data)
' Set the content length in the request headers
request.ContentLength = byteData.Length
Dim postStream As Stream = Nothing
Dim response As HttpWebResponse = Nothing
postStream = request.GetRequestStream()
postStream.Write(byteData, 0, byteData.Length)
If Not postStream Is Nothing Then postStream.Close()
' Get response
response = DirectCast(request.GetResponse(), HttpWebResponse)
' Get the response stream into a reader
Dim reader As StreamReader = New StreamReader(response.GetResponseStream())
Dim xml As String = reader.ReadToEnd()
If Not response Is Nothing Then response.Close()
As you can see, the function takes two parameters. The first is the URL the request is being posted to. The process of using an address to retrieve latitude and longitude coordinates is called Geocoding
. I have stored the request URL in the web config. The URL I am using is http://local.yahooapis.com/MapsService/V1/geocode
. The second value I'm passing in is a long string of data. The structure of the data can be found on the Yahoo! Maps developer site.
In order to query the Yahoo! Maps API, you need to have a special ID assigned to you. I have stored the YahooAppId in the web.config as well. After I have created a new HttpRequest object, I append the Yahoo App Id to the data that will be passed to Yahoo. Once that's done, I look for a response from the server. The response will contain a stream of XML.
So, let's try it out. Let's pass in an address and see what response we get back. Here's my function for getting XML from an address:
Public Function GetAddressXml(ByVal street As String, ByVal city As String, ByVal state As String, ByVal zip As String) As String
Dim data As StringBuilder = New StringBuilder()
data.Append("&street=" + HttpUtility.UrlEncode(street))
data.Append("&city=" + HttpUtility.UrlEncode(city))
data.Append("&state=" + HttpUtility.UrlDecode(state))
data.Append("&zip=" + HttpUtility.UrlDecode(zip))
Dim addressXml As String
addressXml = GetResponse(New Uri(ConfigurationManager.AppSettings("YahooGeoCodeUrl")), data.ToString())
To call this code, I'll pass in the values:1600 Pennsylvania Ave NW, Washington, D.C., and 10001. This is the XML I get back:
<ResultSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:yahoo:maps" xsi:schemaLocation="urn:yahoo:maps http://api.local.yahoo.com/MapsService/V1/GeocodeResponse.xsd">
<Result precision="address" warning="The exact location could not be found, here is the closest match: 1300 Pennsylvania Ave Nw, Washington, DC 20004">
<Address>1600 PENNSYLVANIA AVE NW</Address>
<!-- ws03.search.re2.yahoo.com uncompressed/chunked Wed Sep 19 17:11:35 PDT 2007 -->
Some things to note about this XML:
- There's an attribute on the result set called "precision". As the name says, it denotes how precise the results are. In this example, Yahoo found the street address passed in. If only a state had been passed in, the precision would be "state" and so on.
- Note the warning attribute of the Result node.
- If you are searching an address in North America, the longitude will most likely be a negative number, indicating that it's west of Greenwich. The latiude will most likely be a postiive number, idicating it's north of the equator. Latitudes can be a number from 0 to 90° and longitudes can be a number from 0 to 180°. If you were to search for an address beyond those ranges, you would receive an error.
At this point, it's smart to save the coordinates in the database for that location. The next step is to get the map image from Yahoo based on those coordinates. You can do this by passing in a data string such as this:
Please note that there's a different request URL to send to the GetResponse function. It's http://local.yahooapis.com/MapsService/V1/mapImage
As I just explained, when using the Broadband map, the "lon" and "lat" query string parameters indicate where the map should be centered, and the "q1" parameter is a URL-encoded string made of the concatenated address. This will indicate where the balloon will appear. Futhermore, the addrress of the balloon shows up in the address text boxes on the left side of the page. The "mag" paramters is the same as the "zoom" parameter passed in to get the map; it indicates the zoom of the map.
There's a bit of a gotcha to keep in mind, and that is that not everyone is using the broadband version of Yahoo! Maps. Yahoo uses a cookie to remember which kind of map you like to use. If you notice, the URL points to the Flash version. Luckily, Yahoo is smart enough to redirect the user to the same page and location in the low-bandwidth version if they are using that version of Yahoo! Maps.