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!
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.)
FindAStore.aspx. The user has entered an address - namely, the postal code 92109 - and is about to click the Send button.
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.
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>
|
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.
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
|
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
|
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.
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, ...
|
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:
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
|
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.
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.
Happy Programming!
Attachments:
Further Readings:



