|
 Saturday, March 17, 2007
« HOWTO: Debugging JavaScript using "debug... | Main | Using Excel to help with Data formatting... »

HOWTO: Build a Store Locator in ASP.NET

Full Visual Studio 2005 solution included at the bottom of this post.

Many organizations have multiple locations throughout the country. A lot of the time the organizations customers will want to know where the closet location of that store is. We’ve all become accustomed to going to a companies website and finding the store locator and locating the store that’s closest to us. This is a simple thing for a user to do. But building a store locator is far from simple and can become quite complex, real quick. I’ve put together a simple example that will enable you to add store locator functionality to your ASP.NET website in no time.

Let’s start by covering the basics….

Requirements for store locator functionality: 
   - Database to store your store locations
   - Each store location needs a Longitude / Latitude 
   - A method to retrieve Latitude / Longitude, also known as Geocoding
   - A Web Front end that allows users to search and view the results.

Screen Shots

Search Screen:

Results Screen (click for larger view):

 

The Database

The database is very simple. We have one table, two sprocs, and 3 functions. It is in the App_Data folder of this solution.

The Location table holds the store locations.

The GetNearbyLocations sproc gets the locations that are closest to the user through a mathematical calculation that is inside of the sproc.

The InsertLocation sproc inserts a new location into the Location table (we’ll get to that near the end).

The three functions: XAxis, YAxis, ZAxis are used in the distance calculation that is inside of the sproc.

Important Note: I’m not the genius that wrote this mathematical calculation. Therefore, I can’t take credit for it. The database table, functions and distance sproc are all modeled after this post on MSDN. I have altered quite a few things especially the GetNearbyLocations sproc where I actually calculate the Earths X,Y, and Z axis’ at runtime. 

 

The Geocoding – Getting the Latitude / Longitude

The latitude and longitude are needed to perform the distance calculation in the GetNearbyLocations stored procedure. The Latitude and Longitude of each store is stored in the Locations table (see the database image above) with the location record itself.

How to do we get the Latitude / Longitude?

That’s where the Google Maps API comes into place. Google Maps allows you to sign up for a free API key.

Important Note: If you are going to test this locally, you will need to get a API key that is associated with your local machine, e.g.: http://localhost:port/  where “port” is your port number, such as: localhost:4688, localhost:1637, etc).

Where put your Google Maps API Key
The key is located in two places (I know, its not best practice) but, it’s the only way I could get it to work without spending hours on figuring it out. It’s located in the web.Config and in the Default.aspx page.

Default.aspx page (click for larger view)

web.Config

In a previous post I had built a class that retrieved the latitude and longitude of an address. By using this class while a new location is being added, (AddNewStore.aspx), the Lat/Lon coordinates are then retrieved and saved.

protected void addNewStoreButton_Click(object sender, EventArgs e)
{
   string address = String.Format("{0}, {1}, {2} {3}"
   addressTextBox.Text,
   cityTextBox.Text,
   stateTextBox.Text,
   zipTextBox.Text);
   Coordinate addressCoordinates = Geocode.GetCoordinates(address);
   ... // Other code ommitted
}

The Web Front End

The web front end is simple (errrrrrrr… kind of). The application requires 2 things:
1. An address
2. A distance to search within.

The user enters this information, it’s passed to the database, the calculation is performed and the records that match the distance compared to the address are returned. Simple, right?

Now its time for the “somewhat” confusing part: the task of integrating the Google map with clickable markers. Google doesn’t offer markers that are numbered (from what I could find). So I found some on the net and included them in this project. Which leads me to the next point…

A maximum of 100 results will be returned.

This is set within the stored procedure. The reason we only return 100 records is because we only have 100 icon images (1 through 100). Plus, placing more markers on the page would have a negative impact on the memory and processor on the user’s machine. Furthermore, who’s really going to look at 100 results? The user is probably only looking for the closest couple of stores. Not the top 100.

Google Marker Placement

In order to place markers on the screen we needed lat/lon points to pass to Google’s API. This poses a problem because our data is in the DB, but Google Maps API needs it on the client to process it. Therefore, while on the server, while we have the data in a dataset (below) ...

/// <summary>
/// Gets the store locations and the coordinate for the "from" area (the address the user entered).
/// </summary>
/// <param name="coordinate">A coordinate.</param>
/// <param name="data">A LocationsData dataset.</param>
private LocationsData GetLocationData(Coordinate coordinate)
{
SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["StoresDb"].ConnectionString);
connection.Open();

