When you think ASP, think...
Recent Articles
All Articles
ASP.NET Articles
ASPFAQs.com
Message Board
Related Web Technologies
User Tips!
Coding Tips
Search

Sections:
Book Reviews
Sample Chapters
Commonly Asked Message Board Questions
JavaScript Tutorials
MSDN Communities Hub
Official Docs
Security
Stump the SQL Guru!
Web Hosts
XML
Information:
Advertise
Feedback
Author an Article
Jobs

ASP ASP.NET ASP FAQs Message Board Feedback ASP Jobs
 
Print this Page!
Published: Wednesday, May 19, 2010

Building a Store Locator ASP.NET Application Using Google Maps API (Part 1)

By Scott Mitchell


Implementing the Store Locator Using ASP.NET MVC
Since this article was published, several readers have emailed me asking for an ASP.NET MVC version of the store locator application. For those that are interested in ASP.NET MVC, check out Implementing the Store Locator Application Using ASP.NET MVC, which walks through porting the WebForms application presented in this article into an ASP.NET MVC application, step by step.

Introduction


Over the past couple of months I've been working on a couple of projects that have used the free Google Maps API to add interactive maps and geocoding capabilities to ASP.NET websites. In a nutshell, the Google Maps API allow you to display maps on your website, to add markers onto the map, and to compute the latitude and longitude of an address, among many other tasks.

With some Google Maps API experience under my belt, I decided it would be fun to implement a store locator feature and share it here on 4Guys. A store locator lets a visitor enter an address or postal code and then shows the nearby stores. Typically, store locators display the nearby stores on both a map and in a grid, along with the distance between the entered address and each store within the area. To see a store locator in action, check out the Wells Fargo store locator.

This article is the first in a multi-part series that walks through how to add a store locator feature to your ASP.NET application. In this inaugural article, we'll build the database table to hold the store information. Next, we'll explore how to use the Google Maps API's geocoding feature to allow for flexible address entry and how to translate an address into latitude and longitude pairs. Armed with the latitude and longitude coordinates, we'll see how to retrieve nearby locations as well as how to compute the distance between the address entered by the visitor and the each nearby store. (Part 2 examines how to display a map showing the nearby stores.) Read on to learn more!

- continued -

An Overview of the Demo Application


Before we explore the steps involved in creating the search locator, let me take a moment to show you the finished product. (These screen shots are taken from the demo that is available for download at the end of this article.) The store locator demo consists of two primary ASP.NET pages:
  • FindAStore.aspx - from here, the user is prompted to enter an address, which can be a street address, a city name, or a postal code. Upon entering a valid address, the user is redirected to...
  • ShowStoreLocations.aspx - which displays stores near the specified address in a grid. (Part 2 shows how to add a map to this page that includes markers indicating the locations of the nearby stores.)
Here's a screen shot of the FindAStore.aspx. The user has entered an address - namely, the postal code 92109 - and is about to click the Send button.

The user wants to know what stores are near his postal code, 92109.

After clicking Send, the FindAStore.aspx ensures that the address is valid using Google's geocoding service, which is a free address to latitude/longitude coordinates service that we'll examine later on in this article. In addition to reporting the coordinates of the address, the geocoding service also validates the address. If it is an unknown address then an appropriate message is displayed. If the address is ambiguous - say they enter as an address the text Springfield, which is the name of several cities across the US - a list of possible addresses are displayed. Clicking one of those addresses whisks the user to the results page (ShowStoreLocations.aspx).

The screen shot below shows the results page when entering an address criteria of 92109. The store number, address, and distance are displayed for all stores within 15 miles of the address.

Stores within 15 miles of the 92109 postal code are displayed.

Building the Database


The first order of business in building a store locator is to determine where and how store information will be maintained. For this exercise, I decided to use a database. Specifically, I created a new database named StoreLocations.mdf in the App_Data folder. This database has a single table named Stores that contains a record for each store location. (Note: this database, as well as a complete, functioning ASP.NET application, can be downloaded from the end of this article.) The Stores table has the following schema:

