StructureCMS

April 20, 2010

Google Maps – Create your own store locator

Filed under: AJAX, Web Development — joel.cass @ 12:43 pm

Recently I have had to create a store locator for a website I have been working on. On the outset, it sounds easy. Simply add a number of points to the map and calculate the distance between a point of reference and the points you have added.

Then the question comes up: How do you calculate distance? As the crow flies, or by road directions?

To test this out, I created a Google Maps Distance Calculator.

Road directions work, and are not too difficult to get from the API. However, they are dead slow and require callbacks. Sometimes a request may take up to 30 seconds, which means that it could take ages to render the distance between 10 or so points. Furthermore, sometimes the distance cannot be calculated at all.

So the more efficent and reliable way is to draw a line and measure it. That’s much easier and does not require a callback method:

var polyline = new GPolyline([
		point1.getLatLng(), // starting point
		point2.getLatLng() // finishing point
	],
	"#0000FF", // color
	1 // opacity
);
numDistance = polyline.getLength(); // in metres

I could go on about the intricacies of storing and sorting an array of points, but I won’t. Instead I have created a script that can do this for you. First off, you will need to get a google maps key. This is easy, you can do it here. Then, you need to call the google maps V2 API as follows:

<head>
...
<script type="text/javascript" src="http://www.google.com/jsapi?key=__________________KEY_________________"></script>
<script type="text/javascript">
	// initialise google maps
	google.load("maps", "2");
</script>
</head>
....

You will need to add some divs for containing the map and listing areas. By default, the map element is assumed to be a <div> tag and the listing container is a <table> tag.

<body>
...
	<div id="map"></div>
	<table id="table"></table>
...
</body>

(We will get into how you can change the listing container later)

Then, you will need to include the pointSorter.js script as follows:

<head>
...
</script>
<script type="text/javascript" src="pointSorter.js"></script>
<script type="text/javascript">
	// set target divs
	pointSorter.setDiv("map");
	pointSorter.setListingContainer("table");

	// load maps, point sorter etc
	google.setOnLoadCallback(function () {
		// initialise point sorter
		pointSorter.init();

		// add points
		...
	});
</script>
...
</head>

You may have noticed that I have not entered anything below “// add points” in the block above. There are two ways to add points:

// add point by coordinate
pointSorter.addPoint(
	new GLatLng(37.423156,-122.084917),
	"Google Inc.","<strong>Google Inc. Head Office</strong>"
);

// add point by address
pointSorter.addPointByAddress(
	"701 First Avenue, Sunnyvale, California",
	"<strong>Yahoo! Head Office</strong>"
);

Both methods take similar arguments. The first takes a GLatLng (point) object + a description, whilst the second takes an address and a description. The first method is much faster than the second as no request has to be made to the server to look up an address.