string SQL = "GetNearbyLocations";
SqlCommand command = new SqlCommand(SQL, connection);
command.Parameters.Add(new SqlParameter("@CenterLatitude", coordinate.Latitude));
command.Parameters.Add(new SqlParameter("@CenterLongitude", coordinate.Longitude));
command.Parameters.Add(new SqlParameter("@SearchDistance", distanceDropDown.SelectedValue));
command.Parameters.Add(new SqlParameter("@EarthRadius", 3961)); // In Miles
command.CommandType = CommandType.StoredProcedure;

SqlDataAdapter da = new SqlDataAdapter(command);
LocationsData data = new LocationsData();
da.Fill(data.Location);
return data;
}

GetJSONLocations(LocationsData data);

... we have to create a string representation of a JSON object array that holds all this info, this is done in the GetJSONLocations method. Please see the method for more documentation on how that variable is created.

Info that the JSON object holds: 
   - Location Name
   - Address 
   - Url Encoded Address
   - Latitude
   - Longitude

This info was written to the screen using the RegisterClientScriptBlock.

Using the same method of as before we create another JSON variable (homeSpatialInfo) that holds the “home info”. This info is the address that the user typed in.

This information is used for the popup marker, as well as the link that is provided within the popup marker. Clicking on the “directions” link (within the popup marker) will take you to Google’s map site with the “to” and “from” address parameters already populated, therefore giving you a directional map from the location you typed in, to the location that you clicked on the map. Smooth, eh?

(click for larger view)

 

Adding a new Location

To add a new location, fire up the AddNewStore.aspx page and type in the required info (in this case I’m requiring all fields, but this can be changed to fit your own needs) and submit. If the address cannot be found, (Google Maps API returns “0” for both the Lat/Lon coordinates that it cannot find) it will inform you. Otherwise the address will be added and then upon your next search, it will be returned in the results (if the location is within the distance specified).

 

Database Note: The locations that are in the Stores.mdf that come with this solution are 200 Starbucks stores that are within the New York Area. This is not a comprehensive list, this is just a list I happened to have for testing purposes.


And that’s all folks!

Download and enjoy. :)

StoreLocator.zip (521.92 KB)

kick it on DotNetKicks.com
#    Comments [27] |
Thursday, April 05, 2007 2:20:48 PM (Eastern Standard Time, UTC-05:00)
Very nice article. Thanks for writing and sharing with the community. I have a questions how I can change the nearest store distace from MILES TO KILOMETERS. Do I have to change the code somewhere ?

I will appreciate you reply.
A, M
Thursday, April 05, 2007 3:18:06 PM (Eastern Standard Time, UTC-05:00)
youasif,

You will have to open the Default.aspx.cs file, go to this line:
command.Parameters.Add(new SqlParameter("@EarthRadius", 3961)); // In Miles
and change the value here.

I did a quick look up on a online miles to kilometers calculator online and here's what it says:

Answer: 3961 mi. = 6374.611 km

So this new line should look like this:
command.Parameters.Add(new SqlParameter("@EarthRadius", 6374.611)); // In KM

With a small amount of work you could extract this to the web.config. Thats probably the best place for it.

Good luck!
Wednesday, April 11, 2007 2:48:05 PM (Eastern Standard Time, UTC-05:00)
Very useful. I have one question, how can I add paging? I'm showing the addresses in a datalist control, I want to show 9 addresses per page.
Cindy Spencer
Thursday, April 12, 2007 12:37:49 PM (Eastern Standard Time, UTC-05:00)
Cindy,
You could implement paging the same way you would implement paging in any other type of databound application. There are many ways to do it, this is going to be the simplest method, but not the most "efficient" method in regards to memory or bandwidth consumption.

Here are a few things you'd want to change:

- Storing the Dataset for filtering
I'd probably store the dataset somewhere, either in Session or in viewstate or another medium. If this is not a possibility, then you're going to have to alter the Sproc that gets the data and filter it from there.

The rest of this is based on the fact if you were to store the dataset in session and use a dataview or any other method.

- The call that is made to GetJsonHomeSpatialInfo .
I would pass in start and end parameters that would signify the record number to start from, to the record number that you want it to end at. Therefore only the proper JSON Variables are written to the page. You could also do this by using a dataview.

- Repeater Binding
I would probably bind this to either a dataview or a filter or something like that. Just look up paging on google and you'll find 1000 ways to apply this. This would be where you say you want records between x and y.

- Users Geo Info
You would probably want to save the users location info (Geo Coordinates) into session so that you dont keep requesting it upon each post back, remember, you only have 50K Geocode requests you can make in one day! Becareful, if its abused, Google said they will revoke abusers who abuse it!

I think that should do it!