Column Type Notes
StoreNumber int Primary key
Address nvarchar(50)  
City nvarchar(50)  
Region nvarchar(50)  
CountryCode nvarchar(2)  
PostalCode nvarchar(10)  
Latitude decimal(10,7)  
Longitude decimal(10,7)  

After creating this table I added ten rows. Rather than make up fictional addresses, I decided to enter ten Wells Fargo branch locations from around southern California. (Wells Fargo is a large bank with branches throughout most of the United States.) Specifically, I added four San Diego branches and six from the greater Los Angeles area (Torrance, Studio City, and West Hollywood).

While I was able to find the addresses for these ten Wells Fargo branches from Wells Fargo's website, I could not find the latitude and longitude information there. The latitude and longitude are geographical coordinates used to specify a position on Earth. Knowing the latitude and longitude of each store is important for two reasons. First, given the latitude and longitude of two points you compute the distance between them. Second, many map-related APIs - such as Google Maps API - have you use latitude and longitude measurements when performing various tasks, such as centering the map on a location or adding markers to the map. Fortunately, determining the latitude and longitude given an address is easy thanks to free services like GeoCoder.us, which is what I used to determine the latitude and longitude readings for the ten branches in the Stores table. (The Google Maps API's Geocoding service, which we'll examine later in this article, can also be used to determine the latitude and longitude of a given address.)

Using Google Maps API's Geocoding Service To Resolve Addresses


One challenge of building a store locator is collecting and parsing the user's input (namely, their address for which they want to find nearby stores). One option would be to prompt the user to enter the address, city, region, and postal code through individual textboxes, and then to build up a database query that compares the entries the user made with the values in the Stores table's City, Region, and PostalCode fields. But this has some drawbacks. For starters, a branch location may be in located in city X just a few blocks from city Y. If a visitor were to enter an address in city Y our simplistic search criteria would omit the nearby store because it is not in the same city.

A better approach is to determine which stores to display (if any) based on their distance from the entered address. To accomplish this we need to compute the latitude and longitude coordinates of the address entered by the user. Once we have that information we can use a fairly simple WHERE clause to bring back only those store locations within a certain range, such as within 15 miles. To accomplish this we need to be able to programmatically determine the latitude and longitude coordinates of the address entered by the user. The Google Maps API includes a geocoding service that provides this very functionality.

Geocoding is the process of determining the latitude and longitude coordinates given an address (either a street address, city, or postal code). Using Google's geocoding service is fairly straightforward. Simply make an HTTP request to http://maps.google.com/maps/api/geocode/xml?address=address&sensor=false and Google returns an XML document with information about that address. For example, to geocode the address 1600 Pennsylvania Ave, Washington D.C., you would make an HTTP request to http://maps.google.com/maps/api/geocode/xml?address=1600+Pennsylvania+Ave,+Washington+D.C.&sensor=false. This returns the following (abbreviated and formatted) XML:

<GeocodeResponse>
   <status>OK</status>

   <result>
      <type>establishment</type>
      <formatted_address>The White House, Washington, DC 20500, USA</formatted_address>

      ...

      <geometry>
         <location>
            <lat>38.8976500</lat>
            <lng>-77.0356669</lng>
         </location>
         ...
      </geometry>
   </result>
</GeocodeResponse>

The key elements to note are:

  • <result> - there is one <result> element for each possible address match.
  • <status> - indicates the status of the geocoding request.
  • <geometry> - provides geometry details about the address match, including the latitude and longitude pairs (shown above) and viewport information (not shown above), which is useful for displaying a map that includes the address.