By now (after some style tweaking), your code may be looking something like this:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>
	<title>Maps Test</title>
	<style type="text/css">
		body { font-family:sans-serif; font-size:0.8em; }
		/* ID Selectors */
		#map {
			width:70%;
			height:500px;
			float:left;
		}
		#table {
			margin-top:1em;
			width:30%;
			height:500px;
			overflow-y:auto;
			float:right;
		}
		/* classes */
		.info { color:#008000; }
	</style>
	<script type="text/javascript" src="http://www.google.com/jsapi?key=_____________KEY____________"></script>
	<script type="text/javascript" src="pointSorter.js"></script>
	<script type="text/javascript">
	// initialise google maps
	google.load("maps", "2");

	// set target divs
	pointSorter.setDiv("map");
	pointSorter.setListingContainer("table");

	// load maps, point sorter etc
	google.setOnLoadCallback(function () {
		// initialise point sorter
		pointSorter.init();

		// add point by coordinate
		pointSorter.addPoint(
			new GLatLng(37.423156,-122.084917),
			"Google Inc.","<strong>Google Inc. Head Office</strong>"
		);

		// add point by address
		pointSorter.addPointByAddress(
			"701 First Avenue, Sunnyvale, California",
			"<strong>Yahoo! Head Office</strong>"
		);

	});
	</script>
</head>

<body>

<div id="map"></div>
<table id="table"></table>

</body>
</html>

If you run this in your browser, you should see a map appear with two locations shown. Clicking on either will reveal the name of the location.

Now the only thing left to do is allow users to enter in an address. This is as simple as adding the method and form below. When the form is “submitted”, a search is made for the address.

<script type="text/javascript">
	...
	function setLocation (location) {
		// set point of reference
		pointSorter.setPointOfReference(
			location,
			"<strong>Reference Location</strong><p>"+location+"</p>"
		);
	}
	</script>
</head>

<body>

<form action="" onsubmit="setLocation(this.address.value);return false;">
	<input type="text" name="address" />
	<input type="submit" value="Set Reference Location" />
</form>
...
</html>

When the form is submitted, a new point is added to the map and the distances are shown.

Here are some other tips about the script.

You can set icons for the map pointers by using the following methods:

// set icon for reference location
pointSorter.setHomeIcon("http://www.google.com/mapfiles/dd-start.png", 20, 34);
// set icon for surrounding point locations
pointSorter.setPointerIcon("http://www.google.com/mapfiles/dd-end.png", 20, 34);

If you have a lot of locations, you can limit the display to only list locations within a certain distance from the reference point, e.g. 100km:

// distance in kilometres
pointSorter.setDistanceThreshold(100)

if you are not going to use a table, you need to set the item template for listing items. The example below allows your items to be listed inside an unordered list (<ul>) tag:

pointSorter.setItemTemplate(
	"
  • " + " {description} ({distance_direct}km)" + "
  • " );

    20 Comments »

    1. [...] This post was mentioned on Twitter by MultiChannel Attrib. MultiChannel Attrib said: Google Maps 13 Create your own store locator « Jozza.net http://bit.ly/9ihYlJ [...]

      Pingback by Tweets that mention Google Maps – Create your own store locator « Jozza.net -- Topsy.com — April 21, 2010 @ 1:42 pm

    2. How would you code this for miles?

      Comment by Felipe — May 12, 2010 @ 4:31 am

    3. Given that 1km = 1.609344 miles, you could simply alter the input and output by multiplying or dividing respectively.

      e.g. Search within 10mi = 10 * 1.609344 = 16km
      A distance of 32km = 32 / 1.609344 = 20mi

      Comment by joel.cass — May 12, 2010 @ 4:47 pm

    4. Joel, thanks for this demo and access to the source file. It’s exactly what I was looking for. It seems to have trouble in IE though…have you run into that at all? Any possible solution?

      Comment by Kevin — October 20, 2010 @ 2:39 am

    5. NVM, got it working.

      Comment by Kevin — October 21, 2010 @ 1:33 am

    6. Thanks for the superb code and demo here Joel. I’m tweaking it and trying to use it for a store locator on my website. The only issue I’m running across is getting the “distance_direct” in pointsorter.js to return me a number in miles. I already added the code to change km to miles in the input in the main html page. Its just in this snippet (pasted below) where I’m trying to manipulate it but it looks like distance_direct is being returned as a text field here.

      pointSorter.strItemTemplate =
      “” +
      ” {distance_direct} km” +
      ” {description}” +
      “”;

      Any help would be appreciated!

      Comment by Kavita — December 9, 2010 @ 4:56 am

    7. Joel or Kevin – I’m also having trouble with the table showing in IE – what was your fix Kevin?

      Comment by Matt — December 15, 2010 @ 9:51 am

    8. @Matt / @Kevin,

      The issue was actually in the demos – I had written it originally to use a table but later changed it to use div elements. Unfortunately I forgot to update the HTML – divs were being added to a table element, which is incorrect. Firefox ignored the issue, while IE had a spaz.

      Demos have been updated.

      Comment by joel.cass — December 15, 2010 @ 2:33 pm

    9. @Kavita,

      I have added the {distance_direct_miles} param to the script (line 283).

      Comment by joel.cass — December 15, 2010 @ 2:35 pm

    10. Great job. I just plugged this in on my site and works fine. Just takes a moment to load because I have 56 entries and don’t care to enter the Lat and Long for each one :)

      It does randomly tell me, as its loading, that it can’t find some addresses but they don’t have that problem if I reload (maybe a different address) is this because it is taking time to find them all?

      Comment by Brandon Jackson — January 4, 2011 @ 7:59 am

    11. Hi Brandon,

      I’ve had that issue before and it is usually due to too many connections to the google servers at the same time. The points should be processed in a serial manner provided that you are calling the pointSorter.addPointByAddress() method.

      A tool I have been using to debug such situations is ‘tamper data’ for firefox. It shows the running requests and their status codes.

      Alternatively, convert all the addresses to coordinates and it will run much faster. One app I wrote had 2000+ locations that I was loading by address, needless to say it did not run well, or at all in some computers. Converting the addresses to points made it run very smoothly. To convert the addresses to points you can either use the maps AJAX API (one thing tamper data helped me figure out) or just populate a text box by modifying the pointSorter.addPoint() method, e.g. txtBox.value += "\r\n" + address + ": " + point.getLatLng().lat + ", " + point.getLatLng().lng.

      Hope that helps

      Comment by joel.cass — January 4, 2011 @ 1:19 pm

    12. That’s very helpful, thanks. I’ll have to check it out. I have it all pulling through an XML sheet (easier for others to update than Javascript).

      Guess I’ll just have to suck it up and put the coordinates in there.

      Comment by Brandon Jackson — January 5, 2011 @ 1:08 am

    13. How can I make the info window larger. Currently I have a link that is outside of the window. Thanks for the code this was exceptionally helpful!!!

      Comment by Tony — February 9, 2011 @ 12:14 pm

    14. Hi Tony,

      The info window size is controlled by the maps API. I’m not sure of how they calculate it. But try this.

      In the pointSorter.js, line 241 replace the code as follows: pointSorter.aryMarkers[i].openInfoWindow(pointSorter.aryMarkers[i].description, {maxWidth:99999});

      Comment by joel.cass — February 10, 2011 @ 2:12 pm

    15. Awesome application :)

      Just one question. Is it possible to change the default location? Is allways loads somewhere near Australia, but i would like him to automaticly load Europe.

      Any help would by appriciated.

      Comment by WJ — July 8, 2011 @ 12:01 am

    16. How can you make the map load on a custom location?
      By default it will load somewhere near Australia.

      Thanks.

      Comment by Wietze — July 8, 2011 @ 4:45 pm

    17. @WJ – the default location is calculated as the “average” of all the points on the map, and zoom is adjusted to include the points accordingly.

      However if this is not working, you could set the point of reference to a location you wish to center the map on. Not the best solution, I know, but I’ll have a look when time permits.

      Comment by joel.cass — August 17, 2011 @ 10:48 am

    18. Hey we’ve been using this for a while and it works great. I modified it to work with an XML list we use to updated the dealer locations for our store.

      Was wondering if you had a version or a quick conversion of this that works with the new Version 3 API, which doesn’t require an API Key and I believe has a few more HTML5 features.

      Thanks for the code again, works great we’re just moving to more HTML5 standards and would love to update this if possible.

      Comment by Brandon — November 4, 2011 @ 3:56 am

    19. Is it possible to display all the locations in the location_table when the page first loads, then reorder the table when somebody makes a search?

      Any help would be massively appreciated. :D

      Comment by Darren — January 11, 2013 @ 7:36 pm

    20. HI there, great store locator. I know this is a very old post but been using this store locator for just over a year now. Like Brandon I was wondering if you have thought about updating this project to use the Google 3 API?

      Comment by Darren — October 1, 2013 @ 7:28 pm

    RSS feed for comments on this post. TrackBack URL

    Leave a comment