Good luck.
Monday, April 16, 2007 11:32:14 AM (Eastern Standard Time, UTC-05:00)
I'm new to SQL 2005 and I have never attached a database so please bear with me, I am trying to attach the zipped SQL DB but I am getting the following error:

Could not find row in sysindexes for database ID 11, object ID 1, index ID 1. Run DBCC CHECKTABLE on sysindexes.
Could not open new database 'C:\DEVELOPMENT\STORELOCATOR\WEBSITE\APP_DATA\STORES.MDF'. CREATE DATABASE is aborted. (.Net SqlClient Data Provider)Could not find row in sysindexes for database ID 11, object ID 1, index ID 1. Run DBCC CHECKTABLE on sysindexes.
Could not open new database 'C:\DEVELOPMENT\STORELOCATOR\WEBSITE\APP_DATA\STORES.MDF'. CREATE DATABASE is aborted. (.Net SqlClient Data Provider)

Thank ahead for any and all help.
Tuesday, April 17, 2007 1:33:17 PM (Eastern Standard Time, UTC-05:00)
@pkm32,
Make sure that the file is unzipped. Also, when inside of SQL Management studio, be sure that you're right clicking on the Databases Folder and clicking "Attach". That should do it. I just downloaded the file, and attached it to my local machine (which is not the machine I developed the project on) and it worked fine. This should work, its a normal .mdf file with no modifications made to it.

Good luck!
Monday, October 22, 2007 12:51:59 AM (Eastern Standard Time, UTC-05:00)
I was just playing with this and was also getting similar errors to Paul. Is this a SQL 2005 database? I would like to do something similar, but I am stuck on an older version of SQL. Could this actually run against something like SQL 2000?

Thanks and awesome work.
Jeremy
Friday, November 16, 2007 7:31:56 AM (Eastern Standard Time, UTC-05:00)
Hey everybody,

I'm also having a problem with the sql, i have a full sql 2005 not express, does anyone know the connectionstring for this one?

Tx in advance!

Wim
Wim
Tuesday, December 11, 2007 12:10:05 PM (Eastern Standard Time, UTC-05:00)
Thanks man, very helpful!
timt
Tuesday, February 19, 2008 10:51:51 PM (Eastern Standard Time, UTC-05:00)
What a great feature to add to a site! Is it possible to display a line in the results for each individual result that says ({Distance Away From You} km from you).
Please let me know if this is possible and if so how?

Thanks
J.P.
Wednesday, February 27, 2008 5:04:05 PM (Eastern Standard Time, UTC-05:00)
is it possible to show the distance from each location to the destination? seems like you'd have that information somewhere.
courtney
Wednesday, February 27, 2008 5:56:48 PM (Eastern Standard Time, UTC-05:00)
Courtney,

This information is available, but the information that is available is only in radial miles. The distance will not be in actual miles traveled, but in distance from your specified origin to the destination - in "as the bird flys" miles.

I hope that helps!

Donn
Monday, March 03, 2008 3:43:52 PM (Eastern Standard Time, UTC-05:00)
It was really helpfull. When the input for the address field is "Elmhurst, NY" "Woodside, ny" "union city , ct" the map displays the map it a incorrect location, it returns the incorrect Lat and Long . Is there a way to correct this ? Does it have to do with my key? Thanks

/// <summary>
/// Gets a Coordinate from a address.
/// </summary>
/// <param name="address">An address.
/// <remarks>
/// <example>
/// 3276 Westchester Ave, Bronx, NY 10461
///
/// or
///
/// New York, NY
///
/// or
///
/// 10461 (just a zipcode)
/// </example>
/// </remarks>
/// </param>
/// <returns>A spatial coordinate that contains the latitude and longitude of the address.</returns
Jimmy Q.
Thursday, March 06, 2008 3:20:29 PM (Eastern Standard Time, UTC-05:00)
I believe that the problem arises when searching for a city with the same name. How can i know If there was more than one result.
I thought somehting like:
if (result.Placemark.length > 1)

but i wouldnt know where to put the code.
Thanks




JImmy Q.
Thursday, March 20, 2008 4:51:52 AM (Eastern Standard Time, UTC-05:00)
Hello,

In this part of the code: string[] geocodeInfo = client.DownloadString(uri).Split(',');

I always get this error:
"The remote server returned an error: (407) Proxy Authentication Required."

Can you help me out with this?
Thank you,
Augusto
Augusto
Thursday, March 20, 2008 9:08:10 AM (Eastern Standard Time, UTC-05:00)
@Augusto - Did you get a Google API Key? The link above "sign up for a free API key" is a link to sign up for a FREE Google API Key. Make sure you have that and make sure that it's in your web.config file.
Tuesday, April 22, 2008 10:12:21 AM (Eastern Standard Time, UTC-05:00)
This looks like it's exactly what i'm looking for but i'm new to .net and sql as our current sites are built on asp classic and access. when i try to run this on my local machine running visual studio 2005 and sql server express 2005 i get the following error;