The geocoding service is extremely flexible in its input. You can enter a postal code for instance, such as 92101 (e.g., http://maps.google.com/maps/api/geocode/xml?address=92101&sensor=false), or just the name of a city, like San Diego, or the name of a city and region, like San Diego, CA. And if you enter an address where there is some ambiguity, such as Springfield, the geocoding service responds with a <result> element for each possible match.

When the user enters an address in the FindAStore.aspx page and clicks the Send button, the Google geocoding service is called from the code-behind class. The following code snippet shows how easy it is to call a remote Web service and load the results into an XElement object, which is part of the LINQ to XML library. (While the below code is in C#, the demo available for download includes both C# and VB code. Also, note that I've formatted and abbreviated this code snippet and subsequent ones to improve readability.)

// Use the Google Geocoding service to get information about the user-entered address
var url = String.Format("http://maps.google.com/maps/api/geocode/xml?address={0}&sensor=false", Server.UrlEncode(address));

// Load the XML into an XElement object (whee, LINQ to XML!)
var results = XElement.Load(url);

The code above starts by defining the URL to visit to get the address information. Note that I use the Server.UrlEncode method on the address information entered by the user (address) before placing it in the querystring. This ensures that if the user has entered an address with any reserved querystring characters, such as ? or &, they will be properly escaped. Next, the call the XElement.Load(url) makes the HTTP request to the specified URL and loads the returned data in the XElement object results.

Now that we have the geocoding data back we need to determine how many results are included. If no address information was returned then we need to show a message; if more than one address was returned then the initial address was too ambiguous and we want to list the potential matches; finally, if there is precisely one address found we want to redirect the user to the results page, ShowStoreLocations.aspx. In addition to the address TextBox control, the FindAStore.aspx page also includes a Label and a ListView, with IDs lblNoResults and lvDidYouMean, respectively. The lblNoResults Label is used to display a message informing the user that the address they entered could not be understood. The lvDidYouMean ListView is used to display possible address matches in the event that the user enters an ambiguous address. Specifically, the ListView displays an ordered list of matches, rendering each match as a HyperLink that, when clicked, redirects the user to the results page using the address selected.

This workflow to determine which approach to take - whether to send the user directly to the results, to show the lblNoResults Label, or to show the possible addresses in the lvDidYouMean ListView, is handed by the following code:

// Determine how many elements exist
var resultCount = results.Elements("result").Count();

// How many results did we get back?
if (resultCount == 0)
   lblNoResults.Visible = true; // Show the No Results message

else if (resultCount == 1)
   // Send the user to the results page...
   Response.Redirect("ShowStoreLocations.aspx?Address=" +
                     Server.UrlEncode(results.Element("result").Element("formatted_address").Value));

else
{
   //Got back multiple results - We need to ask the user which address they mean to use...
   var matches = from result in results.Elements("result")
                 let formatted_address = result.Element("formatted_address").Value
                 select formatted_address;

   lvDidYouMean.DataSource = matches;
   lvDidYouMean.DataBind();
   lvDidYouMean.Visible = true;
}

The number of <result> elements is computed using the code results.Elements("result").Count(). If there are no <result> elements then the address was invalid. (dlsakjfrourow3ur is an example of an unknown address.) If there is precisely one result then the user is redirected to ShowStoreLocations.aspx, passing the the value of the <formatted_address> element along in the querystring. If multiple results were returned then each <formatted_address> element is queried and displayed in the lvDidYouMean ListView. The lvDidYouMean ListView is pretty straightforward - it renders each formatted address as a HyperLink control in an ordered list item. When one of the HyperLinks is clicked, the user is whisked to the results page, passing along the formatted address in the querystring.

The screen shot below shows the lvDidYouMean ListView in action. Here, the user searched for the city Springfield, which returned six possible matches. Clicking one of the links in the ordered list takes the user to the results page as if they had typed in the formatted address to begin with.

If the visitor searches on an ambiguous address the page lists potential matches.

Displaying Nearby Stores and Computing the Distance From the Address Entered


By the time the user reaches the results page (ShowStoreLocations.aspx) we know they have entered (or chosen) an unambiguous address with known latitude and longitude coordinates. All that remains now is to show those stores near the address entered by the user. The logic I use to determine whether a store is "near" the address entered is by comparing the latitude and longitude coordinates for the address entered by the user and the addresses in the database. Specifically, I bring back any store where the distance between the latitude and longitude pairs are less than 0.25. A distance of 0.25 in the coordinates corresponds to, roughly, 15 miles. (I say "roughly" because the distances between the lines of longitude get shorter the closer you are to the poles.)

Here is an abbreviated form of the SQL query I use to retrieve nearby stores:

SELECT StoreNumber, Address, ...
FROM Stores
WHERE ABS(Latitude - @Latitude) < 0.25 AND ABS(Longitude - @Longitude) < 0.25

Note the occurrence of the two parameters in the WHERE clause, @Latitude and @Longitude. These parameters are the latitude and longitude of the address entered by the visitor; we'll see how they are set momentarily. Using these two parameters the WHERE clause returns those stores whose latitude and longitude are less than 0.25 away from the address entered by the user. This essentially draws a bounding square around the location, where each of the square's edges are 0.25 units long and the square is centered at the address entered by the user. Any stores that fall within that square are returned in the results.

In addition to bringing back nearby stores' address information, I also wanted to include the distance between the address entered by the user and the nearby stores. If you remember back to your high school geometry days you may remember that the Pythagorean theorem can be used to computing the distance between two points in a Cartesian coordinate system. To summarize, given two points (x1, y1) and (x2, y2), the distance between the two points is:

The square root of x1 - x2 squared plus y1 - y2 squared.

Of course, the Earth isn't flat, so using the Pythagorean theorem introduces a bit of error. However, this error is rather small (a dozen meters or so) when measuring small distances. (If you need to accurately measure distances, or are dealing with longer distances or coordinates near the poles, consider using the Haversine formula.)

The SQL query includes a computed value in the SELECT list that uses the Pythagorean theorem to compute the distance between the address entered by the user (the @Latitude and @Longitude parameters) and each store returned by the query. This result is then multiplied by 62.1371192, which scales the results and converts it into miles. The results are also ordered so that the closer stores appear first:

SELECT StoreNumber, Address, ..., SQRT(POWER(Latitude - @Latitude, 2) + POWER(Longitude - @Longitude, 2)) * 62.1371192 AS DistanceFromAddress
FROM Stores
WHERE ABS(Latitude - @Latitude) < 0.25 AND ABS(Longitude - @Longitude) < 0.25
ORDER BY DistanceFromAddress

The last piece of the puzzle is setting the @Latitude and @Longitude parameters based on the coordinates of the address being searched. This is handled in the result page's Page_Load event handler. A call is made to the same Google geocoding service examined earlier in this article and the retrieved latitude and longitude readings are pulled and used as these parameter values.

The screen shot below shows the results page when searching for store locations near the Staples Center in Los Angeles.

The six store branches near the Staples Center are displayed.

Conclusion


This article is the first in a multi-part series that examines how to build a store locator feature in an ASP.NET application using the Google Maps API. In this initial installment we saw how to use Google's geocoding service to both validate an address and to determine its latitude and longitude coordinates. Once we know the coordinates of a user-supplied address we can then take that information and return nearby stores and determine the distance from the entered address and each nearby store. Part 2 shows how to augment the results to include a map of the area with markers to denote the nearby stores.

  • Read Part 2
  • Read Part 3
  • Happy Programming!

  • By Scott Mitchell


    Attachments:


  • Download the code used in this article

    Further Readings:


  • The Google Maps API
  • The Google Geocoding Web Service
  • GeoCoder.us - Find Latitude and Longitude Coordinates for an Address
  • LINQ to XML (technical docs)
  • LINQ to XML - 5 Minute Overview
  • The Pythagorean Theorem
  • The Haversine Formula
  • Building a Store Locator ASP.NET Application Using Google Maps API (Part 2)
  • Implementing the Store Locator Application Using ASP.NET MVC (Part 1)


  • ASP.NET [1.x] [2.0] | ASPMessageboard.com | ASPFAQs.com | Advertise | Feedback | Author an Article