"An error has occurred while establishing a connection to the server. When connecting to SQL Server 2005, this failure may be caused by the fact that under the default settings SQL Server does not allow remote connections. (provider: SQL Network Interfaces, error: 26 - Error Locating Server/Instance Specified)"

I've looked online but none of the hints seem to help solve the issue.

Thanks

Stu
Stuart
Saturday, April 26, 2008 3:42:10 PM (Eastern Standard Time, UTC-05:00)
hi,

I downloaded the visual studio file, and configured it. it all works perfectly and is exactly what I need.

However, when i upload it - it no longer works. I get the error:

The type or namespace name 'Coordinate' could not be found

and also sometimes get the error:

The type or namespace name 'GoogleGeocoder' cannot be found


have you got any idea why it would work on the local machine, but not once it's uploaded?

i'd appreciate your help.

-stuart.
stuart
Sunday, July 20, 2008 2:36:06 AM (Eastern Standard Time, UTC-05:00)
i have the problem can you help me please for solutions
i will apreciate reply

Server Error in '/StoreLocator' Application.
Compilation Error
Description: An error occurred during the compilation of a resource required to service this request. Please review the following specific error details and modify your source code appropriately.

Compiler Error Message: CS0234: The type or namespace name 'GoogleGeocoder' does not exist in the namespace 'System' (are you missing an assembly reference?)

Source Error:

Line 9: using System.Web.UI.HtmlControls;
Line 10: using System.Data.SqlClient;
Line 11: using System.GoogleGeocoder;
Line 12: using System.Text;
Line 13:
moris manzur
Sunday, July 20, 2008 10:31:42 AM (Eastern Standard Time, UTC-05:00)
@Moris -

Change line 11
From: "using System.GoogleGeocoder;"
To: "using GoogleGeocoder;"

The GoogleGeocoder class does not exist in the "System" namespace. Thats why you're getting that error.
Sunday, July 20, 2008 10:32:54 AM (Eastern Standard Time, UTC-05:00)
@stu - Make sure you install Sql Server Management Studio and open the server "surface area configuration" program. Then enable remote connections.
Good luck!
Donn
Friday, August 01, 2008 2:37:07 AM (Eastern Standard Time, UTC-05:00)
How come the address that the user searches for is not a marker on the map? Do you know how to add the users address as a temporary marker so that there is a quick way to spot the search location? and maybe even have a radius circle around the marker?
J
Friday, August 01, 2008 8:01:49 AM (Eastern Standard Time, UTC-05:00)
@J - You could add this very easily. Just look how its implemented for each of the addresses and add a call into the system to place a marker on the map for the origination point. You could use a different icon for it as well. Google for Google Map Icons and you'll find a plethora.
Friday, August 01, 2008 5:54:13 PM (Eastern Standard Time, UTC-05:00)
Thank you for the quick response. I will see if your hint helps me accomplish this.

I am trying to add three things... the temporary marker (custom icon different from the other markers in the database) for the users address, the circle radius around the users marker based on the radius selected, and a street view layer (street view shown below the map) with a toggle on/off switch.

If I am able to accomplish this I will report back. Any other hints you have are greatly appreciated. Thank you.
J
Sunday, August 03, 2008 7:55:14 AM (Eastern Standard Time, UTC-05:00)
Well I can't seem to figure this "easy" task out. If you can please help I would be very thankful.

If you only have time to help with one thing.... please help with how to add a temporary marker (custom icon different from the other markers in the database) for the users entered address.

I wish this was easy for me, but I have been trying for a few days now with no luck :( I guess I need to take a class or something because I can't seem to figure it out on my own.

Please help.
J
Tuesday, August 05, 2008 3:25:53 PM (Eastern Standard Time, UTC-05:00)
Please help.... I usually figure things our fairly quickly, but I am going on like 5 days now without any luck and/or hope. I would greatly appreciate your help.
J
Saturday, August 09, 2008 11:39:59 AM (Eastern Standard Time, UTC-05:00)
Donn,

Thank you for your email! You helped me so much and I appreciate it. Your solution was exactly what I needed and I did learn a lot using the program WinMerge that you suggested. Thank you!

J
J
Name
E-mail
(will show your gravatar icon)
Home page

Comment (HTML not allowed)  

Enter the code shown (prevents robots):

Live Comment Preview